Utility and polishing + bag fixes
This commit is contained in:
@@ -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`).
|
||||
Reference in New Issue
Block a user