スプライトとテキストのエフェクト

2Dスプライトとテキストに影、アウトライン、グロー、ぼかしを付ける。それぞれのコスト、動くスプライトに対する挙動、そしてこれが置き換える手作りのレシピ。

これは何のためにあるか#

これまで2Dスプライトが持てるのはティントと画像だけで、それ以外は何もありませんでした。キャラクターを背景から浮き上がらせるには、エフェクトを手作りするしかありませんでした。2枚目のスプライトを作り、それを黒くティントし、オフセットし、後ろに置き、そして1枚目と、フレームごとにずれないよう同期させ続けるのです。これは機能します。ただし、キャラクター1体につき4行分の余計な帳簿仕事であり、そのうちの1行を忘れた日に、影が持ち主から遅れてついてくることになります。

4つのコマンドが、これをスプライトとテキストの両方に対して肩代わりします。

hero = LoadSprite("hero.png")
SpritePosition(hero, 160, 120)

SpriteShadow(hero, 4, 4, 6, 128)        ' offset x, offset y, softness, opacity
SpriteOutline(hero, 2, 255, 255, 255)   ' thickness, colour
SpriteGlow(hero, 0, 240, 255, 8)        ' colour, size

DO
  Sync()
LOOP

それ以上、維持するものはありません。スプライトを動かし、拡大縮小し、反転させ、アニメーションを再生させても、エフェクトは追従します。

4つのエフェクト#

Shadow(影)#

SpriteShadow(spriteID, offsetX, offsetY, softness, opacity)
SpriteShadowColor(spriteID, red, green, blue)

offsetXoffsetYは、SpritePositionと同じ数え方をする、自分自身の画面単位です。正のYは下方向です。softnessは影を広げ、opacityは0から255です。色は指定しない限り黒です。

SpriteShadow(hero, 3, 5, 4, 110)
SpriteShadowColor(hero, 20, 0, 40)      ' a cold shadow, not a grey one

不透明度0の影は影ではありません。それがオフにする方法です。

Outline(アウトライン)#

SpriteOutline(spriteID, thickness, red, green, blue)

アウトラインは、スプライトが収まっている矩形ではなくシルエットに沿います。透過PNGなら、ファイルの周りではなく描かれた絵の周りにアウトラインが付きます。太さは画面単位で、1から3が実用的な範囲です。それを超えると、アウトラインを構成する8方向が8つの出っ張りとして見え始めます。

SpriteOutline(hero, 2, 255, 255, 255)   ' the readable-on-anything outline
SpriteOutline(hero, 0, 0, 0, 0)         ' off

Glow(グロー)#

SpriteGlow(spriteID, red, green, blue, size)

シルエットの外側size単位まで広がる、色付きのハローです。スプライトの後ろ、影の前に位置するので、両方を同時に使えます。

SpriteGlow(pickup, 255, 210, 0, 10)     ' this one is worth walking to

Blur(ぼかし)#

SpriteBlur(spriteID, radius)

Blurだけは毛色が違います。他の3つはスプライトの周りに描かれますが、blurはスプライト自身の画像を、ぼかしたコピーに置き換えます。これには知っておく価値のある2つの意味があります。

  • 1フレームあたりのコストがゼロです(ぼかした画像は一度だけ計算されます)。
  • ぼかしは画像の端でクリップされます。PNGの端に触れている絵は、そのハローのうち画像の外にはみ出す部分を失います。ぼかすつもりのファイルには、透明なピクセルの余白を少し残しておいてください。

SpriteColorは、ぼかしたスプライトに対しても引き続き機能します。

すべてをオフにし、また戻す#

SpriteEffects(spriteID, 0)   ' all four off
SpriteEffects(spriteID, 1)   ' all four back, exactly as they were

SpriteEffectsは設定を消去しません。リセットではなくスイッチなので、キャラクターは、後で改めて影を宣言し直すことなく点滅できます。

テキストにも同じ4つを#

score = CreateText("SCORE 0")
TextPositionSet(score, 10, 10)
TextSizeSet(score, 6)

TextOutline(score, 3, 0, 0, 0)          ' readable on any background
TextShadow(score, 2, 2, 4, 160)
TextGlow(score, 255, 80, 0, 6)
TextBlur(score, 0)
TextEffects(score, 1)

