diff --git a/.gitignore b/.gitignore index 57f1443..66ee4cf 100644 --- a/.gitignore +++ b/.gitignore @@ -151,3 +151,4 @@ export_test/ extra_compress/ preview_dist/ build/ +.studio/ diff --git a/CLAUDE.md b/CLAUDE.md index 9c37ae7..cc0350f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,1508 +1,243 @@ # MyGamePlayable — контекст проєкту -> Цей файл автоматично підвантажується в кожній сесії Claude Code. Тримай його -> в актуальному стані: онови статуси, коли щось зміниться, видали "Day 5" секцію -> коли вона остаточно перевірена і стабільна, додай нову секцію під наступний день. +Playable ad на three.js + пропрієтарний SDK HitPlay. Цей файл вантажиться в +кожну сесію — тримай його **інструкцією, а не щоденником**. -## Стек і структура +--- -- **Не 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()`) — - використовувати для видалення спавнених/знищених мешів. +## ПРАВИЛА ВЕДЕННЯ ЦЬОГО ФАЙЛУ -### Файли +**Стеля — 300 рядків.** Перевищив — перенеси найстаріше й уже усталене в +`docs/worklog/`. Історія розробки живе там, не тут. + +### Що ДОДАВАТИ сюди + +- правило або конвенцію, що змінює майбутні рішення («використовуй утиліту X, + не пиши формулу заново»); +- граблі, які **вкусять ще раз** (поведінка SDK/бібліотеки/тулчейна, а не + разовий баг у власному коді); +- зміну структури: новий контролер, нова утиліта, новий конфіг-параметр; +- нову команду або публічний API SDK, який не очевидний з коду. + +### Що НЕ додавати + +- «сьогодні зробив…», «Day N», хронологію, цитати з чату; +- «перевірено в браузері / білд пройшов» — це не знання, це подія; +- підібрані числа (`CAMERA_OFFSET`, тривалості, відсотки) — вони живуть у коді + й там же змінюються; сюди — тільки *де* важіль, не його поточне значення; +- переказ того, що й так видно з коду за 10 секунд. + +### Куди що писати + +| Знання | Місце | +|---|---| +| Пояснює один файл/рядок | коментар у коді | +| Перетинає кілька файлів або є політикою проєкту | `CLAUDE.md` | +| «Чому ми дійшли саме до цього» | `docs/worklog/` | + +### Коли граблі усунені в корені + +Якщо гочтя закрита утилітою — **заміни розповідь одним рядком** з посиланням на +утиліту. Приклад: `.chain()`-баг → `src/utils/tweenChain.ts`. Довга версія +лишається в `docs/worklog/02-day6-tween-animations.md`. + +### Новий запис у worklog + +`docs/worklog/NN-коротка-англійська-назва.md`, `NN` — наступний номер. +Додай рядок у `docs/worklog/README.md`. Один H1 на файл. + +--- + +## Стек + +- **three.js `0.185`** (НЕ Babylon), обгорнутий у SDK `@hitplay/playable_template` + + `@hitplay/ads_common` (приватні пакети HitPlay). +- Фізика: `cannon-es`. **SDK сам кличе `world.fixedStep()` щокадру** + (`Physics_internal.update`) — свій `step()`/`fixedStep()` НЕ викликати. + Ініт — `Physics_internal.init(...)` у `beforeResourcesLoadedCb.ts`. +- Твіни: `@tweenjs/tween.js` через SDK-обгортку `TweenC`. **`TweenC.init()` + треба покликати один раз** (є в `beforeResourcesLoadedCb.ts`), інакше + `TweenC.add()` тихо нічого не анімує. +- VFX: `three.quarks` — дизайнерські префаби з quarks.art (`.json`). +- Звук `howler`, ввід `nipplejs`, дебаг-панель `lil-gui`, аналітика `bytebrew-web-sdk`. +- Тулчейн: Vite 6 + TypeScript 7. + +`UpdateController.Instance.onUpdate` — **єдиний** update-івент, `delta` в секундах. +`ThreeC.removeFromScene(obj)` — видалення спавнених мешів. + +## Команди + +| Команда | Що робить | +|---|---| +| `npm start` | vite dev-сервер | +| `npm run export` | single-file HTML → `export/output_export_browser_en.html` | +| `npm run build` | zip для платформи 24ADS → `zip/` | +| `npm run update` | оновити `@hitplay/ads_common` + `playable_template` | + +Перевірка змін — `npx vite build` (у проєкті прийнято верифікувати саме білдом). +`stats.html` — rollup-visualizer, дивитись коли розпухає бандл. + +## Структура ``` -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 -