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

105 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`).