16 KiB
Стек і структура
- Не 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).