シグネチャはスプライト側とまったく同じなので、新しく覚えることはありません。挙動の違いは2つです。

  • グローはシャドウのsoftnessに勝ちます。 両方とも同じ仕組みで描かれており、softnessのないグローは見えるのに、softnessのないシャドウはそれでも影として見えます。両方を要求すると、softnessはグローに与えられます。
  • これらはベクターテキストに適用され、ビットマップフォントのテキストには適用されません。 TextFontImageで描かれたテキストはスプライトの合成物なので、これらのコマンドはそこでは静かに無視されます。その場合はスプライト用のエフェクトを、そのスプライトに対して使ってください。

実例:どんな背景でも読める文字#

これらのコマンドが実際に解決している問題は、自分では制御できないコントラストです。白いスコアは雪の上で見えなくなり、黒いスコアは夜に見えなくなります。アウトラインは、これからあなたが描くあらゆる背景に対して、一度でこれを解決します。

CreateText(1, "SCORE 0")
TextPositionSet(1, 10, 10)
TextSizeSet(1, 8)
TextColorSet(1, 255, 255, 255, 255)
TextOutline(1, 3, 0, 0, 0)

hero = LoadSprite("hero.png")
SpriteName(hero, "hero")
SpriteShadow(hero, 4, 6, 5, 120)
SpriteOutline(hero, 2, 255, 255, 255)

points = 0
DO
  points = points + 1
  TextString(1, "SCORE " + Str(points))
  Sync()
LOOP

エフェクトを読み戻す#

書き込んだパラメータにはすべて、SpriteColorに対するSpriteColorRedのように、読み取り用のコマンドがあります。これはこの方言の規則であり、これらのコマンドもそれに従います。

IF SpriteEffectsEnabled(hero) = 1
  SpriteEffects(hero, 0)
ENDIF

alpha = SpriteShadowAlpha(hero)      ' 0 to 255
size  = SpriteGlowSize(hero)         ' 0 means no glow

ファミリーごとの全セットです。SpriteShadowOffsetXSpriteShadowOffsetYSpriteShadowBlurSpriteShadowAlphaSpriteShadowColorRedSpriteShadowColorGreenSpriteShadowColorBlueSpriteGlowRedSpriteGlowGreenSpriteGlowBlueSpriteGlowSizeSpriteOutlineThicknessSpriteOutlineRedSpriteOutlineGreenSpriteOutlineBlueSpriteBlurRadiusSpriteEffectsEnabled、そして同じ17個に Text接頭辞を付けたものです。

知っておく価値のある2つの規則です。

  • 一度もエフェクトを与えられたことのないスプライトは、自分をONだと報告します。 それが既定の状態です。0と答えてしまうと、エフェクトを何も設定していない段階なのに、それが切られていると伝えてしまうことになります。
  • 存在しないスプライトについて尋ねると0と答え、プログラムは止まりません。 これはこの方言が、あらゆる問いをどう扱うかという流儀です。SpriteHitは何にも触れていないとき0と答え、プログラムはその0を、テストせずにそのまま読みます。

コスト#

Shadow、outline、glowは描かれるものなので、描画コールのコストがかかります。影は1回(ソフトなときは3回)、グローは4回、アウトラインは8回です。アウトライン付きのスプライトが200個ある画面は、1800個のクアッドを描く画面であり、それは目に見えて重くなります。これらは、プレイヤーが見るべき一握りのもの、キャラクター、拾えるアイテム、選択中のユニットのために用意されています。

Blurは1フレームあたりのコストがゼロです。

実用的な原則としては、動いていて大事なものにエフェクトを、背景には何も付けないことです。

書き出したゲームに何が引き継がれるか#

ターゲットShadow、outline、glowBlurText effects
エディタyesyesyes
HTML5書き出しyesyesyes
ネイティブおよびwasm(Bevy)yesnono

Shadow、outline、glowは通常のスプライトから組み立てられているので、スプライトが行く場所ならどこへでも運ばれます。BlurとText effectsは、Bevyのターゲットがまだ持っていない橋渡しを必要とします。書き出しのコンバージョンレポートはそれらを名指しし、黙って落とすことはしません。だからビルドは、何を運べなかったかを教えてくれます。

これらのエフェクトが存在しない場所#

エフェクト名を文字列として受け取るSpriteEffect(id, "glow", ...)のようなコマンドはありません。各エフェクトが独自のコマンドになっているのは、そのパラメータに名前を付け、文書化し、入力中に補完できるようにするためです。この4つを超える用途には、他の2つのコマンドがあります。加算合成や乗算合成のブレンド(炎、レーザー)用のSpriteBlendModes(id, src, dst)と、LoadSpriteShaderでコンパイルしたシェーダーをスプライトに乗せるSpriteShader(id, shaderID)です。どちらも長い間、実装のないままカタログに宣言されており、呼び出すとエラーになっていましたが、今は動きます。