Effetti su sprite e testo

Ombra, contorno, glow e sfocatura su sprite 2D e su testo, con quanto costa ognuno, cosa fa a uno sprite in movimento, e la ricetta fatta a mano che sostituisce.

A cosa serve#

Finora uno sprite 2D poteva portare una tinta e un'immagine, e nient'altro. Per staccare un personaggio dal suo sfondo dovevi costruire l'effetto a mano: creare un secondo sprite, tingerlo di nero, spostarlo, metterlo dietro, e tenerlo sincronizzato con il primo a ogni singolo fotogramma. Funziona. Sono anche quattro righe extra di contabilità per personaggio, e il giorno in cui ne dimentichi una l'ombra rimane indietro rispetto al suo proprietario.

Quattro comandi lo fanno al posto tuo, su sprite e su testo:

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

Nient'altro da mantenere. Sposta lo sprite, scalalo, capovolgilo, riproduci la sua animazione: gli effetti seguono.

I quattro effetti#

Ombra#

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

offsetX e offsetY sono nelle tue unità schermo, contate allo stesso modo di SpritePosition: la Y positiva è verso il basso. softness allarga l'ombra; opacity va da 0 a 255. Il colore è nero a meno che tu non dica diversamente.

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

Un'ombra di opacità 0 non è un'ombra. È così che la disattivi.

Contorno#

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

Il contorno segue la silhouette dello sprite, non il rettangolo in cui vive: un PNG trasparente ottiene un contorno intorno al disegno, non intorno al file. Lo spessore è in unità schermo, e da 1 a 3 è l'intervallo utile. Oltre quello, le otto direzioni con cui il contorno è costruito iniziano a mostrarsi come otto lobi.

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)

Un alone colorato, che si diffonde per size unità oltre la silhouette. Sta dietro allo sprite e davanti all'ombra, il che è ciò che ti permette di usarli entrambi.

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

Sfocatura#

SpriteBlur(spriteID, radius)

La sfocatura è quella diversa dalle altre. Le altre tre vengono disegnate intorno allo sprite; la sfocatura sostituisce l'immagine dello sprite stessa con una sua copia sfocata. Questo comporta due cose da sapere:

  • non costa nulla per fotogramma (l'immagine sfocata viene calcolata una sola volta), e
  • la sfocatura viene tagliata al bordo dell'immagine. Un disegno che tocca il bordo del suo PNG perde la parte del suo alone che finirebbe fuori. Lascia qualche pixel trasparente di margine nel file se pensi di sfocarlo.

SpriteColor continua a funzionare su uno sprite sfocato.

Spegnere tutto, e riaccenderlo#

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

SpriteEffects non cancella le tue impostazioni. È un interruttore, non un azzeramento, così un personaggio può lampeggiare senza che tu debba ridichiarare la sua ombra dopo.

Gli stessi quattro sul testo#

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)

Le firme corrispondono esattamente a quelle degli sprite, quindi non c'è nulla di nuovo da imparare. Due differenze di comportamento:

  • Un glow vince sulla morbidezza di un'ombra. Entrambi sono disegnati dallo stesso meccanismo, e un glow senza morbidezza è invisibile mentre un'ombra senza morbidezza resta comunque un'ombra. Quando chiedi entrambi, il glow ottiene la morbidezza.
  • Questi si applicano al testo vettoriale, non al testo bitmap-font. Un testo disegnato con TextFontImage è una composizione di sprite; i comandi vengono ignorati in silenzio su di esso. Usa gli effetti sprite sugli sprite in quel caso.

Un esempio pratico: leggibile su tutto#

Il problema che questi comandi risolvono davvero è il contrasto che non controlli. Un punteggio bianco è invisibile sulla neve; uno nero è invisibile di notte. Un contorno lo risolve una volta per tutte, per ogni sfondo che disegnerai mai.

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

Rileggere un effetto#

Ogni parametro che scrivi ha un lettore, allo stesso modo in cui SpriteColor ha SpriteColorRed. Questa è la regola del dialetto, e questi comandi la seguono.

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

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

L'insieme completo, per famiglia: SpriteShadowOffsetX, SpriteShadowOffsetY, SpriteShadowBlur, SpriteShadowAlpha, SpriteShadowColorRed, SpriteShadowColorGreen, SpriteShadowColorBlue, SpriteGlowRed, SpriteGlowGreen, SpriteGlowBlue, SpriteGlowSize, SpriteOutlineThickness, SpriteOutlineRed, SpriteOutlineGreen, SpriteOutlineBlue, SpriteBlurRadius, SpriteEffectsEnabled, più gli stessi diciassette con un prefisso Text.

Due regole da sapere:

  • Uno sprite a cui non è mai stato assegnato un effetto si dichiara ACCESO. È lo stato predefinito: rispondere 0 direbbe a un programma che i suoi effetti sono disattivati prima ancora che ne abbia impostato uno.
  • Chiedere di uno sprite che non esiste risponde 0, non ferma il programma. È così che il dialetto tratta le domande ovunque: SpriteHit risponde 0 quando nulla viene toccato, e i programmi leggono quello 0 senza testarlo.

Cosa costa#

Ombra, contorno e glow vengono disegnati, quindi costano chiamate di disegno: una per l'ombra (tre quando è morbida), quattro per un glow, otto per un contorno. Uno schermo con duecento sprite con contorno è uno schermo che disegna milleottocento quad, e si vedrà. Sono pensati per la manciata di cose che il giocatore deve davvero guardare: il personaggio, l'oggetto da raccogliere, l'unità selezionata.

La sfocatura non costa nulla per fotogramma.

Una regola pratica: effetti su ciò che si muove e conta, nessuno sullo sfondo.

Cosa passa in un gioco esportato#

TargetOmbra, contorno, glowSfocaturaEffetti testo
L'editor
Esportazione HTML5
Nativo e wasm (Bevy)nono

Ombra, contorno e glow sono costruiti con sprite ordinari, quindi viaggiano ovunque viaggino gli sprite. La sfocatura e gli effetti testo hanno bisogno di un ponte che i target Bevy non hanno ancora: il rapporto di conversione dell'esportazione li nomina invece di scartarli in silenzio, così una build ti dice cosa non ha potuto portare.

Dove gli effetti non ci sono#

Non esiste un SpriteEffect(id, "glow", ...) che prende il nome dell'effetto come stringa. Ogni effetto è un comando a sé, così i suoi parametri possono essere nominati, documentati e completati mentre digiti. Per qualsiasi cosa oltre questi quattro, altri due comandi entrano in gioco: SpriteBlendModes(id, src, dst) per una fusione additiva o moltiplicativa (un fuoco, un laser), e SpriteShader(id, shaderID) per mettere su uno sprite uno shader compilato da LoadSpriteShader. Entrambi erano dichiarati nel catalogo da tempo senza un'implementazione, e chiamarli sollevava un errore; ora funzionano.