Utility and polishing + bag fixes

This commit is contained in:
Oleksandr Vlasiuk
2026-08-14 15:03:43 +03:00
parent 250f4a0d2c
commit 1f72420da0
45 changed files with 2542 additions and 1618 deletions
+182
View File
@@ -0,0 +1,182 @@
# Стек і структура
- **Не Babylon.js** — це **three.js** (`three@0.185`) обгорнутий у пропрієтарний
SDK `@hitplay/playable_template` (плейбл-реклама, HitPlay).
- Фізика: `cannon-es@0.20` + `cannon-es-debugger`. World піднятий через SDK
(`Physics_internal.init(...)` в `src/templateConfig/beforeResourcesLoadedCb.ts:27`),
дебаг-рендер тригериться прапорцем `Template.initConfig({ debug: { physics: false } })`
в `src/index.ts` (поки `false` — увімкнути під час дебагу фізики).
**SDK сам кличе `world.fixedStep()` кожен кадр** (`Physics_internal.update`,
всередині пакету) — свій `world.step()`/`fixedStep()` НЕ викликати, це
вже фіксований таймстеп з коробки.
- `UpdateController.Instance.onUpdate` — єдиний update-event, `delta` в
секундах (з `THREE.Clock.getDelta()`), без фіксованого таймстепу на цьому
рівні (фіксований степ — тільки всередині cannon, через `fixedStep()`).
- `ThreeC.removeFromScene(obj)` існує (просто `obj.removeFromParent()`) —
використовувати для видалення спавнених/знищених мешів.
## Файли
```
src/controllers/
PlayerC.ts - джойстик-рух (nipplejs), AABB-колізії з "colliders",
бій: неперервний AoE-swing по всіх breakable-цілях в
зоні ураження (240°-конус, FACING_DOT_THRESHOLD=-0.5,
Day 7 — див. "Кут ураження"), перебивається рухом
TestSceneC.ts - завантажує ZombiePunk_Map.glb, /collider/i -> hidden+colliders,
ставить cannon Wall-тіла на все, крім Lootable-крейтів;
ініціалізує всі VFX/HUD-контролери (PropVfxC,
SparkleFxC, WeaponTrailC, HudC) в правильному порядку
CameraFollowC.ts - камера-слідкувач, obstacle-avoidance через Raycaster
по тому ж масиву colliders (НЕ торкались)
PhysicsC.ts - PhysicsLayer enum, PhysicsBody (Box/Sphere+cannon Body
з коректним world-space size/rotation), PhysicsObjPair
BreakablePropC.ts - парсить групу "Lootable" в мапі, S1/S2/S3 damage-stages,
2 хіти на стейдж, hit() advance stage / повне
знищення (tween-джус, Day 6), дропає ресурси
WeaponTrailC.ts - Day 7: hand-rolled screen-facing ribbon-шлейф за
битою під час замаху (samples PlayerC.getWeaponAnchor(),
обчислює "кінець бити" з реальної local bbox, не
вгадує offset), тає/звужується за час, не тон-мап,
toggled через PlayerC.updateCombat (isAttacking)
ResourceC.ts - Day 7: спавн ресурсу з крейта -> scatter -> 2 баунси
(відчутні, БЕЗ ротейту) -> на 2-му баунсі: білий
флеш (окремий additive-оверлей-меш, БЕЗ
squash-and-widen — чистий uniform shrink, тримається,
не пружинить назад) + SparkleFxC-вибух зірочок ->
staggered (по черзі, не купою) політ до
HudC.getWorldAnchorPosition() (ціль пере-семплюється
ЩОКАДРУ, не застигає, якщо камера рухається) з
компенсацією перспективи (див. RESOURCE_ICON_BASE_SCALE,
нижче) -> яскравіє й зникає рівно на іконці. НЕ знає
про Pay Zone/гравця; візуал — клон реального
"UI_Wood" з мапи; createVisual() — публічний доступ
до цього візуалу (без баунс/флеш начинки) для інших
контролерів (PayZoneC)
PayZoneC.ts - Day 6/8: окремо зливає ResourceC.getCollectedCount() в
Pay Zone (вузол мапи "UI_Interactive_Zone_02"), по
одному ресурсу, тільки поки гравець стоїть в зоні;
зникає після RESOURCES_TO_CLOSE_ZONE доставлених
(зараз 20); росте плаский fill-overlay квад по мірі
заповнення; політ депозиту (HudC.getWorldAnchorPosition()
-> Pay Zone) БЕЗ ротейту, з тією ж компенсацією
перспективи й базовим розміром, що й ResourceC
(RESOURCE_ICON_BASE_SCALE, спільна константа —
інакше токен виглядає більшим за ресурс, що щойно
був на тому ж місці), плюс окремий
DEPOSIT_FLIGHT_SCALE_MULTIPLIER для локального тюнінгу
HudC.ts - Day 8: лічильник ресурсу — реальний дизайнерський
ассет (resourceCounterBgSrc, Tool_15.webp з temp/,
планка вже вбудована в картинку, не окрема іконка),
DOM-оверлей в #ui, стилі в src/css/ui.css (не inline
<style>); getWorldAnchorPosition() — СПРАВЖНЯ
screen-to-world проєкція (getBoundingClientRect()
правого краю плейта + unproject через камеру), НЕ
вгаданий локальний офсет від камери (той підхід
видалений повністю, разом з WORLD_ANCHOR_LOCAL_OFFSET)
SparkleFxC.ts - Day 7: пул із 30 зіркових мешів/матеріалів (pop-in
Back.Out -> hold -> fade-out через TweenC), жодних
нових Mesh/Material під час гри — spawnBurst() бере
вільний слот з пулу; використовується ResourceC на
2-му баунсі
PropVfxC.ts - Day 6: завантажує дизайнерські quarks.art VFX-префаби
(JSON з temp/, sparks/dust/debris) для хіту/знищення
крейтів
WeaponHudC.ts - Day 8: статична юайка внизу-зліва — восьмикутна
плита (weaponHudBgSrc, ZombiePunk_Icon-Bottom.webp з
temp/) + іконка бити (weaponIconBatSrc, Tool_2.webp)
+ хардкоджений текст "LVL 0" (нема системи рівнів
зброї в проєкті — суто візуал під референс). Іконка
завжди бита, не стежить за поточною екіпірованою
зброєю (pistol/bat) — окреме, свідомо не зроблене
рішення
ZombieInvasionHudC.ts - Day 8: топ-центр "ZOMBIE INVASION" — заголовок +
таймер-бар. Голова зомбі (zombieHeadIconSrc,
REF_1.png з temp/ — файл НАСПРАВДІ webp, не png,
розширення в джерелі помилкове) їде з
right:100% -> left:0% за TIMER_DURATION_S (120с)
через CSS transition (JS лише раз перемикає
значення); окремий .zombie-invasion__fill росте
0%->100% width з right:0 в ТОЙ САМИЙ момент/тривалість
— їх ліва межа завжди математично збігається з
позицією голови, без пофреймової синхронізації.
Останні WARNING_FRACTION=0.05 (6с) — pulse+червоний
флеш через setTimeout+клас (CSS keyframes самі
крутяться в infinite, ніхто їх не зупиняє після
заповнення — свідомо, за проханням користувача).
Немає дизайнерського ассету під сам бар/трек —
чистий CSS pill, лише голова — реальна картинка
ThreeC.ts/CameraC.ts - базовий сетап three.js/камери від SDK
resources/meshes/ - ZombiePunk_Map.glb, ZombiePunk_Character.glb
resources/images/ - resource_counter_bg.webp (Tool_15.webp з temp/,
дизайнерський плейт лічильника), weapon_hud_bg.webp,
weapon_icon_bat.webp, zombie_head_icon.webp,
images.ts (icon_wood.webp/iconWoodSrc видалені —
стали осиротілими після заміни на resourceCounterBgSrc)
resources/fonts/ - PassionOne-Black.otf (з temp/) — див. "gameFont"
нижче
resources/vfx/ - VFX_Lootable_Hit.json, VFX_Lootable_Destroy.json
(quarks.art префаби, копії з temp/, реально
використовуються з коду — див. PropVfxC)
```
`TriggerC.ts` (beginContact/endContact -> onEnter/onExit) і кінематичне
cannon-тіло гравця для тригерів — **були написані й видалені в межах цієї ж
сесії, ще до коміту**: перший дизайн ресурсів вимагав підходити до пікапа й
стояти в тригер-зоні; користувач уточнив, що гравцю підходити не треба
(ресурси самі розлітаються і зараховуються по таймеру), тож фізичні тригери
виявились непотрібні для Day 5. Якщо колись знадобиться proximity-based
тригер для чогось іншого (вода, зона урону, детект ворога) — писати заново,
але сам підхід (`world.addEventListener('beginContact'/'endContact')` +
мапа `body.id -> handlers`) вже перевірений робочим, просто не лишений в коді.
## Ключова знахідка: структура glb вже готова під breaking/loot
Мапа (`ZombiePunk_Map.glb`) містить групу **`Lootable`** (пряма дитина кореня
`Map`) з ~18 дітьми `Wooden_Box_XXX` (XXX = `000`..`017`). Кожен має:
```
Wooden_Box_XXX
BoxCollider.NNN <- obstacle-бокс, він же в загальному /collider/i списку
Wooden_Box_States_XXX
Wooden_Box_XXX_S1 <- найменше пошкоджений (не завжди є)
Wooden_Box_XXX_S2
Wooden_Box_XXX_S3 <- найбільш пошкоджений/уламки (завжди є хоча б цей)
```
Не всі крейти мають усі 3 стейджі — деякі стартують вже пошкодженими
(тільки S2/S3) або взагалі як декоративні уламки (тільки S3). `BreakablePropC`
сортує дітей `..._States_XXX` за номером у назві й показує лише перший —
кожен хіт просуває на наступний, а хіт по останньому стейджу знищує крейт.
Root-рівень gltf-сцени (`getObject("map")` = `gltf.scene`, НЕ сам "Map"-нод)
містить ще 3 сиблінги "Map": `UI_Tool_Zone`, `UI_Interactive_Zone_02`,
`UI_Wood`/`UI_Wood.001` — плоскі quad-меші на позиції origin. В грі це
видно як пунктирний білий квадрат, що лежить на дорозі біля крейтів
(скріншот при першому запуску) плюс текстуру дерева поверх нього.
**`UI_Wood` (mesh `Plane.002`) — це реальний іконка-ресурсу, не сміття.**
Матеріал `Material` (index 6) використовує текстуру з назвою **`Icon_Wood`**
(unlit, alphaMode BLEND, doubleSided) — художник підготував конкретно "іконку
дерева" саме для цього. Тепер підключено: `TestSceneC` бере цей нод,
віддає його в `ResourceC.setPickupTemplate()` (клонується на кожен спавн
ресурсу — `.clone()`) і ховає оригінал (`visible=false`, він більше не
статична декорація, а шаблон для клонування). `ResourceC.createPickupMesh()`
примусово виставляє `clone.visible = true`, бо `.clone()` копіює і
`visible:false` з прихованого оригіналу.
`UI_Wood.001` (mesh `Plane.008`, матеріал `M_Items`/текстура `T_items_icons`
— схоже на спрайт-атлас з кількома іконками) і `UI_Tool_Zone` — призначення
досі не зрозуміле, але користувач попросив прибрати їх як "зайві текстури"
навколо Pay Zone — тепер ховаються в `TestSceneC` (`visible=false`) поруч з
`UI_Wood`. `UI_Interactive_Zone_02` (пунктирний квадрат) — **тепер
підключений** в Day 6 як Pay Zone (див. нижче).
Ще одна знахідка (не сиблінг, а дитина `Map` -> `"UI"`): группа `UI`
(`UI_Background`/`UI_Foreground`/`UI_Middleground`, всі матеріал `_Part2`
— той самий атлас, що й крейти) — окремо повернута група (власний
quaternion), локально приблизно (0.31, 1.21, -1.9), тобто підвішена в
повітрі на висоті голови персонажа. Схоже на фейковий install-button
мокап (типовий playable-ad прийом), рендерився завжди, візуально
"прямокутна синя пластина над плей зоною" з якою скаржився користувач.
Теж ховається тепер (`map.getObjectByName("UI")`, `visible=false`).