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:
SpriteHitrisponde 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#
| Target | Ombra, contorno, glow | Sfocatura | Effetti testo |
|---|---|---|---|
| L'editor | sì | sì | sì |
| Esportazione HTML5 | sì | sì | sì |
| Nativo e wasm (Bevy) | sì | no | no |
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.