Utility and polishing + bag fixes
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
# Розширений hit-feedback на крейтах: спарки, HP-бар, числа урону (2026-08-12)
|
||||
|
||||
Користувач показав референс-скріншоти з іншої гри (damage-numbers, HP-бар,
|
||||
іскри в точці контакту) і сказав, що зараз у нас із фідбеку на хіт **лише
|
||||
невеликий нахил** — флеш кольору, який мав бути з Day 6, візуально не
|
||||
працює. Плейл поступово (по одному ефекту, з перевіркою між кроками).
|
||||
|
||||
## ⚠️ Знайдений і виправлений баг: флеш кольору був тихим no-op з самого Day 6
|
||||
`playHitFeedback` лерпив `material.color` від `baseColor` до `FLASH_COLOR`
|
||||
(білий) — але залогувавши реальний матеріал crate-мешів (`MeshBasicMaterial`,
|
||||
`color: #ffffff`, без `emissive` — це не Standard/Phong), виявилось, що
|
||||
**`baseColor` вже й був чистим білим**. Текстура дає весь реальний колір,
|
||||
тінт-колір лишався на дефолтному білому — лерп "білий -> білий" не змінює
|
||||
візуально НІЧОГО, tween виконувався справно (перевірено таймингом), просто
|
||||
результат був непомітний. `MeshBasicMaterial` — unlit, `color` множиться на
|
||||
текстуру, тож "яскравіше за білий" через цей канал взагалі неможливо.
|
||||
|
||||
**Виправлення**: замість лерпу `.color` — `StageVisual.flashMesh`, клон тієї
|
||||
самої mesh-геометрії (`mesh.clone()`), покладений зверху з власним
|
||||
`MeshBasicMaterial({ color: FLASH_COLOR, transparent: true, opacity: 0,
|
||||
blending: AdditiveBlending, depthWrite: false })`. Флеш тепер анімує
|
||||
`flashMesh.material.opacity` (0→1→0) замість кольору базового меша —
|
||||
працює незалежно від того, який в оригінала колір/текстура, бо це
|
||||
адитивний шар зверху, а не множник. **Побічний ефект**: `flashMesh` — це
|
||||
ще один mesh під `map`, тож `TestSceneC`'s `ThreeC.setShadowsStateForChildren
|
||||
(map, true, true)` (виконується ПІСЛЯ `BreakablePropC.init()`) увімкнув би
|
||||
йому shadows теж — додано `BreakablePropC.disableFlashShadows()`, викликаний
|
||||
з `TestSceneC` одразу після цього рядка, щоб вимкнути назад (даремний
|
||||
shadow-caster на майже завжди прозорому оверлеї).
|
||||
|
||||
Перевірено (Playwright, серія скріншотів кожні 80мс під час бою): крейт у
|
||||
кадрі точно на позначці очікуваного хіта помітно біліший/яскравіший, ніж у
|
||||
кадрах до/після — флеш реально видно, на відміну від попередньої версії.
|
||||
|
||||
**Урок**: тінт-колір (`material.color`) на unlit-текстурованому меші не дає
|
||||
"флешу", якщо колір вже білий (звичайний дефолт) — для видимого
|
||||
hit-flash-у на такому матеріалі потрібен окремий адитивний оверлей-шар, а
|
||||
не лерп самого кольору. Якщо десь ще знадобиться подібний ефект на
|
||||
текстурованому unlit-меші — той самий підхід (clone + additive overlay).
|
||||
|
||||
## Іскри в точці контакту — v1 hand-rolled, замінено (2026-08-12)
|
||||
Перша версія — новий `SparkFxC.ts`, ручно зібраний `ParticleSystem` (білі
|
||||
квадратні частинки, `SphereEmitter(radius:0.05)`, звичайний `BillBoard`).
|
||||
Технічно працював (Playwright підтвердив спалах у момент хіта), але
|
||||
користувач подивився і сказав: **"Іскри не підходять, це мають бути як
|
||||
промені. Саме такі як на скріні"** — потрібні тонкі промені-стріки, не
|
||||
блоб з крапок. Перебудував рендер на `RenderMode.StretchedBillBoard`
|
||||
(розтягує квад уздовж власної швидкості частинки — це і дає "промінь", а
|
||||
не крапку) + `speedFactor`, `ColorOverLife`+`Gradient` для згасання альфи.
|
||||
Це вже виглядало краще, але тут користувач відкрив `temp/VFX_Lootable_Destroy.json`
|
||||
у редакторі й запитав, що в цих файлах — і виявилось, що це змінює все.
|
||||
|
||||
## ⚠️ Знахідка: в `temp/` лежали готові дизайнерські VFX-префаби, повністю невикористані
|
||||
`temp/VFX_Lootable_Hit.json`, `temp/VFX_Lootable_Destroy.json` (+
|
||||
`PassionOne-Black.otf` — шрифт, ще не використаний, ймовірно для майбутніх
|
||||
чисел урону/HUD) — це **справжні експорти з редактора quarks.art**
|
||||
(`Object3D.toJSON()` на `Group` з `ParticleEmitter`-дітьми, кожен зі своїм
|
||||
готовим, підібраним дизайнером `ps` — shape/speed/color/behaviors, і
|
||||
реальними текстурами, вбудованими як base64: `cfxr stretch smoke arc
|
||||
dissolve.webp`, `novalines.webp` — назва "novalines" **буквально "лінії
|
||||
нової" = промені-риски**, це саме те, що користувач мав на увазі скріном).
|
||||
`VFX_Lootable_Hit` = 2 емітери ("StretchSmoke" + "SharpImpact" — це і є
|
||||
ray-burst). `VFX_Lootable_Destroy` = 4 емітери (дим, уламки-Pieces,
|
||||
Ground_Dirt як реальна 3D-mesh геометрія (`renderMode: Mesh`), пилова хмара).
|
||||
**Жодного SDK/іншого коду проєкту ці файли не використовували взагалі** —
|
||||
чисто сирі ассети, що чекали на підключення. Замінив увесь ручний
|
||||
`SparkFxC` на завантаження цих реальних префабів — жодна власноруч
|
||||
підібрана крива/швидкість не заміняє справжній дизайнерський ассет.
|
||||
|
||||
**Що таке VFX**: "visual effects" — тут конкретно означає one-shot
|
||||
частинкові ефекти (спалахи/дим/уламки), авторовані окремо в
|
||||
редакторі quarks.art і експортовані як самодостатній JSON (геометрія +
|
||||
матеріали + текстури-base64 + вже налаштовані `ParticleSystem` параметри
|
||||
для кожного емітера) — не код, чистий даних-ассет, який рушій (`three.quarks`)
|
||||
вміє розпарсити назад у робочі `Object3D`.
|
||||
|
||||
## `PropVfxC.ts` — новий контролер, замінив `SparkFxC.ts`
|
||||
Завантажує обидва префаби ОДИН РАЗ при `init()` через сирий
|
||||
`QuarksLoader` (`three.quarks`) + `Template3d.manager` (`@hitplay/playable_template`),
|
||||
**НЕ** через SDK-хелпер `quarksLoader()`/`ConvertToBase64WhenRelease`
|
||||
(паттерн `meshes.ts`/`images.ts`) — свідомо, з причини:
|
||||
|
||||
**⚠️ Чому не той самий паттерн, що і images/meshes**: `quarksLoader(base64String)`
|
||||
жорстко очікує рядок формату `data:...;base64,XXXX` (робить
|
||||
`base64String.split(",")[1]` потім `atob(...)`). Це працює лише ПІСЛЯ
|
||||
build-time AST-трансформації (`convertToBase64InAST`, підключена в
|
||||
`vite.config.js`), яка й замінює виклик `ConvertToBase64WhenRelease()` на
|
||||
реальний base64 рядок. У **звичайному `vite dev`** (як я весь час тестую
|
||||
через Playwright!) сам хелпер — лише прохідний passthrough, що повертає
|
||||
ГОЛИЙ ШЛЯХ (`"resources/vfx/....json"`) без коми — `.split(",")[1]` дав би
|
||||
`undefined`, і `atob(undefined)` зламав би завантаження саме в dev-режимі,
|
||||
хоча в build/export він працював би. Замість цього ризику — звичайний
|
||||
Vite JSON-імпорт (`import json from "../resources/vfx/VFX_Lootable_Hit.json"`),
|
||||
який Vite вміє інлайнити нативно в БУДЬ-ЯКОМУ режимі (dev/build/export)
|
||||
без жодного fetch — і сирий `new QuarksLoader(Template3d.manager).parse(json)`
|
||||
(синхронний, повертає `Object3D` напряму, `onLoad` callback не потрібен,
|
||||
бо всі текстури вже base64 в самому JSON, немає мережевого чекання).
|
||||
Додав `"resolveJsonModule": true` в `tsconfig.json` (був відсутній) — без
|
||||
цього `tsc` (не сам vite/esbuild — ті й так все розуміють) скаржився б на
|
||||
JSON-імпорт типами.
|
||||
|
||||
`PropVfxC.spawnHit(point)`/`spawnDestroy(point)` — клонують відповідний
|
||||
завантажений шаблон (`.clone()` на `Group`), виставляють `position`,
|
||||
форсують `QuarksUtil.setAutoDestroy(instance, true)` (в оригінальних
|
||||
префабах `autoDestroy: false` — дизайнер, ймовірно, керував завершенням
|
||||
вручну в своєму інструменті; нам потрібен чистий "one-shot і забути"),
|
||||
`QuarksUtil.addToBatchRenderer(instance, renderer)` (реєструє КОЖЕН
|
||||
`ParticleEmitter` в дереві з рендерером — без цього нічого не малюється),
|
||||
`QuarksUtil.play(instance)`. Кожен `ParticleSystem` з `autoDestroy`
|
||||
прибирає СЕБЕ (свій `ParticleEmitter`-нод) з дерева, коли його частинки
|
||||
померли — але порожній корінь-`Group`, який я додав в сцену, сам не
|
||||
видаляється (нема кому), тож прибираю його окремим `setTimeout`
|
||||
(`CLEANUP_DELAY_MS=1500`, з запасом під найдовший `duration+life` в обох
|
||||
префабах).
|
||||
|
||||
`BreakablePropC.playHitFeedback` кличе `PropVfxC.spawnHit(contactPoint)` на
|
||||
кожному хіті (як і раніше з `SparkFxC`); `BreakablePropC.destroy()`
|
||||
додатково кличе `PropVfxC.spawnDestroy(...)` в позиції крейта — цього не
|
||||
було в hand-rolled версії взагалі (не було Destroy-ефекту), додав одразу,
|
||||
бо це буквально друга половина того самого знайденого ассета.
|
||||
|
||||
## ⚠️ Знахідка (потребує рішення користувача, ще не виправлено): "StretchSmoke"-шар вилітає далеко від крейта і виглядає як артефакт
|
||||
Перевірено в браузері (Playwright): "SharpImpact" (промені/nova-спалах,
|
||||
`novalines.webp`) рендериться коректно ПРЯМО на крейті в момент хіта —
|
||||
саме той ефект, що відповідає скріну користувача. Але другий емітер того
|
||||
самого префаба, "StretchSmoke" (`startSpeed:14`, `worldSpace:true`, життя
|
||||
0.2-0.3с — тобто до ~4 world units прольоту по прямій!), у нашій сцені
|
||||
летить ДАЛЕКО від крейта (видно на скріні як сірувата паличка десь у
|
||||
верхній частині екрана, повністю відірвана від точки удару) — швидше за
|
||||
все, дизайнер тюнив цей ассет під інший масштаб сцени/камери. Це
|
||||
відтворюється на КОЖНОМУ хіті (не витік/застигла частинка — просто кожен
|
||||
новий хіт заново вилітає в приблизно те саме "неправильне" місце).
|
||||
**Ще не виправлено** — варіанти: (а) лишити як є (автентичний ассет), (б)
|
||||
притлумити цей конкретний емітер (зменшити `startSpeed`/життя клоном
|
||||
префаба перед грою), (в) прибрати "StretchSmoke" зовсім і лишити тільки
|
||||
"SharpImpact". Потребує візуальної перевірки користувачем — не вирішував
|
||||
сам, це пряма зміна дизайнерського ассета.
|
||||
|
||||
Destroy VFX (`VFX_Lootable_Destroy`, 4 емітери включно з mesh-based
|
||||
"Ground_Dirt") підключений і не кидає помилок в консолі, але **ще не
|
||||
підтверджений візуально** — не вдалось надійно спричинити повне
|
||||
знищення крейта в тестовому прогоні за відведений час.
|
||||
|
||||
## Наступні кроки (ще не реалізовано, чекає на пріоритети користувача)
|
||||
HP-бар (потребує рішення: дискретні стейджі vs. реальний HP-пул?) і числа
|
||||
урону (DOM чи sprite в 3D-просторі? теж потребує реального numeric HP,
|
||||
якого зараз нема) — ще не почато. `PassionOne-Black.otf` (в `temp/`,
|
||||
ще не імпортований) — ймовірно призначений саме для цього.
|
||||
Reference in New Issue
Block a user