Files
onboarding-project/docs/worklog/03-hud-counter-and-flight-retarget.md
T
2026-08-14 15:03:43 +03:00

9.1 KiB
Raw Blame History

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).