Files
onboarding-project/docs/worklog/04-camera-linear-offset.md
2026-08-14 15:03:43 +03:00

121 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Камера: прибрано `lookAt`, лінійне зміщення замість орбіти (2026-08-12)
Фідбек від ментора користувача: замість `lookAt`-based слідкування камери
за персонажем — краще визначити точку біля персонажа і зміщувати камеру
відносно неї, залежно від напрямку погляду персонажа. Уточнення від
користувача: **без orbit-behind** — це має бути суто лінійне зміщення
позиції, кут камери взагалі не повинен обертатись.
## Що було
`CameraFollowC.update()` щокадру: (1) демпінгував позицію камери до
`targetPosition + offset + lookAhead` (це вже було чисте лінійне
зміщення — `lookAhead` — той самий "зсув в напрямку погляду персонажа",
про який казав ментор, він вже існував), (2) **окремо** демпінгував
`smoothedLookAt`-точку і кожен кадр робив `camera.lookAt(smoothedLookAt)`
— тобто поворот перераховувався з нуля щокадру з двох незалежно
згладжених точок, а не одного узгодженого стану.
## Що зроблено (перша ітерація)
- Прибрано `smoothedLookAt`, `LOOK_DAMPING`, і сам виклик `camera.lookAt()`
з `update()` повністю. Позиційна частина (`offset` + `lookAhead` +
obstacle-avoidance raycast) лишилась незмінною — вона й раніше була
чистим translation, без жодного обертання.
- Поворот камери відтепер **виставляється один раз** в `init()`:
`camera.position.copy(target.position).add(offset); camera.lookAt(target.position);`
— і після цього `update()` більше НІКОЛИ не торкається `camera.rotation`/
`camera.quaternion`. Кут "заморожений" геометрично на старті й лишається
таким назавжди, скільки б персонаж не розвертався.
## Доопрацювання: навіть без обертання камери відчувався рух "по колу" (2026-08-12)
Користувач: коли персонаж РОЗВЕРТАЄТЬСЯ (не рухаючись при цьому), камера
все ще відчутно "йде по колу". Причина — той самий `lookAhead`-вектор, що
лишили в першій ітерації: він рахувався з напрямку, куди персонаж
**дивиться** (`facing`, з `target.quaternion`), а не куди рухається.
Навіть згладжений (`LOOK_AHEAD_DAMPING`), він все одно ОБЕРТАЄТЬСЯ разом
з поворотом персонажа (наприклад, коли той розвертається на місці, щоб
глянути на ящик) — а що обертається, те й тягне позицію камери по дузі
навколо персонажа, хай і невеликій. Користувач хотів буквально "2 точки і
пряма між ними" — жодної залежності від напрямку погляду.
**Перша спроба**: прибрав `lookAhead` повністю (`desiredPosition =
targetPosition + offset`, без жодного додаткового вектора). Круговий рух
дійсно зник, але користувач одразу зауважив: зникло й саме зміщення —
"раніше зміщення було підходяще, єдина проблема була в тому, що воно йшло
по колу". Тобто магнітуда/факт панорамування був потрібен, просто НЕ
прив'язаний до повороту.
**Фінальне виправлення**: `lookAhead` вернув, але тепер рахується з
**реального зміщення позиції персонажа між кадрами**
(`targetPosition - previousTargetPosition`), а не з `target.quaternion`.
`previousTargetPosition` — нове поле, оновлюється щокадру. Якщо кадровий
рух менший за `MOVEMENT_EPSILON_SQ` (стоїть на місці — байдуже, як
розвернутий) — цільовий look-ahead = нульовий вектор; якщо рухається —
нормалізований напрямок руху × `LOOK_AHEAD_DISTANCE` (та сама магнітуда,
що й була). Обидва варіанти йдуть через той самий `smoothedLookAhead.lerp`
з `LOOK_AHEAD_DAMPING`, як і раніше — тільки джерело напрямку інше.
Результат: розворот на місці = нуль руху = камера взагалі не рухається;
ходьба = той самий пан вперед, що й був до всіх цих правок.
**Урок**: "згладжений вектор, що обертається з об'єктом" все одно
читається як орбітальний рух — згладжування прибирає різкість, а не
кривизну; правильний фікс — прив'язати джерело напрямку до РУХУ
(position delta), а не до facing/quaternion, а не просто видалити ефект.
Перевірено в браузері (Playwright) — рендер коректний з першого кадру і
після ходьби/бою, без console-помилок.
## Доопрацювання ×3: зовсім не відчувалось, тримати зсув, миттєвий розворот на 180° (2026-08-12)
Після руху-based lookahead користувач: "Немає зміщення взагалі". Причина —
не баг у логіці (перевірив логуванням, числа рахувались вірно), а те, що
**стара `lookAt`-версія давала подвійний ефект** (і зсув позиції, і
доворот камери в той же бік щокадру), а зараз лишився тільки зсув позиції
— і сама позиційна складова (`LOOK_AHEAD_DISTANCE=1.2`) була занадто
малою, щоб щось означати з відстані ~12.7 од. (`(0,9,-9)` риг). Підняв до
`4`, користувач сам потюнив назад до **`2`** (лишив цю зміну, не
відкатувати).
Ще дві правки в тому ж повідомленні:
- **"Камера після зсуву не має повертатись назад"** — `desiredLookAhead`
тепер **окреме поле**, не локальна константа: оновлюється (`.copy(movement)...`)
ТІЛЬКИ коли `movement.lengthSq() > MOVEMENT_EPSILON_SQ`, і просто НЕ
чіпається, коли персонаж стоїть — замість `: new Vector3()` (нуль) в
тернарному операторі, як було. Тобто пан тримається на місці, де
зупинився, а не з'їжджає назад до центру, поки не з'явиться новий, ІНШИЙ
напрямок руху.
- **"Зроби анімацію плавнішою"** — `LOOK_AHEAD_DAMPING` знижений з `2` до
`1.2` (повільніший ease).
- **"Персонаж має розвертатись моментально, якщо ми змінюємо напрям на
протилежний"** — це вже не про камеру, а про `PlayerC` (`updateMovement`):
новий `OPPOSITE_TURN_THRESHOLD = Math.PI * (150/180)` (~150°). Якщо кут
між поточним і цільовим facing (`quaternion.angleTo(...)`) перевищує цей
порог — `quaternion.copy(facingRotation)` миттєво, інакше — старий
капований `rotateTowards(facingRotation, MAX_TURN_SPEED * delta)`. Тобто
тільки різкі розвороти "в протилежну сторону" миттєві, дрібні корекції
курсу лишаються плавними, як і були.
## ⚠️ Знахідка: `camera_rotation_p`/`camera_rotation_l` (дизайнерський конфіг) виявився нежиттєздатним самостійно
Спочатку спробував залишити кут камери таким, яким його виставляє
`CameraC.setCamera()` з конфіг-параметрів `camera_rotation_p`/`_l`
(дизайнер може їх тюнити через UI) — просто прибравши `lookAt` і НІЧОГО
більше не додаючи. Результат: **порожнє темно-синє небо**, ні мапи, ні
персонажа в кадрі. Причина: цей конфіг-кут ніколи насправді не був
призначений працювати самостійно — `CameraFollowC` з першого ж комміту,
що його додав, миттєво перезаписував поворот через `lookAt()` щокадру,
тож ніхто й не міг помітити (чи потребу) тюнити `camera_rotation_p` під
реальний рух — він був "мертвим" параметром, що впливав щонайбільше на
один кадр до першого `update()`.
**Виправлення**: не покладатись на цей конфіг взагалі для follow-камери —
рахувати фіксований кут геометрично з реального `offset` (`(0,9,-9)`) в
момент `CameraFollowC.init()` (описано вище). Це гарантовано framing
персонажа з самого старту, незалежно від того, що зараз стоїть в
`camera_rotation_p`. **Урок**: значення з `configUIParams`/`globalSettings`
не завжди відображають те, що реально відбувається в грі — перевіряти,
чи щось інше (як тут `CameraFollowC`) не перезаписує їх одразу після
встановлення, перш ніж покладатись на них як на джерело правди.
Перевірено в браузері (Playwright): після фіксу сцена рендериться коректно
з самого старту (мапа/персонаж/крейти на місці, кут ідентичний
попередньому вигляду), і лишається так само коректно framed після
ходьби/бою/розвороту персонажа — камера просто зсувається лінійно, кут
не змінюється.