Ефекти для спрайтів і тексту
Тінь, контур, світіння й розмиття на 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)
offsetX і offsetY у ваших власних одиницях екрана, полічених так само,
як у SpritePosition: позитивний Y це вниз. softness розтягує тінь;
opacity іде від 0 до 255. Колір чорний, якщо не сказано інше.
SpriteShadow(hero, 3, 5, 4, 110)
SpriteShadowColor(hero, 20, 0, 40) ' холодна тінь, а не сіра
Тінь з непрозорістю 0 це відсутність тіні. Ось так її вимикають.
Outline#
SpriteOutline(spriteID, thickness, red, green, blue)
Контур слідує за силуетом спрайта, а не за прямокутником, у якому він живе: прозорий PNG отримує контур навколо малюнка, а не навколо файлу. Товщина в одиницях екрана, і корисний діапазон: від 1 до 3. Вище цього вісім напрямків, з яких будується контур, починають проявлятися як вісім пелюсток.
SpriteOutline(hero, 2, 255, 255, 255) ' контур, читабельний на будь-чому
SpriteOutline(hero, 0, 0, 0, 0) ' вимкнено
Glow#
SpriteGlow(spriteID, red, green, blue, size)
Кольоровий ореол, що розповсюджується на size одиниць за межі силуету. Він
розташований позаду спрайта і попереду тіні, саме це дозволяє
використовувати обидва одночасно.
SpriteGlow(pickup, 255, 210, 0, 10) ' до цього варто дійти пішки
Blur#
SpriteBlur(spriteID, radius)
Blur це той, що не такий, як решта. Інші три малюються навколо спрайта; blur замінює власне зображення спрайта на розмиту копію. Це означає дві речі, про які варто знати:
- це нічого не коштує на кадр (розмите зображення обчислюється один раз), і
- розмиття обрізається на краю зображення. Малюнок, що торкається краю свого PNG, втрачає ту частину свого ореолу, яка мала б вийти за межі. Залиште кілька прозорих пікселів запасу у файлі, якщо плануєте його розмивати.
SpriteColor продовжує працювати на розмитому спрайті.
Вимкнути все, і повернути назад#
SpriteEffects(spriteID, 0) ' усі чотири вимкнено
SpriteEffects(spriteID, 1) ' усі чотири повернуто, рівно як були
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)
Сигнатури точно збігаються зі спрайтовими, тож учити нічого нового не потрібно. Дві відмінності в поведінці:
- Glow перемагає м'якість тіні. Обидва малюються тим самим механізмом, і glow без м'якості невидимий, тоді як тінь без м'якості все ще лишається тінню. Коли ви просите обидва, м'якість дістається glow.
- Вони застосовуються до векторного тексту, а не до тексту з бітмап-
шрифту. Текст, намальований через
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
Повний набір, за родиною: SpriteShadowOffsetX, SpriteShadowOffsetY,
SpriteShadowBlur, SpriteShadowAlpha, SpriteShadowColorRed,
SpriteShadowColorGreen, SpriteShadowColorBlue, SpriteGlowRed,
SpriteGlowGreen, SpriteGlowBlue, SpriteGlowSize,
SpriteOutlineThickness, SpriteOutlineRed, SpriteOutlineGreen,
SpriteOutlineBlue, SpriteBlurRadius, SpriteEffectsEnabled, плюс ті самі
сімнадцять із префіксом Text.
Два правила, про які варто знати:
- Спрайт, якому ніколи не задавали ефект, повідомляє, що він УВІМКНЕНИЙ. Це стан за замовчуванням; відповідь 0 сказала б програмі, що її ефекти вимкнені ще до того, як їх узагалі задали.
- Запит про спрайт, якого не існує, відповідає 0, а не зупиняє
програму. Так діалект поводиться з питаннями всюди:
SpriteHitвідповідає 0, коли нічого не торкнулося, і програми читають цей 0, не перевіряючи його.
Скільки це коштує#
Shadow, outline і glow малюються, тож вони коштують викликів малювання: один для тіні (три, коли вона м'яка), чотири для glow, вісім для outline. Екран із двомастами спрайтів із контуром це екран, що малює тисячу вісімсот квадів, і це буде помітно. Вони призначені для жменьки речей, на які гравець має дивитися: персонаж, підбирайка, вибраний юніт.
Blur не коштує нічого на кадр.
Практичне правило: ефекти на тому, що рухається й важить, і ніяких на фоні.
Що переживає експорт гри#
| Ціль | Shadow, outline, glow | Blur | Text effects |
|---|---|---|---|
| Редактор | так | так | так |
| Експорт HTML5 | так | так | так |
| Native та wasm (Bevy) | так | ні | ні |
Shadow, outline і glow побудовані зі звичайних спрайтів, тож вони подорожують скрізь, куди подорожують спрайти. Blur і текстові ефекти потребують мосту, якого в цілей Bevy ще немає: звіт конверсії експорту називає їх, а не мовчки відкидає, тож збірка повідомляє, чого вона не змогла перенести.
Де ефектів немає#
Немає SpriteEffect(id, "glow", ...), що приймає назву ефекту як рядок.
Кожен ефект це окрема команда, щоб його параметри можна було назвати,
задокументувати й автодоповнити під час набору. Для всього, що виходить за
межі цих чотирьох, беруть на себе дві інші команди: SpriteBlendModes(id, src, dst) для адитивного чи множинного змішування (вогонь, лазер), і
SpriteShader(id, shaderID), щоб поставити на спрайт шейдер, скомпільований
LoadSpriteShader. Обидві довго значилися в каталозі без реалізації, і їх
виклик видавав помилку; тепер вони працюють.