스프라이트와 텍스트 이펙트

2D 스프라이트와 텍스트 위의 그림자, 아웃라인, 글로우, 블러. 각각의 비용, 움직이는 스프라이트에 미치는 영향, 그리고 이것이 대체하는 손수 만든 레시피까지.

이것은 무엇을 위한 것인가#

지금까지 2D 스프라이트는 틴트와 이미지만 가질 수 있었고, 그 외에는 아무것도 없었습니다. 캐릭터를 배경에서 떠오르게 하려면 이펙트를 손으로 만들어야 했습니다: 두 번째 스프라이트를 만들고, 검게 물들이고, 오프셋을 주고, 뒤에 놓은 다음, 매 프레임 첫 번째 스프라이트와 발을 맞춰야 했습니다. 작동은 합니다. 하지만 캐릭터 하나당 네 줄의 추가 잡무이며, 그중 하나라도 잊는 날에는 그림자가 주인 뒤를 질질 끌려다닙니다.

네 개의 명령이 이를 대신해줍니다, 스프라이트에도 텍스트에도.

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

더 이상 유지 관리할 것이 없습니다. 스프라이트를 움직이고, 크기를 바꾸고, 뒤집고, 애니메이션을 재생하세요: 이펙트가 따라갑니다.

네 가지 이펙트#

Shadow#

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

offsetXoffsetYSpritePosition과 같은 방식으로 세어지는, 사용자 자신의 화면 단위입니다: 양수 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이 유용한 범위입니다. 그 이상이 되면 아웃라인을 구성하는 여덟 방향이 여덟 개의 돌출부로 드러나기 시작합니다.

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)

블러는 예외입니다. 다른 세 가지는 스프라이트 주위에 그려지지만, 블러는 스프라이트 자신의 이미지를 흐릿한 사본으로 교체합니다. 이는 알아둘 가치가 있는 두 가지를 뜻합니다.

  • 프레임당 비용이 전혀 들지 않습니다(흐릿한 이미지는 한 번만 계산됩니다), 그리고
  • 블러는 이미지의 가장자리에서 잘립니다. PNG의 가장자리에 닿는 그림은 그 바깥으로 나갈 후광의 일부를 잃습니다. 블러를 적용할 계획이라면 파일에 투명 픽셀 여백을 몇 개 남겨두세요.

SpriteColor는 블러 처리된 스프라이트에서도 계속 작동합니다.

모두 끄기, 그리고 다시 켜기#

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

SpriteEffects는 설정을 지우지 않습니다. 이는 리셋이 아니라 스위치이므로, 캐릭터는 이후에 그림자를 다시 선언할 필요 없이 번쩍일 수 있습니다.

텍스트에서도 같은 네 가지#

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)

시그니처는 스프라이트 쪽과 정확히 일치하므로, 새로 배울 것이 없습니다. 동작에는 두 가지 차이가 있습니다.

  • 글로우는 그림자의 소프트니스를 이깁니다. 둘 다 같은 메커니즘으로 그려지며, 소프트니스가 없는 글로우는 보이지 않는 반면 소프트니스가 없는 그림자는 여전히 그림자입니다. 둘 다 요청하면 글로우가 소프트니스를 가져갑니다.
  • 이것들은 벡터 텍스트에만 적용되며, 비트맵 폰트 텍스트에는 적용되지 않습니다. 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

이펙트 값 다시 읽기#

작성한 모든 파라미터는 읽는 방법도 있습니다. SpriteColorSpriteColorRed가 있는 것처럼요. 그것이 이 방언의 규칙이며, 이 명령들도 그 규칙을 따릅니다.

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

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

계열별 전체 목록입니다: SpriteShadowOffsetX, SpriteShadowOffsetY, SpriteShadowBlur, SpriteShadowAlpha, SpriteShadowColorRed, SpriteShadowColorGreen, SpriteShadowColorBlue, SpriteGlowRed, SpriteGlowGreen, SpriteGlowBlue, SpriteGlowSize, SpriteOutlineThickness, SpriteOutlineRed, SpriteOutlineGreen, SpriteOutlineBlue, SpriteBlurRadius, SpriteEffectsEnabled, 그리고 같은 열일곱 개가 Text 접두사와 함께 있습니다.

알아둘 가치가 있는 두 가지 규칙입니다.

  • 한 번도 이펙트가 지정된 적 없는 스프라이트는 자신이 켜져 있다고 보고합니다. 그것이 기본 상태입니다. 0으로 답한다면 프로그램에게 아무 이펙트도 설정하지 않았는데 이펙트가 꺼져 있다고 말하는 셈이 될 것입니다.
  • 존재하지 않는 스프라이트에 대해 물어도 0으로 답할 뿐, 프로그램을 멈추지 않습니다. 이 방언이 어디서나 질문을 다루는 방식이 그렇습니다: SpriteHit는 아무것도 맞지 않았을 때 0으로 답하고, 프로그램은 그 0을 검증 없이 그대로 읽습니다.

비용#

그림자, 아웃라인, 글로우는 그려지므로 드로우콜 비용이 듭니다: 그림자 하나(소프트할 때는 셋), 글로우 넷, 아웃라인 여덟입니다. 아웃라인이 있는 스프라이트 200개가 있는 화면은 1,800개의 쿼드를 그리는 화면이며, 그것은 티가 납니다. 이 이펙트들은 플레이어가 눈여겨봐야 할 소수를 위한 것입니다: 캐릭터, 픽업 아이템, 선택된 유닛.

블러는 프레임당 비용이 전혀 들지 않습니다.

실용적인 규칙: 움직이고 중요한 것에는 이펙트를, 배경에는 아무것도 넣지 마세요.

내보낸 게임으로 넘어가는 것#

대상Shadow, outline, glowBlurText effects
에디터지원지원지원
HTML5 내보내기지원지원지원
네이티브와 wasm (Bevy)지원미지원미지원

그림자, 아웃라인, 글로우는 평범한 스프라이트로 만들어지므로 스프라이트가 가는 곳이면 어디든 함께 이동합니다. 블러와 텍스트 이펙트는 Bevy 대상이 아직 갖고 있지 않은 다리가 필요합니다. 내보내기 변환 보고서가 이를 조용히 빠뜨리는 대신 이름으로 알려주므로, 빌드는 무엇을 옮기지 못했는지 알려줍니다.

이펙트가 없는 곳#

이펙트 이름을 문자열로 받는 SpriteEffect(id, "glow", ...) 같은 것은 없습니다. 각 이펙트는 자신만의 명령을 가지므로 그 파라미터에 이름을 붙이고, 문서화하고, 입력하면서 자동완성할 수 있습니다. 이 네 가지를 넘어서는 것을 위해서는 두 개의 다른 명령이 대신합니다: 가산 또는 곱연산 블렌딩(불, 레이저)을 위한 SpriteBlendModes(id, src, dst), 그리고 LoadSpriteShader로 컴파일된 셰이더를 스프라이트에 올리는 SpriteShader(id, shaderID)입니다. 둘 다 오랫동안 구현 없이 카탈로그에 선언되어 있었고, 호출하면 에러가 발생했지만, 이제는 작동합니다.