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

11 KiB
Raw Blame History

Камера: прибрано 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 після ходьби/бою/розвороту персонажа — камера просто зсувається лінійно, кут не змінюється.