5.9 KiB
Health bar для дестройбл-елементів (2026-08-13)
Референс-скріншот: прямокутний хелз бар з бордером, синя заливка HP, чорним — вже відняте попередніми ударами, білим — відняте саме поточним ударом (з коротким затуханням у чорне), число нанесеного урону над баром. Нехай 10 урону за удар, 100хп у цілого ящика.
BreakablePropC — перехід з дискретного лічильника хітів на HP
HITS_PER_STAGE/hitsOnStage повністю видалені. BreakableProp тепер
несе currentHp/maxHp (MAX_HP = 100, export-нутий, щоб HealthBarC
рахував той самий 0..1 дріб без дублювання числа) + startStageNumber/
totalPossibleStages — реальні номери з _SN у назвах стейдж-нод (не
вигадані), з яких рахується, скільки HP пошкоджений з коробки крейт вже
має "по дефолту" (пошкоджені S2/S3-старт крейти стартують не з 100).
hit() тепер: відняти HIT_DAMAGE, повідомити HealthBarC.onHit(...),
знищити на HP<=0 (раніше — "просунувся за останній стейдж"), інакше —
перевести stageIndex на Math.floor((1 - currentHp/maxHp) * totalPossibleStages) (з відступом на startStageNumber), змаплений у
межі власного (можливо короткого) масиву стейджів.
HealthBarC.ts — новий контролер, world-tracking DOM-бари
Одна мапа BreakableProp -> BarInstance, живиться з BreakablePropC.hit()
через onHit/onDestroyed (аналогічний DI-паттерн до ResourceC.setFlyTarget).
Позиціювання: position:fixed + сирі px з canvasRect (та ж техніка,
що й HudC.getWorldAnchorPosition, лише в протилежний бік — тепер
винесена в src/utils/screenWorldProjection.ts, обидва напрямки). Точка
прив'язки — реальний Box3().setFromObject(prop.root) (верх+центр X/Z),
не вгаданий офсет.
Три кольори — чистий CSS, без пофреймової JS-анімації: .health-bar__white
й .health-bar__blue — два накладені div. На кожен хіт: білий миттєво
(transition-duration: 0s) стрибає на ПОПЕРЕДНІЙ %, форс-reflow через
offsetWidth, потім і білий (з transition-delay), і синій одразу
виставляються на ПОТОЧНИЙ % — синій доїжджає без затримки, білий
"зависає" на старому значенні й наздоганяє з лагом, показуючи чорний трек
під собою в проміжку. Ніякого requestAnimationFrame/tween — сама
CSS-transition timing і є ефектом.
Авто-приховування: idleTime рахується щокадру, >= HIDE_DELAY_S (2с)
без нового хіта — is-hiding клас + FADE_OUT_MS (250мс) opacity-transition,
потім видалення з DOM. Скидається на 0 при кожному onHit. onDestroyed
видаляє бар негайно (порожній бар не має лишатись висіти).
Числа урону — кілька раундів фідбеку, фінал:
- Спочатку плавали вгору з затуханням — користувач попросив натомість
різку появу непрозорого числа (
-webkit-text-stroke, не text-shadow-глов) з різким скейл-панчем до 1.5x + ротейтом і поверненням, і тільки ПОТІМ затуханням (не рухом). - Ротейт — послідовно звужувався: спершу довільний напрямок/кут, потім
користувач уточнив "тільки по годинниковій стрілці, до 2 години дня"
(тобто 0..60°,
--damage-rotate, рандомізується в JS на кожен спавн, читається в keyframe черезvar()). - Баг знайдений і виправлений: затухання на спокої (force=0, у контексті джойстика — не плутати з цим, тут йдеться про сам панч) показувало прозору прогалину замість базового кольору, бо альфа інтерполювалась до 0 — фікс: альфа фіксована, змінюється лише RGB-канал (той самий принцип пізніше повторився і в джойстику, див. нижче).
- Тривалість/розмір/швидкість анімації (920мс, +20% розмір шрифту, expo-out крива "швидкий старт — повільний кінець") — прямі числові правки за проханням користувача.
src/css/health-bar.css — новий файл
Слідує тому ж house convention, що й ui.css (одна --health-bar-width
змінна, --lvh не сирий vh) — окремо зазначено користувачем не
чіпати: файл (як і ui.css) свідомо не отримав landscape
@media-блоку і досі на vh, не rem/% — відомий, прийнятий
відступ від Day 8 чекліста, відкладений на потім.