Efekty sprite'ów i tekstu
Cień, obrys, poświata i rozmycie na sprite'ach 2D i na tekście, ile kosztuje każdy z nich, co robi z poruszającym się sprite'em i jaką ręcznie sklejaną metodę zastępuje.
Do czego to służy#
Do tej pory sprite 2D mógł nosić tint i obraz, i nic więcej. Żeby oderwać postać od tła, trzeba było zbudować efekt ręcznie: stworzyć drugi sprite, zabarwić go na czarno, przesunąć, umieścić z tyłu i trzymać go w kroku z pierwszym na każdej pojedynczej klatce. To działa. To też cztery dodatkowe linie księgowości na postać, i w dniu, w którym zapomnisz o jednej z nich, cień zaczyna wlec się za swoim właścicielem.
Cztery polecenia robią to za ciebie, na sprite'ach i na tekście:
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
Nic więcej do utrzymywania. Przesuń sprite, przeskaluj go, odbij, odtwórz jego animację: efekty podążają za nim.
Cztery efekty#
Cień#
SpriteShadow(spriteID, offsetX, offsetY, softness, opacity)
SpriteShadowColor(spriteID, red, green, blue)
offsetX i offsetY są w twoich własnych jednostkach ekranu, liczonych tak
samo jak w SpritePosition: dodatnie Y to w dół. softness rozprasza
cień; opacity biegnie od 0 do 255. Kolor jest czarny, chyba że powiesz
inaczej.
SpriteShadow(hero, 3, 5, 4, 110)
SpriteShadowColor(hero, 20, 0, 40) ' a cold shadow, not a grey one
Cień o opacity 0 to brak cienia. Tak właśnie go wyłączasz.
Obrys#
SpriteOutline(spriteID, thickness, red, green, blue)
Obrys podąża za sylwetką sprite'a, nie za prostokątem, w którym żyje: przezroczysty PNG dostaje obrys wokół rysunku, nie wokół pliku. Thickness jest w jednostkach ekranu, a użyteczny zakres to 1 do 3. Powyżej tego osiem kierunków, z których budowany jest obrys, zaczyna być widoczne jako osiem płatków.
SpriteOutline(hero, 2, 255, 255, 255) ' the readable-on-anything outline
SpriteOutline(hero, 0, 0, 0, 0) ' off
Poświata#
SpriteGlow(spriteID, red, green, blue, size)
Kolorowa poświata, rozprzestrzeniająca się o size jednostek poza sylwetkę.
Siedzi za sprite'em i przed cieniem, co pozwala używać obu naraz.
SpriteGlow(pickup, 255, 210, 0, 10) ' this one is worth walking to
Rozmycie#
SpriteBlur(spriteID, radius)
Rozmycie jest tym nietypowym. Pozostałe trzy są rysowane wokół sprite'a; rozmycie zastępuje własny obraz sprite'a jego rozmytą kopią. To oznacza dwie rzeczy warte poznania:
- nic nie kosztuje na klatkę (rozmyty obraz jest liczony raz), oraz
- rozmycie jest przycinane na krawędzi obrazu. Rysunek, który dotyka krawędzi swojego PNG, traci tę część poświaty, która wypadłaby poza plik. Zostaw kilka przezroczystych pikseli marginesu w pliku, jeśli planujesz go rozmyć.
SpriteColor nadal działa na rozmytym sprite'cie.
Wyłączanie wszystkiego i włączanie z powrotem#
SpriteEffects(spriteID, 0) ' all four off
SpriteEffects(spriteID, 1) ' all four back, exactly as they were
SpriteEffects nie kasuje twoich ustawień. To przełącznik, nie reset, więc
postać może błysnąć, a ty nie musisz potem na nowo deklarować jej cienia.
Te same cztery na tekście#
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)
Sygnatury zgadzają się dokładnie z tymi od sprite'ów, więc nie ma niczego nowego do nauczenia. Dwie różnice w zachowaniu:
- Poświata wygrywa z miękkością (softness) cienia. Obie są rysowane tym samym mechanizmem, a poświata bez softness jest niewidoczna, podczas gdy cień bez softness wciąż jest cieniem. Gdy poprosisz o obie naraz, to poświata dostaje softness.
- Działają na tekście wektorowym, nie na tekście z czcionki bitmapowej.
Tekst narysowany za pomocą
TextFontImageto kompozycja sprite'ów; polecenia są na nim po cichu ignorowane. W takim przypadku użyj efektów sprite'ów na samych sprite'ach.
Przykład: czytelne na wszystkim#
Problem, który te polecenia naprawdę rozwiązują, to kontrast, którego nie kontrolujesz. Biały wynik jest niewidoczny na śniegu; czarny jest niewidoczny w nocy. Obrys rozwiązuje to raz, na każde tło, jakie kiedykolwiek narysujesz.
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
Odczytywanie efektu z powrotem#
Każdy parametr, który zapisujesz, ma swój odpowiednik do odczytu, tak jak
SpriteColor ma SpriteColorRed. To reguła tego dialektu, i te polecenia
się jej trzymają.
IF SpriteEffectsEnabled(hero) = 1
SpriteEffects(hero, 0)
ENDIF
alpha = SpriteShadowAlpha(hero) ' 0 to 255
size = SpriteGlowSize(hero) ' 0 means no glow
Pełny zestaw, dla każdej rodziny: SpriteShadowOffsetX, SpriteShadowOffsetY,
SpriteShadowBlur, SpriteShadowAlpha, SpriteShadowColorRed,
SpriteShadowColorGreen, SpriteShadowColorBlue, SpriteGlowRed,
SpriteGlowGreen, SpriteGlowBlue, SpriteGlowSize,
SpriteOutlineThickness, SpriteOutlineRed, SpriteOutlineGreen,
SpriteOutlineBlue, SpriteBlurRadius, SpriteEffectsEnabled, plus te same
siedemnaście z przedrostkiem Text.
Dwie reguły warte poznania:
- Sprite, któremu nigdy nie nadano efektu, zgłasza się jako ON. To stan domyślny; odpowiedź 0 mówiłaby programowi, że jego efekty są wyłączone, zanim jakikolwiek ustawił.
- Pytanie o sprite'a, który nie istnieje, odpowiada 0, nie zatrzymuje
programu. Tak dialekt traktuje pytania wszędzie:
SpriteHitodpowiada 0, gdy nic nie zostało dotknięte, a programy czytają to 0 bez sprawdzania go wcześniej.
Ile to kosztuje#
Cień, obrys i poświata są rysowane, więc kosztują draw calls: jeden na cień (trzy, gdy jest miękki), cztery na poświatę, osiem na obrys. Ekran z dwustoma sprite'ami z obrysem to ekran rysujący tysiąc osiemset quadów, i to będzie widać. Są pomyślane na garstkę rzeczy, na które gracz ma patrzeć: postać, przedmiot do zebrania, zaznaczoną jednostkę.
Rozmycie nic nie kosztuje na klatkę.
Praktyczna zasada: efekty na tym, co się rusza i ma znaczenie, żadnych na tle.
Co przechodzi do wyeksportowanej gry#
| Cel | Cień, obrys, poświata | Rozmycie | Efekty tekstu |
|---|---|---|---|
| Edytor | tak | tak | tak |
| Eksport HTML5 | tak | tak | tak |
| Natywny i wasm (Bevy) | tak | nie | nie |
Cień, obrys i poświata są zbudowane ze zwykłych sprite'ów, więc podróżują wszędzie tam, gdzie sprite'y. Rozmycie i efekty tekstu potrzebują mostka, którego cele Bevy jeszcze nie mają: raport konwersji eksportu je nazywa, zamiast po cichu je porzucać, więc build mówi ci, czego nie zdołał przenieść.
Gdzie tych efektów nie ma#
Nie ma SpriteEffect(id, "glow", ...) przyjmującego nazwę efektu jako
string. Każdy efekt to osobne polecenie, żeby jego parametry mogły być
nazwane, udokumentowane i podpowiadane w trakcie pisania. Dla wszystkiego
poza tymi czterema przejmują dwa inne polecenia: SpriteBlendModes(id, src, dst) dla mieszania addytywnego albo mnożonego (ogień, laser), oraz
SpriteShader(id, shaderID), żeby nałożyć na sprite'a shader skompilowany
przez LoadSpriteShader. Oba były zadeklarowane w katalogu od dawna, bez
implementacji, a ich wywołanie zgłaszało błąd; teraz działają.