9.1 KiB
HUD-лічильник + перенаправлення анімацій (2026-08-12)
Користувач попросив: (1) справжню 2D UI-іконку ресурсу з лічильником у правому верхньому куті, "як на скріні" (темна округла табличка + іконка в круглій рамці зліва + число справа), (2) ресурс замість миттєвого зникнення має анімовано летіти ДО цього лічильника, (3) політ у Pay Zone має бути ВІД лічильника до зони (не від гравця).
Іконка
Витягнув сам PNG/WebP з тієї ж текстури Icon_Wood, яку вже юзає 3D-меш
UI_Wood (src/resources/meshes/ZombiePunk_Map.glb, bufferView з
images[6]) — маленьким одноразовим Node-скриптом (парсинг glb JSON-чанка,
пошук images[].name === "Icon_Wood", зріз байтів з BIN-чанка за
bufferViews[bufferView]). Зберіг як src/resources/images/icon_wood.webp
(webp з альфа-каналом, 512×512). Новий src/resources/images/images.ts
експортує iconWoodSrc = ConvertToBase64WhenRelease("./icon_wood.webp") —
той самий паттерн, що і meshes.ts (той же helper з @hitplay/ads_common,
викликаний з файлу в ТІЙ САМІЙ директорії, що й ассет — важливо: цей
хелпер в dev/build режимі просто рядково замінює провідний . на
resources (ConvertToBase64WhenRelease.js), а окремий AST-плагін
(convertToBase64InAST), підключений через defineConfigTemplate в
vite.config.js, на build-time переписує сам виклик у справжній inline
data:...;base64,... — це працює для ВСІХ режимів (dev/build/export), не
тільки для "export").
HudC.ts — новий контролер
DOM-паттерн підглянутий у InstallBanner з SDK (немає спільного
DOM-builder helper-а в пакетах, усе руками через document.createElement
- власний
<style>, що вставляється в<head>): статичний клас, монтує DOM-елемент у#ui(SDK-контейнер,position:absolute, зафіксований 9:16 blocks —calc(100vh*9/16)×100vh, центрований — це і є "екран гри", не весь браузер, тожtop/rightу % рахуються відносно НЬОГО)..resource-hud { pointer-events:none }— щоб не перехоплював тач/клік від джойстика під ним. Стиль — власний CSS, вигаданий (в проєкті НЕ було жодного готового "плата/банер" стилю для запозичення — перевірено, вui.css/main.cssнуль хітів наborder-radius).
Другий обов'язок HudC — світова точка (worldAnchor, порожній
Object3D, дитина камери через CameraC_internal.getCamera().add(...),
локальний офсет — спочатку (1.0, 0.8, -2.2), потім підкручено до
(1.0, 1.2, -2.0) (див. нижче, чому)), яка приблизно проєктується туди,
де візуально сидить DOM-іконка. Це не точний screen-to-world розрахунок
(FOV/aspect не враховані математично) — просто підібраний на око офсет;
якщо HUD переїде/зміниться розмір екрану, можливо треба підкрутити
координати ще раз. getWorldAnchorPosition() — це і є точка, куди тепер
летять ресурси (ResourceC.setFlyTarget) і звідки стартує депозит-політ
(PayZoneC.playDepositFlight).
Що змінилось у ResourceC/PayZoneC
ResourceC:scatter-> settle -> (якщоflyTargetзаданий, а тепер він завжди заданий —HudC.getWorldAnchorPosition) новий стейтtoTarget(0.5с, smoothstep + згасаюча дуга вгору, прямо до лічильника) ->collect(). БезflyTarget— стара миттєва поведінка (fallback). На відміну від попередньої версії — НЕ телепортується на позицію гравця перед польотом (той крок був заточений під "летить З гравця", зараз відповідь користувача про сам HUD не згадувала цей крок, тож прибрав його — політ іде прямо з місця, де ресурс осів, до лічильника).PayZoneC.playDepositFlight:fromтеперHudC.getWorldAnchorPosition()замість позиції гравця — "від лічильника до пей зони", як попросив користувач. Тригер (proximity до зони) не змінився — усе ще потребує стояння гравця в зоні, змінилась лише точка ВИЛЬОТУ візуалу.
Перевірено в браузері (Playwright, scratchpad): іконка рендериться коректно
(512×512 webp, валідний data: URI, naturalWidth/naturalHeight не 0),
лічильник в DOM оновлюється синхронно з console.log-ами збору (звірено
скріншотом — "8" на екрані == 8-й [ResourceC] gathered wood в консолі).
Доопрацювання: лічильник має бути "живим балансом", не lifetime-total (2026-08-12)
Спочатку HudC показував ResourceC.getCollectedCount() напряму — тобто
lifetime-суму, яка ТІЛЬКИ росте (депозит в Pay Zone на неї не впливав).
Користувач попросив: число має рости при зборі І зменшуватись при
депозиті — тобто показувати поточний "баланс", а не історичний тотал.
Розв'язання без нової мутабельної змінної (і без циклічного імпорту —
PayZoneC вже імпортує HudC для getWorldAnchorPosition(), тож
HudC імпортувати PayZoneC напряму означало б цикл):
PayZoneC.getDepositedCount()— новий публічний гетер, повертає внутрішнійdeposited(лічильник, який і раніше рахував "скільки вже влетіло в зону", просто не був назовні доступний).HudC.setBalanceGetter(fn)— інжектований гетер (той самий паттерн, що йResourceC.setFlyTarget/onDestroyedвBreakablePropC), замінив пряму залежністьHudC -> ResourceC.HudCбільше НЕ імпортуєResourceCвзагалі.TestSceneCзв'язує:HudC.setBalanceGetter(() => ResourceC.getCollectedCount() - PayZoneC.getDepositedCount())— чиста похідна величина, рахується на льоту щокадру вHudC.update(), без жодного окремого "decrement"-виклику. Росте колиcollectedросте (щось зібрали), падає колиdepositedросте (щось долетіло в зону) — саме по собі, без спеціальної синхронізації.
Перевірено: зібрав 7 ресурсів (консоль: gathered wood (7 total)), весь
цей час стояв в Pay Zone -> усі 7 злились в зону протягом ~2.1с
(DEPOSIT_INTERVAL=0.3 × 7) -> лічильник на екрані повернувся до 0,
хоча collected лишився 7 — підтверджує, що баланс дійсно "живий", а не
lifetime-сума.
Доопрацювання: точка вильоту ресурсів була занизько (2026-08-12)
WORLD_ANCHOR_LOCAL_OFFSET підняли з (1.0, 0.8, -2.2) до
(1.0, 1.2, -2.0) — більший Y (вище в camera-space) і трохи менший |Z|
(ближче до камери, тому той самий Y дає більше вертикальне зміщення на
екрані через перспективу) — все ще підібрано на око, не розраховано
математично з FOV/aspect. Це найбільш "на око" підібрана частина
роботи — якщо після цього фіксу політ все ще не влучає точно в іконку,
підкрутити ці три числа ще раз (в HudC.ts).