diff --git a/Content/Blueprints/BP_MainCharacter.uasset b/Content/Blueprints/BP_MainCharacter.uasset index a2d4b4f..0c78bce 100644 --- a/Content/Blueprints/BP_MainCharacter.uasset +++ b/Content/Blueprints/BP_MainCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:566676c4c7ff13fecce9a8fcaf504d59752422e1e69fa477e8a7f035b67edc62 -size 314181 +oid sha256:59bc47bf1be763d5f4e13c709fc3ee6d89c3fd33dfb9024383f696f3273a32a1 +size 347043 diff --git a/Content/Input/Actions/IA_Jump.ts b/Content/Input/Actions/IA_Jump.ts new file mode 100644 index 0000000..665cbce --- /dev/null +++ b/Content/Input/Actions/IA_Jump.ts @@ -0,0 +1,6 @@ +// Content/Input/Actions/IA_Jump + +import { InputAction } from '/Content/UE/InputAction.ts'; +import { Name } from '/Content/UE/Name.ts'; + +export const IA_Jump = new InputAction(null, new Name('IA_Jump')); diff --git a/Content/Input/Actions/IA_Jump.uasset b/Content/Input/Actions/IA_Jump.uasset new file mode 100644 index 0000000..581850b --- /dev/null +++ b/Content/Input/Actions/IA_Jump.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0b8f52272ffe80b79af2a5fc0ea08a6be53b2fe3753e14bc703bc3f1753dd25 +size 1142 diff --git a/Content/Input/IMC_Default.ts b/Content/Input/IMC_Default.ts index fc29817..94c9681 100644 --- a/Content/Input/IMC_Default.ts +++ b/Content/Input/IMC_Default.ts @@ -10,6 +10,7 @@ import { IA_ToggleHUD } from '/Content/Input/Actions/IA_ToggleHUD.ts'; import { IA_ToggleVisualDebug } from '/Content/Input/Actions/IA_ToggleVisualDebug.ts'; import { InputMappingContext } from '/Content/UE/InputMappingContext.ts'; import { Key } from '/Content/UE/Key.ts'; +import { IA_Jump } from '/Content/Input/Actions/IA_Jump.ts'; export const IMC_Default = new InputMappingContext(); @@ -21,3 +22,4 @@ IMC_Default.mapKey(IA_ToggleHUD, new Key('IA_ToggleHUD')); IMC_Default.mapKey(IA_ToggleVisualDebug, new Key('IA_ToggleVisualDebug')); IMC_Default.mapKey(IA_Look, new Key('IA_Look')); IMC_Default.mapKey(IA_Move, new Key('IA_Move')); +IMC_Default.mapKey(IA_Jump, new Key('IA_Jump')); diff --git a/Content/Input/IMC_Default.uasset b/Content/Input/IMC_Default.uasset index 8f3dbab..c8d6338 100644 --- a/Content/Input/IMC_Default.uasset +++ b/Content/Input/IMC_Default.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:291eb306a3955379f8379b3912aa85a80bcc33b94fa56aeb69c30185ca834a77 -size 11443 +oid sha256:93af5cf62d01121d470ee4154513ac5d41d8658ccdb6477cf7f90c04ca6f68d0 +size 12206 diff --git a/Content/Movement/DA_TengriMovementConfig.uasset b/Content/Movement/DA_TengriMovementConfig.uasset index 14a51ce..ebc4b77 100644 --- a/Content/Movement/DA_TengriMovementConfig.uasset +++ b/Content/Movement/DA_TengriMovementConfig.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df346aa688899f30326cc51868d2bed03ae0ff23e723fb85d5aa30a56777ea08 -size 1231 +oid sha256:c1ab3af1bead68f886c036df3a75ab1381591ffba4a23e6c0893e44dc1b53519 +size 1401 diff --git a/Documentation/Roadmap.md b/Documentation/Roadmap.md index 8a08d1e..bc0f9ea 100644 --- a/Documentation/Roadmap.md +++ b/Documentation/Roadmap.md @@ -1,553 +1,476 @@ -[//]: # (Documentation/Roadmap.md) +# TengriPlatformer — Roadmap v2.0 -# Этап 1: Базовая настройка и константы -**Цель:** Система классификации поверхностей по углам - -**Результат:** Стабильная база для всех последующих расчетов - -**Что реализуем:** -- Переменные движения (MaxSpeed, Acceleration, Friction, Gravity) -- Система углов поверхностей (Walkable ≤50°, SteepSlope ≤85°, Wall ≤95°, Ceiling >95°) -- Конвертация градусы ↔ радианы -- Функции инициализации и тестирования констант -- Enhanced Input System интеграция - -**Критерии успеха:** -- ✅ Корректная конвертация углов (точность <0.001) -- ✅ Все константы инициализируются при старте -- ✅ Debug вывод показывает правильные значения -- ✅ Автоматические тесты проходят +> **Статус:** Этапы 1-12 завершены +> **Фокус:** Прототип уровня "Подвал" + полноценная система движения +> **Принцип:** Сначала базовое движение, потом механики уровня, потом полировка --- -# Этап 2: Debug HUD система -**Цель:** Статичный debug вывод для удобной отладки +## ✅ ЗАВЕРШЁННЫЕ ЭТАПЫ (1-12) -**Результат:** Профессиональная debug система - -**Что реализуем:** -- Цветовое кодирование разных типов информации -- Функции переключения debug режимов -- Контроль частоты обновления HUD - -**Критерии успеха:** -- ✅ Информация отображается статично на экране -- ✅ Цветовая дифференциация работает -- ✅ Легкое включение/выключение debug режимов -- ✅ Нет влияния на производительность +| # | Название | Статус | +|---|----------|--------| +| 1 | Инициализация проекта | ✅ | +| 2 | Debug HUD система | ✅ | +| 3 | Toast уведомления | ✅ | +| 4 | Камера система | ✅ | +| 5 | Детекция устройства ввода | ✅ | +| 6 | Enhanced Input настройка | ✅ | +| 7 | Базовое движение по земле | ✅ | +| 8 | Поворот персонажа | ✅ | +| 9 | Sweep collision | ✅ | +| 10 | Стены и углы (wall sliding, step-up) | ✅ | +| 11 | Ground snapping и склоны | ✅ | +| 12 | Fixed Timestep + Interpolation | ✅ | --- -# Этап 3: Система сообщений в виде тостов -**Цель:** Удобный вывод сообщений для отладки - -**Результат:** Система сообщений в виде тостов - -**Что реализуем:** -- Функции для отображения сообщений в виде тостов -- Цветовая дифференциация сообщений -- Контроль времени отображения тостов -- Подстройка положения тостов на экране в зависимости от их количества -- Анимация появления и исчезновения тостов - -**Критерии успеха:** -- ✅ Сообщения отображаются в виде тостов -- ✅ Цветовая дифференциация работает -- ✅ Тосты исчезают через заданное время -- ✅ Положение тостов адаптируется в зависимости от их количества -- ✅ Анимация появления и исчезновения тостов плавная и не вызывает рывков +# 🔴 ФАЗА 1: CORE MOVEMENT (Критический путь) +> Без этого нельзя тестировать ничего другое --- -# Этап 4: Детекция поверхностей -**Цель:** Надежное определение типа поверхности под персонажем +## Этап 13: Система прыжков +**Цель:** Отзывчивое управление уровня лучших платформеров +**Блокирует:** ВСЕ механики уровня требуют прыжков -**Результат:** Стабильная классификация Walkable/SteepSlope/Wall/Ceiling - -**Что реализуем:** -- Функции классификации поверхности по нормали -- Функции запросов состояния (IsSurfaceWalkable, IsSurfaceSteep, etc.) -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD - -**Критерии успеха:** -- ✅ Точная классификация поверхностей по углам -- ✅ Стабильное определение типа поверхности -- ✅ Корректная работа с нормалями поверхностей -- ✅ Детальная debug информация -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD - ---- - -# Этап 5: Детекция текущего игрового девайса -**Цель:** Определение типа устройства ввода (мышь/клавиатура) - -**Результат:** Стабильное определение типа устройства ввода - -**Что реализуем:** -- Функции определения типа устройства (E_InputDeviceType) -- Функции проверки состояния устройства (IsKeyboard, IsGamepad) -- Смена подсказок в HUD в зависимости от устройства -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD - -**Критерии успеха:** -- ✅ Корректное определение типа устройства ввода -- ✅ Подсказки в HUD меняются в зависимости от устройства -- ✅ Легкая интеграция с Enhanced Input System -- ✅ Отсутствие ошибок при смене устройства -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD - ---- - -# Этап 6: Вращение камерой мышкой или стиком -**Цель:** Плавное вращение камеры с учетом устройства ввода - -**Результат:** Плавное управление камерой - -**Что реализуем:** -- Плавное вращение камеры при движении мышью или стиком геймпада -- Учет чувствительности и инверсии осей -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD - -**Критерии успеха:** -- ✅ Плавное вращение камеры при движении мышью -- ✅ Плавное вращение камеры при движении стиком геймпада -- ✅ Учет чувствительности и инверсии осей -- ✅ Отсутствие рывков и заиканий -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD - ---- - -# Этап 7: Базовое движение по земле -**Цель:** Плавное детерминированное движение по плоским поверхностям - -**Результат:** Отзывчивое управление без рывков и заиканий - -**Что реализуем:** -- VInterpTo для плавного ускорения и торможения -- Применение гравитации с правильным обнулением на земле -- Горизонтальное движение только на walkable поверхностях -- Ограничение максимальной скорости -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD - -**Критерии успеха:** -- ✅ Плавное ускорение при нажатии WASD и стика геймпада -- ✅ Плавное торможение при отпускании клавиш/стика геймпада -- ✅ Скорость не превышает MaxSpeed -- ✅ Диагональное движение не быстрее прямого -- ✅ Стабильное поведение на земле -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD - ---- - -# Этап 8: Поворот персонажа вслед за движением -**Цель:** Плавный поворот персонажа в сторону движения - -**Результат:** Персонаж естественно реагирует на направление движения - -**Что реализуем:** -- При использовании мыши или стика геймпада персонаж поворачивается в сторону движения -- Учет наклона камеры для корректного поворота -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD - -**Критерии успеха:** -- ✅ Персонаж плавно поворачивается в сторону движения -- ✅ Поворот учитывает наклон камеры -- ✅ Плавный переход между направлениями -- ✅ Нет рывков при повороте -- ✅ Персонаж не поворачивается, если не движется -- ✅ Поворот не влияет на скорость движения -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD - ---- - -# Этап 9: Детерминированный Sweep collision -**Цель:** Полное устранение tunneling через stepped collision detection -**Результат:** Bullet-proof система коллизий - -**Что реализуем:** -- PerformDeterministicSweep с пошаговой проверкой -- HandleSweepCollision для обработки ударов -- Адаптивный размер шагов sweep -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD - -**Критерии успеха:** -- ✅ Полное отсутствие tunneling при любых скоростях -- ✅ Стабильная Z позиция (разброс <0.5 единиц) -- ✅ Детерминированность (100% воспроизводимость) -- ✅ Performance <25 collision checks за кадр -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD - ---- - -# Этап 10: Обработка стен и углов -**Цель:** Плавное скольжение вдоль стен без застреваний -**Результат:** Качественная навигация в сложной геометрии - -**Что реализуем:** -- Wall sliding - скольжение вдоль стен -- Corner resolution - обработка внутренних углов -- Multi-directional sweep - несколько попыток движения -- Edge detection и step-up механика -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD - -**Критерии успеха:** -- ✅ Персонаж не застревает в углах -- ✅ Плавное скольжение вдоль стен любой геометрии -- ✅ Автоматический step-up на небольшие препятствия -- ✅ Работает в сложных лабиринтах -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD - ---- - -# Этап 11: Движение по склонам -**Цель:** Реалистичное поведение на наклонных поверхностях -**Результат:** Естественное движение по пандусам и скатывание - -**Что реализуем:** -- Slope walking - движение вверх/вниз по склонам ≤45° -- Slope sliding - скатывание с крутых поверхностей >45° -- Ground snapping - прилипание к неровной поверхности -- Momentum preservation на склонах -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD - -**Критерии успеха:** -- ✅ Плавный подъем по пандусам ≤45° -- ✅ Реалистичное скатывание с крутых склонов >45° -- ✅ Отсутствие "прыжков" на неровностях -- ✅ Сохранение импульса при переходах между поверхностями -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD - ---- - -# Этап 12: Разделение физики и рендера -**Цель:** Детерминированная физика + плавная визуализация -**Результат:** AAA-качество визуального движения - -**Что реализуем:** -- Dual position system (physics + render positions) -- Position interpolation для плавности -- Fixed timestep для физики (120Hz physics, variable render) -- Smooth transitions между состояниями -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD - -**Критерии успеха:** -- ✅ Физика остается детерминированной -- ✅ Визуально плавное движение без микрозаиканий -- ✅ Stable 60+ FPS без влияния на физику -- ✅ Smooth interpolation работает корректно -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD - ---- - -# Этап 13: Профессиональная камера система -**Цель:** Плавная камера уровня AAA-игр -**Результат:** Комфортная камера без рывков - -**Что реализуем:** -- Camera lag и damping для плавного следования -- Look-ahead prediction (камера смотрит вперед при движении) -- Smooth rotation следования за поворотами -- Dead zone для микродвижений -- Collision avoidance для камеры -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD - -**Критерии успеха:** -- ✅ Камера не дергается при остановке/старте -- ✅ Плавные повороты и наклоны -- ✅ Предсказание направления движения -- ✅ Нет проваливания камеры в стены -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD - ---- - -# Этап 14: Adaptive stepping optimization -**Цель:** Оптимизация производительности sweep системы -**Результат:** Меньше collision checks без потери качества - -**Что реализуем:** -- Variable step size в зависимости от скорости -- Субпиксельная точность для медленного движения -- Performance monitoring и auto-tuning -- Spatial optimization для collision queries -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD - -**Критерии успеха:** -- ✅ <10 collision checks при обычном движении -- ✅ Субпиксельная точность при медленном движении -- ✅ Автоматическая адаптация под нагрузку -- ✅ Stable performance в сложных сценах -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD - ---- - -# Этап 15: Enhanced ground snapping -**Цель:** Плавное прилипание к неровным поверхностям -**Результат:** Персонаж идет по неровной геометрии без отрыва - -**Что реализуем:** -- Multi-point ground detection -- Intelligent surface normal blending -- Smooth height transitions -- Predictive ground snapping -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD - -**Критерии успеха:** -- ✅ Плавное движение по ступенькам -- ✅ Нет отрыва от неровной поверхности -- ✅ Smooth transitions на изменениях высоты -- ✅ Работает на любой сложности геометрии -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD - ---- - -# Этап 16: Система прыжков -**Цель:** Отзывчивое воздушное управление уровня лучших платформеров -**Результат:** Качественный платформинг с точным контролем - -**Что реализуем:** +**Реализация:** - Variable jump height (короткое/длинное нажатие) - Air control с ограничениями и инерцией -- Coyote time (прыжок после покидания платформы) +- Coyote time (прыжок после покидания платформы, ~100-150ms) - Jump buffering (ранние нажатия прыжка) -- Multi-jump система (опционально) -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD +- Landing detection и recovery -**Критерии успеха:** -- ✅ Точный контроль высоты прыжка -- ✅ Forgiving jump timing (coyote + buffer) -- ✅ Responsive но не overpowered air control -- ✅ Плавные transitions между ground/air состояниями -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD +**Критерии:** +- [ ] Точный контроль высоты прыжка +- [ ] Forgiving timing (coyote + buffer) +- [ ] Responsive но не overpowered air control +- [ ] Плавные transitions ground ↔ air + +**Время:** ~4-6 часов --- -# Этап 17: Custom Camera Collision System -**Цель:** Детерминированная замена SpringArm collision detection -**Результат:** Полный контроль над camera collision behavior +## Этап 14: Воздушная физика +**Цель:** Естественное поведение в воздухе +**Нужно для:** Качественный platforming feel -**Что реализуем:** -- Custom sphere trace от character к target camera position -- Smooth camera pull-in при collision с obstacles -- Character highlighting когда он за препятствием (Mario Odyssey style) -- Safe camera position recovery при застревании в геометрии -- Debug visualization для camera collision traces -- Integration с visual debug system - -**Критерии успеха:** -- ✅ Камера никогда не проваливается сквозь препятствия -- ✅ Плавное приближение камеры при collision -- ✅ Character остается видимым (outline/highlight) когда за препятствием -- ✅ Детерминированное поведение на всех платформах -- ✅ Debug traces показывают camera collision queries -- ✅ Performance impact <0.1ms per frame - -# Этап 18: Воздушная физика -**Цель:** Реалистичная но игровая воздушная физика -**Результат:** Естественное поведение в полете - -**Что реализуем:** -- Air resistance и terminal velocity -- Wind/updraft systems -- Gliding механика -- Landing impact detection и анимации +**Реализация:** +- Gravity curve (быстрее падение чем подъём) +- Terminal velocity +- Air resistance (опционально) - Air-to-ground transition smoothing -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD +- Landing impact (приседание при жёстком приземлении) -**Критерии успеха:** -- ✅ Реалистичная траектория полета -- ✅ Плавные приземления без "хлопков" -- ✅ Terminal velocity ограничивает падение -- ✅ Responsive air control без нарушения физики -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD +**Критерии:** +- [ ] Прыжок ощущается "сочно" (Mario-like arc) +- [ ] Плавные приземления +- [ ] Предсказуемая траектория + +**Время:** ~3-4 часа --- -# Этап 19: Продвинутые склоны и поверхности -**Цель:** Сложные взаимодействия с геометрией -**Результат:** Разнообразные типы поверхностей - -**Что реализуем:** -- Ice surfaces (скользкие поверхности с инерцией) -- Conveyor belts (движущиеся платформы) -- Bouncy surfaces (отскакивающие поверхности) -- Sticky surfaces (замедляющие движение) -- Slope acceleration/deceleration physics -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD - -**Критерии успеха:** -- ✅ Каждый тип поверхности ощущается уникально -- ✅ Плавные переходы между типами поверхностей -- ✅ Детерминированное поведение всех типов -- ✅ Легкая настройка параметров поверхностей -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD +# 🟡 ФАЗА 2: LEVEL MECHANICS (Прототип "Подвал") +> Минимум для прохождения уровня от начала до конца --- -# Этап 20: Wall interactions +## Этап 15: Система подбора предметов +**Демо:** Подобрать камешек/кость, держать, положить +**Нужно для:** Бросок в свечу + +**Реализация:** +- Pickup radius detection +- Inventory slot (1 предмет в руках) +- Hold/drop mechanics +- Visual attachment к персонажу +- Item data asset (вес, throwable flag) + +**Критерии:** +- [ ] Подбор в радиусе ~100см +- [ ] Предмет визуально в руке +- [ ] Drop кладёт под ноги + +**Время:** ~3-4 часа + +--- + +## Этап 16: Система бросков +**Демо:** Кинуть камешек в свечу → свеча качается +**Нужно для:** Активация раскачивания + +**Реализация:** +- Aim trajectory preview (параболическая линия) +- Throw force (зажатие = сильнее) +- Projectile physics +- Impact detection → события +- Throwable interface + +**Критерии:** +- [ ] Траектория предсказуема +- [ ] Попадание триггерит события +- [ ] Можно подобрать снова + +**Время:** ~4-5 часов + +--- + +## Этап 17: Интерактивные объекты +**Демо:** Дёрнуть кольцо, открыть дверцу шкафа +**Нужно для:** Все механизмы уровня + +**Реализация:** +- IInteractable interface +- Interaction prompt UI (E / кнопка геймпада) +- State machine (open/closed, on/off) +- Single-use vs reusable +- Audio/visual feedback + +**Критерии:** +- [ ] Prompt появляется в радиусе +- [ ] Состояния сохраняются +- [ ] Работает keyboard + gamepad + +**Время:** ~3-4 часа + +--- + +## Этап 18: Физика качания (Pendulum) +**Демо:** Свеча качается, клетка качается синхронно +**Нужно для:** Побег из клетки, тарзанка + +**Реализация:** +- Pendulum physics (угол, длина, затухание) +- Player-induced swing (нажатия в такт) +- Swing transfer (синхронизация объектов) +- Impact trigger (свеча → верёвка) + +**Критерии:** +- [ ] Физика реалистична +- [ ] Раскачка интуитивна +- [ ] Объекты синхронизируются + +**Время:** ~5-6 часов + +--- + +## Этап 19: Система огня +**Демо:** Свеча поджигает верёвку → верёвка сгорает +**Нужно для:** Падение клетки + +**Реализация:** +- Fire source component +- Flammable component +- Burn duration → destruction +- Visual fire effect (Niagara) +- Fire spread (опционально) + +**Критерии:** +- [ ] Поджог требует контакта +- [ ] Горение → разрушение +- [ ] Визуально убедительно + +**Время:** ~4-5 часов + +--- + +## Этап 20: Система верёвки (Rope) +**Демо:** Накинуть верёвку на крюк, качнуться через котёл +**Нужно для:** Тарзанка + +**Реализация:** +- Rope throw targeting +- Attach point detection +- Rope physics (длина, натяжение) +- Swing while attached (использует Pendulum) +- Dismount с сохранением momentum + +**Критерии:** +- [ ] Верёвка цепляется за валидные точки +- [ ] Качание как pendulum +- [ ] Отпустить = сохранить скорость + +**Время:** ~6-8 часов + +--- + +## Этап 21: Система временных бафов +**Демо:** Выпить зелье → прыжок выше на 10 сек +**Нужно для:** Прыгучее зелье + +**Реализация:** +- Buff data asset (тип, длительность, множитель) +- Active buffs container +- Buff UI (иконка + таймер) +- Stat modification +- Consumable integration + +**Критерии:** +- [ ] Визуальный эффект на персонаже +- [ ] Таймер виден +- [ ] Эффект заканчивается плавно + +**Время:** ~4-5 часов + +--- + +## Этап 22: Головоломка с зеркалом +**Демо:** Шкаф невидим напрямую, виден в отражении +**Нужно для:** Шкаф с зельями + +**Реализация:** +- View frustum check +- Mirror render target +- Conditional visibility +- "Blind interaction" +- Shimmer effect (намёк) + +**Критерии:** +- [ ] Исчезает при прямом взгляде +- [ ] Виден в зеркале +- [ ] Можно взаимодействовать вслепую + +**Время:** ~6-8 часов + +--- + +## Этап 23: Складные механизмы +**Демо:** Дёрнуть кольцо → лестница разворачивается +**Нужно для:** Лестница к люку + +**Реализация:** +- Deployable base class +- Trigger mechanisms (кольцо, бросок) +- Deployment animation +- Safety check (не придавить игрока) + +**Критерии:** +- [ ] Активация от разных источников +- [ ] Плавная анимация +- [ ] Можно использовать после deployment + +**Время:** ~4-5 часов + +--- + +## Этап 24: Взбирание (Climbing) +**Демо:** Залезть по лестнице к люку +**Нужно для:** Финальный выход + +**Реализация:** +- Climbable surface detection +- Climb state machine +- Climb movement (вверх/вниз) +- Dismount (вверху, внизу, в сторону) + +**Критерии:** +- [ ] Автоцепляние к лестнице +- [ ] Плавное движение +- [ ] Выход наверху без рывков + +**Время:** ~4-5 часов + +--- + +# 🟢 ФАЗА 3: POLISH & ADVANCED MOVEMENT +> После играбельного прототипа + +--- + +## Этап 25: Профессиональная камера +**Цель:** AAA-уровень камеры + +**Реализация:** +- Camera lag и damping +- Look-ahead prediction +- Dead zone для микродвижений +- Collision avoidance (sphere trace) +- Character highlight когда за препятствием + +**Критерии:** +- [ ] Нет рывков при старте/остановке +- [ ] Камера не проваливается в стены +- [ ] Плавные повороты + +**Время:** ~5-6 часов + +--- + +## Этап 26: Wall interactions **Цель:** Продвинутые взаимодействия со стенами -**Результат:** Wall jumping, wall sliding, wall climbing -**Что реализуем:** -- Wall jumping с momentum preservation +**Реализация:** +- Wall jumping с momentum - Wall sliding с контролем скорости -- Wall climbing на специальных поверхностях -- Corner grabbing и edge detection -- Wall run система (опционально) -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD +- Corner grabbing +- Ledge detection -**Критерии успеха:** -- ✅ Responsive wall jumping с правильными углами -- ✅ Контролируемое wall sliding -- ✅ Smooth transitions wall ↔ ground ↔ air -- ✅ Интуитивное управление wall mechanics -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD +**Критерии:** +- [ ] Responsive wall jump +- [ ] Smooth transitions wall ↔ ground ↔ air + +**Время:** ~6-8 часов --- -# Этап 21: Специальные движения -**Цель:** Уникальные движения для богатого геймплея -**Результат:** Dash, ground pound, ledge grab и другие +## Этап 27: Специальные движения +**Цель:** Dash, ground pound, ledge grab -**Что реализуем:** -- Dash/dodge с invincibility frames +**Реализация:** +- Dash/dodge с i-frames - Ground pound с area impact - Ledge grabbing и climbing -- Slide/crouch движения -- Special movement abilities -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD +- Slide/crouch -**Критерии успеха:** -- ✅ Каждое движение ощущается impact-ful -- ✅ Smooth combinations между движениями -- ✅ Balanced timing и cooldowns -- ✅ Clear visual и audio feedback -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD +**Критерии:** +- [ ] Каждое движение impactful +- [ ] Smooth combinations + +**Время:** ~6-8 часов --- -# Этап 22: Performance optimization -**Цель:** 60 FPS на целевом железе в любых сценариях -**Результат:** Оптимизированная система коллизий +## Этап 28: Продвинутые поверхности +**Цель:** Разнообразие типов поверхностей -**Что реализуем:** -- Spatial partitioning для collision objects -- LOD system для collision complexity -- Multi-threading collision checks -- Memory pool для collision queries -- Predictive collision culling -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD +**Реализация:** +- Ice (скользкие) +- Conveyor belts +- Bouncy surfaces +- Sticky surfaces -**Критерии успеха:** -- ✅ Stable 60+ FPS на целевом железе -- ✅ <5ms на collision detection в worst case -- ✅ Scalable performance до 100+ collision objects -- ✅ Minimal memory allocations в runtime -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD +**Критерии:** +- [ ] Каждый тип уникален +- [ ] Плавные переходы между типами + +**Время:** ~5-6 часов --- -# Этап 23: Debug и профилирование tools -**Цель:** Профессиональные инструменты для тонкой настройки -**Результат:** Полный контроль над системой +## Этап 29: Audio система +**Цель:** Звуковой feedback -**Что реализуем:** -- Visual collision debugging (rays, sweeps, contacts) -- Real-time performance metrics и profiling -- Tweakable parameters в runtime через UI -- Automated testing suite для regression testing +**Реализация:** +- Footstep sounds (по типу поверхности) +- Jump/land sounds +- Interaction sounds +- Ambient audio + +**Время:** ~4-5 часов + +--- + +## Этап 30: Debug и профилирование +**Цель:** Инструменты для тонкой настройки + +**Реализация:** +- Visual collision debugging +- Runtime tweakable parameters +- Performance metrics - Replay system для детерминированности -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD -**Критерии успеха:** -- ✅ Visual debugging показывает все collision queries -- ✅ Real-time параметры настраиваются без restart -- ✅ Performance metrics точные и useful -- ✅ Automated tests покрывают все основные сценарии -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD +**Время:** ~4-5 часов --- -# Этап 24: Edge cases и stress testing -**Цель:** Bullet-proof система для любых условий -**Результат:** Система работает в экстремальных сценариях +## Этап 31: Edge cases и stress testing +**Цель:** Bullet-proof система -**Что реализуем:** -- Extreme velocity testing (10000+ units/sec) +**Реализация:** +- Extreme velocity testing - Complex geometry stress tests -- Memory leak detection и prevention -- Determinism verification tools -- Edge case handling (NaN, infinity, etc.) -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD +- Memory leak detection +- NaN/infinity handling -**Критерии успеха:** -- ✅ Система не ломается при экстремальных значениях -- ✅ No memory leaks при длительной работе -- ✅ Determinism поддерживается в любых условиях -- ✅ Graceful degradation при перегрузке -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD +**Время:** ~3-4 часа --- -# Этап 25: User experience polish -**Время:** 3-4 дня | **Сложность:** Средняя -**Цель:** Finalized user experience -**Результат:** Система ощущается как в коммерческой игре +## Этап 32: UX Polish +**Цель:** Commercial game feel -**Что реализуем:** -- Input buffering и prediction -- Haptic feedback integration (геймпады) -- Audio feedback integration для movement -- Visual effects integration (dust, particles) +**Реализация:** +- Input buffering refinement +- Haptic feedback (gamepad) +- Visual effects (dust, particles) - Accessibility options -- Вывод необходимых значений в Debug HUD -- Вывод результатов тестов в HUD -**Критерии успеха:** -- ✅ Controls ощущаются максимально responsive -- ✅ Rich feedback для всех действий -- ✅ Поддержка различных input методов -- ✅ Accessibility options работают корректно -- ✅ Значения корректно отображаются в Debug HUD -- ✅ Результаты тестов отображаются в HUD +**Время:** ~4-5 часов --- + +# 🔵 ФАЗА 4: CONTENT +> После полировки движения + +--- + +## Этап 33: Level transition +Переход люк → кухня + +## Этап 34: Save/Checkpoint +Сохранение прогресса + +## Этап 35: Второй уровень +Кухня первого этажа + +--- + +## ЗАВИСИМОСТИ + +``` +ФАЗА 1 (Core Movement) +[13: Прыжки] ──► [14: Воздушная физика] + │ + ▼ +ФАЗА 2 (Level Mechanics) + │ + ├──► [15: Подбор] ──► [16: Броски] ───┐ + │ │ + ├──► [17: Интерактивы] ───────────────┤ + │ │ + ├──► [18: Качание] ──► [19: Огонь] ───┼──► КЛЕТКА + │ │ │ + │ └──► [20: Верёвка] ──────────┼──► ТАРЗАНКА + │ │ + ├──► [21: Бафы] ──┬──► [22: Зеркало] ──┼──► ШКАФ + │ │ │ + └──► [23: Механизмы] ──► [24: Climbing]┴──► ЛЮКЙ +``` + +--- + +## ОЦЕНКА ВРЕМЕНИ + +| Фаза | Этапы | Часы | Результат | +|------|-------|------|-----------| +| 1: Core | 13-14 | ~7-10 | Прыжки работают | +| 2: Level | 15-24 | ~44-58 | Уровень проходим | +| 3: Polish | 25-32 | ~37-47 | AAA feel | +| 4: Content | 33-35 | ~10-15 | Больше контента | +| **Итого** | | **~98-130** | | + +**MVP (Фазы 1-2):** ~51-68 часов = **13-17 дней** (при 4ч/день) +**Polished (+ Фаза 3):** ~88-115 часов = **22-29 дней** + +--- + +## КРИТИЧЕСКИЙ ПУТЬ ДО MVP + +``` +13 → 14 → 15 → 16 → 17 → 18 → 19 → 20 → 21 → 22 → 23 → 24 + ▲ + │ +START HERE +``` + +Каждый этап даёт демонстрируемый результат. + +--- + +## ПРИМЕЧАНИЯ + +1. **Debug HUD** — используем UE инструменты (Gameplay Debugger, etc.) +2. **Тесты** — отложены до Фазы 3 +3. **C++** — продолжаем использовать для производительности +4. **Итерации** — после каждого этапа можно показать прогресс diff --git a/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.cpp b/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.cpp index d2db6bf..29115a7 100644 --- a/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.cpp +++ b/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.cpp @@ -1,4 +1,6 @@ -// Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.cpp +// Request Games © All rights reserved + +// Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.cpp #include "TengriCollisionResolver.h" #include "Components/CapsuleComponent.h" diff --git a/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.h b/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.h index b9e632b..17418bc 100644 --- a/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.h +++ b/Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.h @@ -1,4 +1,6 @@ -// Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.h +// Request Games © All rights reserved + +// Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.h #pragma once diff --git a/Source/TengriPlatformer/Movement/Collision/TengriSweepResult.h b/Source/TengriPlatformer/Movement/Collision/TengriSweepResult.h index 0f2570c..6fe5408 100644 --- a/Source/TengriPlatformer/Movement/Collision/TengriSweepResult.h +++ b/Source/TengriPlatformer/Movement/Collision/TengriSweepResult.h @@ -1,4 +1,6 @@ -// Source/TengriPlatformer/Movement/Collision/TengriSweepResult.h +// Request Games © All rights reserved + +// Source/TengriPlatformer/Movement/Collision/TengriSweepResult.h #pragma once diff --git a/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.cpp b/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.cpp index 27e7ce6..e345349 100644 --- a/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.cpp +++ b/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.cpp @@ -1,4 +1,6 @@ -// Source/TengriPlatformer/Movement/Core/TengriMovementConfig.cpp +// Request Games © All rights reserved + +// Source/TengriPlatformer/Movement/Core/TengriMovementConfig.cpp #include "TengriMovementConfig.h" @@ -16,12 +18,36 @@ void UTengriMovementConfig::PostEditChangeProperty(FPropertyChangedEvent& Proper { Super::PostEditChangeProperty(PropertyChangedEvent); + // ════════════════════════════════════════════════════════════════════════ + // 1. CALCULATE JUMP PHYSICS + // ════════════════════════════════════════════════════════════════════════ + + // Safety check to prevent division by zero + const float ClampedTime = FMath::Max(TimeToJumpApex, 0.1f); + + // Calculate Gravity: g = (2 * h) / t^2 + Gravity = (2.0f * MaxJumpHeight) / FMath::Square(ClampedTime); + + // Calculate Initial Velocity: v = g * t + JumpVelocity = FMath::Abs(Gravity) * ClampedTime; + + // Calculate Min Jump Velocity: v_min = sqrt(2 * g * h_min) + // This is the velocity we clamp to when button is released early + MinJumpVelocity = FMath::Sqrt(2.0f * FMath::Abs(Gravity) * MinJumpHeight); + + // ════════════════════════════════════════════════════════════════════════ + // 2. VALIDATION LOGGING + // ════════════════════════════════════════════════════════════════════════ + + bool bHasErrors = false; + // Validate angle hierarchy if (WalkableAngleDeg >= SteepSlopeAngleDeg) { UE_LOG(LogTemp, Warning, TEXT("TengriMovementConfig: WalkableAngle (%.1f) should be less than SteepSlopeAngle (%.1f)"), WalkableAngleDeg, SteepSlopeAngleDeg); + bHasErrors = true; } if (SteepSlopeAngleDeg >= WallAngleDeg) @@ -29,18 +55,63 @@ void UTengriMovementConfig::PostEditChangeProperty(FPropertyChangedEvent& Proper UE_LOG(LogTemp, Warning, TEXT("TengriMovementConfig: SteepSlopeAngle (%.1f) should be less than WallAngle (%.1f)"), SteepSlopeAngleDeg, WallAngleDeg); + bHasErrors = true; } + // Validate collision parameters if (MaxStepHeight <= 0.f) { UE_LOG(LogTemp, Warning, TEXT("TengriMovementConfig: MaxStepHeight should be positive")); + bHasErrors = true; } if (GroundSnapDistance <= 0.f) { UE_LOG(LogTemp, Warning, TEXT("TengriMovementConfig: GroundSnapDistance should be positive")); + bHasErrors = true; + } + + // Validate jump parameters + if (MinJumpHeight >= MaxJumpHeight) + { + UE_LOG(LogTemp, Warning, + TEXT("TengriMovementConfig: MinJumpHeight (%.1f) should be less than MaxJumpHeight (%.1f)"), + MinJumpHeight, MaxJumpHeight); + bHasErrors = true; + } + + if (TimeToJumpApex <= 0.f) + { + UE_LOG(LogTemp, Warning, + TEXT("TengriMovementConfig: TimeToJumpApex should be positive")); + bHasErrors = true; + } + + // Validate air physics + if (AirControl < 0.f || AirControl > 1.f) + { + UE_LOG(LogTemp, Warning, + TEXT("TengriMovementConfig: AirControl should be between 0 and 1 (current: %.2f)"), + AirControl); + bHasErrors = true; + } + + if (FallingGravityScale < 1.f) + { + UE_LOG(LogTemp, Warning, + TEXT("TengriMovementConfig: FallingGravityScale should be >= 1.0 (current: %.2f)"), + FallingGravityScale); + bHasErrors = true; + } + + // Log calculated values for verification + if (!bHasErrors) + { + UE_LOG(LogTemp, Log, + TEXT("TengriMovementConfig: Calculated Physics - Gravity: %.1f, JumpVel: %.1f, MinJumpVel: %.1f"), + Gravity, JumpVelocity, MinJumpVelocity); } } #endif \ No newline at end of file diff --git a/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.h b/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.h index 340eb52..0c29ddd 100644 --- a/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.h +++ b/Source/TengriPlatformer/Movement/Core/TengriMovementConfig.h @@ -1,4 +1,6 @@ -// Source/TengriPlatformer/Movement/Core/TengriMovementConfig.h +// Request Games © All rights reserved + +// Source/TengriPlatformer/Movement/Core/TengriMovementConfig.h #pragma once @@ -81,9 +83,6 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics") float Friction = 8.0f; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics") - float Gravity = 980.0f; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics") float RotationSpeed = 360.0f; @@ -91,6 +90,78 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics") float MinSpeedForRotation = 10.0f; + // ======================================================================== + // JUMP CONFIGURATION (NEW) + // ======================================================================== + + /** Target height of the jump in cm (UE units) */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump", meta = (ClampMin = "10.0")) + float MaxJumpHeight = 200.0f; + + /** Minimum height for a short hop (when button is released early) */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump", meta = (ClampMin = "1.0")) + float MinJumpHeight = 40.0f; + + /** Time (seconds) to reach the peak of the jump. Defines "heaviness". */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump", meta = (ClampMin = "0.1", ClampMax = "2.0")) + float TimeToJumpApex = 0.5f; + + // ======================================================================== + // JUMP FEEL (TIMINGS) + // ======================================================================== + + /** Time (seconds) after falling off a ledge during which jump is still allowed */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump|Feel", meta = (ClampMin = "0.0")) + float CoyoteTime = 0.15f; + + /** Time (seconds) to buffer a jump input before hitting the ground */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Jump|Feel", meta = (ClampMin = "0.0")) + float JumpBufferTime = 0.15f; + + // ======================================================================== + // AIR PHYSICS + // ======================================================================== + + /** Multiplier for acceleration when in air (0 = no control, 1 = full control) */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Air Physics", meta = (ClampMin = "0.0", ClampMax = "1.0")) + float AirControl = 0.5f; + + /** Friction applied while in air (usually 0 for platformers) */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Air Physics") + float AirFriction = 0.0f; + + // ======================================================================== + // CALCULATED VALUES (READ ONLY) + // ======================================================================== + + /** Gravity magnitude calculated from Jump Height & Time */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Calculated") + float Gravity = 980.0f; + + /** Initial Z velocity required to reach MaxJumpHeight */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Calculated") + float JumpVelocity = 0.0f; + + /** Velocity cut-off for variable jump height */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Calculated") + float MinJumpVelocity = 0.0f; + + // ======================================================================== + // AIR PHYSICS (NEW) + // ======================================================================== + + /** Multiplier for gravity when falling. Makes jump feel "heavy" and snappy. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Air Physics", meta = (ClampMin = "1.0")) + float FallingGravityScale = 1.5f; + + /** Maximum falling speed (cm/s). Prevents infinite acceleration. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Air Physics", meta = (ClampMin = "0.0")) + float TerminalVelocity = 2000.0f; + + /** Z velocity threshold to consider a landing "heavy" (e.g. for landing animation/shake) */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Air Physics") + float HeavyLandVelocityThreshold = -1000.0f; + // ======================================================================== // SURFACE ANGLES // ======================================================================== diff --git a/Source/TengriPlatformer/Movement/TengriMovementComponent.cpp b/Source/TengriPlatformer/Movement/TengriMovementComponent.cpp index 0ae61c8..45e5af4 100644 --- a/Source/TengriPlatformer/Movement/TengriMovementComponent.cpp +++ b/Source/TengriPlatformer/Movement/TengriMovementComponent.cpp @@ -1,3 +1,5 @@ +// Request Games © All rights reserved + // Source/TengriPlatformer/Movement/TengriMovementComponent.cpp #include "TengriMovementComponent.h" @@ -20,6 +22,20 @@ namespace TengriMovement constexpr float JumpingThreshold = 10.0f; // cm/s, skip snap when moving up } +// ============================================================================ +// PENDING EVENTS (NEW) +// ============================================================================ + +/** + * Structure to accumulate events during physics loop. + * These are broadcasted AFTER all physics iterations complete. + */ +struct FPendingLandingEvent +{ + bool bIsHeavy; + float LandingVelocityZ; +}; + // ============================================================================ // CONSTRUCTOR // ============================================================================ @@ -43,7 +59,7 @@ void UTengriMovementComponent::BeginPlay() void UTengriMovementComponent::InitializeSystem() { - AActor* Owner = GetOwner(); + const AActor* Owner = GetOwner(); if (!Owner) { UE_LOG(LogTengriMovement, Error, TEXT("InitializeSystem failed: No owner")); @@ -97,12 +113,26 @@ void UTengriMovementComponent::InitializeSystem() // BLUEPRINT API // ============================================================================ -void UTengriMovementComponent::SetInputVector(FVector NewInput) +void UTengriMovementComponent::SetInputVector(const FVector NewInput) { InputVector = NewInput.GetClampedToMaxSize(1.0f); InputVector.Z = 0.0f; } +void UTengriMovementComponent::SetJumpInput(const bool bPressed) +{ + if (bPressed && !bIsJumpHeld) + { + if (MovementConfig) + { + JumpBufferTimer = MovementConfig->JumpBufferTime; + } + } + + // Обновляем состояние удержания + bIsJumpHeld = bPressed; +} + // ============================================================================ // TICK // ============================================================================ @@ -134,6 +164,12 @@ void UTengriMovementComponent::TickComponent( TimeAccumulator = MaxAccumulatorTime; } + // ════════════════════════════════════════════════════════════════════ + // PENDING EVENTS (Accumulated during physics) + // ════════════════════════════════════════════════════════════════════ + + TArray PendingLandings; + // ════════════════════════════════════════════════════════════════════ // DETERMINISTIC PHYSICS LOOP // ════════════════════════════════════════════════════════════════════ @@ -146,7 +182,8 @@ void UTengriMovementComponent::TickComponent( SavePreviousPhysicsState(); // Run deterministic physics at fixed rate - TickPhysics(FixedTimeStep); + // Pass reference to PendingLandings to accumulate events + TickPhysics(FixedTimeStep, PendingLandings); // Consume fixed time from accumulator TimeAccumulator -= FixedTimeStep; @@ -163,6 +200,28 @@ void UTengriMovementComponent::TickComponent( } } + // ════════════════════════════════════════════════════════════════════ + // BROADCAST ACCUMULATED EVENTS + // ════════════════════════════════════════════════════════════════════ + + // Only broadcast the LAST landing event if multiple occurred + // (Prevents spam if character lands multiple times in one frame) + if (PendingLandings.Num() > 0) + { + const FPendingLandingEvent& LastLanding = PendingLandings.Last(); + if (OnLanded.IsBound()) + { + OnLanded.Broadcast(LastLanding.bIsHeavy); + } + + if (LastLanding.bIsHeavy) + { + UE_LOG(LogTengriMovement, Verbose, + TEXT("Heavy landing detected! Velocity: %.1f cm/s"), + LastLanding.LandingVelocityZ); + } + } + // ════════════════════════════════════════════════════════════════════ // INTERPOLATION & RENDERING // ════════════════════════════════════════════════════════════════════ @@ -191,121 +250,224 @@ void UTengriMovementComponent::TickComponent( // PHYSICS TICK // ============================================================================ -void UTengriMovementComponent::TickPhysics(float FixedDeltaTime) +void UTengriMovementComponent::TickPhysics( + const float FixedDeltaTime, + TArray& OutPendingLandings) { - // ════════════════════════════════════════════════════════════════════ - // Phase 1: Acceleration & Friction - // ════════════════════════════════════════════════════════════════════ + // ════════════════════════════════════════════════════════════════════ + // Phase 0: State & Timer Updates + // ════════════════════════════════════════════════════════════════════ - const float CurrentZ = PhysicsVelocity.Z; - FVector HorizontalVelocity(PhysicsVelocity.X, PhysicsVelocity.Y, 0.f); + // 1. Manage Coyote Time (Can we jump while falling?) + if (bIsGrounded) + { + CoyoteTimer = MovementConfig->CoyoteTime; + bHasJumpedThisFrame = false; + } + else + { + CoyoteTimer -= FixedDeltaTime; + } - if (!InputVector.IsNearlyZero()) - { - const FVector TargetVelocity = InputVector * MovementConfig->MaxSpeed; - HorizontalVelocity = FMath::VInterpTo( - HorizontalVelocity, - TargetVelocity, - FixedDeltaTime, - MovementConfig->Acceleration - ); - } - else - { - HorizontalVelocity = FMath::VInterpTo( - HorizontalVelocity, - FVector::ZeroVector, - FixedDeltaTime, - MovementConfig->Friction - ); - } + // 2. Manage Jump Buffer (Did we press jump recently?) + if (JumpBufferTimer > 0.0f) + { + JumpBufferTimer -= FixedDeltaTime; + } - PhysicsVelocity = HorizontalVelocity; - PhysicsVelocity.Z = CurrentZ; + // ════════════════════════════════════════════════════════════════════ + // Phase 1: Jump Execution + // ════════════════════════════════════════════════════════════════════ - // ════════════════════════════════════════════════════════════════════ - // Phase 2: Rotation - // ════════════════════════════════════════════════════════════════════ + // Check if we can jump: + // 1. Button was pressed recently (Buffer > 0) + // 2. We are on ground OR recently left ground (Coyote > 0) + // 3. We haven't already jumped this frame (double jump prevention) + if (JumpBufferTimer > 0.0f && CoyoteTimer > 0.0f && !bHasJumpedThisFrame) + { + // Apply Jump Velocity + PhysicsVelocity.Z = MovementConfig->JumpVelocity; + + // Update State + bIsGrounded = false; + bHasJumpedThisFrame = true; + + // Consume Timers + JumpBufferTimer = 0.0f; + CoyoteTimer = 0.0f; + } - const float MinSpeedSq = FMath::Square(MovementConfig->MinSpeedForRotation); + // ════════════════════════════════════════════════════════════════════ + // Phase 2: Variable Jump Height + // ════════════════════════════════════════════════════════════════════ - if (PhysicsVelocity.SizeSquared2D() > MinSpeedSq) - { - FRotator TargetRot = PhysicsVelocity.ToOrientationRotator(); - TargetRot.Pitch = 0.0f; - TargetRot.Roll = 0.0f; + // If moving up AND button released -> Cut velocity + if (PhysicsVelocity.Z > MovementConfig->MinJumpVelocity && !bIsJumpHeld) + { + PhysicsVelocity.Z = MovementConfig->MinJumpVelocity; + } - PhysicsRotation = FMath::RInterpConstantTo( - PhysicsRotation, - TargetRot, - FixedDeltaTime, - MovementConfig->RotationSpeed - ); - } + // ════════════════════════════════════════════════════════════════════ + // Phase 3: Horizontal Movement (Air Control) + // ════════════════════════════════════════════════════════════════════ - // ════════════════════════════════════════════════════════════════════ - // Phase 3: Gravity - // ════════════════════════════════════════════════════════════════════ + const float CurrentZ = PhysicsVelocity.Z; + FVector HorizontalVelocity(PhysicsVelocity.X, PhysicsVelocity.Y, 0.f); - if (!bIsGrounded) - { - PhysicsVelocity.Z -= MovementConfig->Gravity * FixedDeltaTime; - } - else - { - PhysicsVelocity.Z = 0.0f; - } + // Select Acceleration/Friction based on state + const float CurrentAccel = bIsGrounded + ? MovementConfig->Acceleration + : (MovementConfig->Acceleration * MovementConfig->AirControl); - // ════════════════════════════════════════════════════════════════════ - // Phase 4: Collision Resolution - // ════════════════════════════════════════════════════════════════════ + const float CurrentFriction = bIsGrounded + ? MovementConfig->Friction + : MovementConfig->AirFriction; - const FVector DesiredDelta = PhysicsVelocity * FixedDeltaTime; + if (!InputVector.IsNearlyZero()) + { + const FVector TargetVelocity = InputVector * MovementConfig->MaxSpeed; + HorizontalVelocity = FMath::VInterpTo( + HorizontalVelocity, + TargetVelocity, + FixedDeltaTime, + CurrentAccel // <-- Uses Air Control if flying + ); + } + else + { + HorizontalVelocity = FMath::VInterpTo( + HorizontalVelocity, + FVector::ZeroVector, + FixedDeltaTime, + CurrentFriction // <-- Usually 0 in air + ); + } - const FTengriSweepResult MoveResult = UTengriCollisionResolver::ResolveMovement( - this, - PhysicsLocation, - DesiredDelta, - OwnerCapsule, - CachedThresholds, - MovementConfig->MaxStepHeight, - MovementConfig->MaxSlideIterations, - false - ); + PhysicsVelocity = HorizontalVelocity; + PhysicsVelocity.Z = CurrentZ; - PhysicsLocation = MoveResult.Location; + // ════════════════════════════════════════════════════════════════════ + // Phase 4: Rotation + // ════════════════════════════════════════════════════════════════════ - // ════════════════════════════════════════════════════════════════════ - // Phase 5: Ground Snapping - // ════════════════════════════════════════════════════════════════════ + if (const float MinSpeedSq = FMath::Square(MovementConfig->MinSpeedForRotation); + PhysicsVelocity.SizeSquared2D() > MinSpeedSq) + { + FRotator TargetRot = PhysicsVelocity.ToOrientationRotator(); + TargetRot.Pitch = 0.0f; + TargetRot.Roll = 0.0f; - FHitResult SnapHit; - const bool bJustSnapped = PerformGroundSnapping(PhysicsLocation, SnapHit); + PhysicsRotation = FMath::RInterpConstantTo( + PhysicsRotation, + TargetRot, + FixedDeltaTime, + MovementConfig->RotationSpeed + ); + } - if (bJustSnapped && !InputVector.IsNearlyZero()) - { - // Preserve momentum along slope - PhysicsVelocity = UTengriCollisionResolver::ProjectVelocity( - PhysicsVelocity, - SnapHit.ImpactNormal - ); - } + // ════════════════════════════════════════════════════════════════════ + // Phase 5: Gravity (FIXED - Apply before collision) + // ════════════════════════════════════════════════════════════════════ - // ════════════════════════════════════════════════════════════════════ - // Phase 6: State Update - // ════════════════════════════════════════════════════════════════════ + if (!bIsGrounded) + { + float CurrentGravity = MovementConfig->Gravity; - // Determine grounded state from snap or collision - const bool bHitWalkable = MoveResult.bBlocked && - CachedThresholds.IsWalkable(MoveResult.Hit.ImpactNormal.Z); + // Apply extra gravity if falling (makes jump snappy) + if (PhysicsVelocity.Z < 0.0f) + { + CurrentGravity *= MovementConfig->FallingGravityScale; + } - bIsGrounded = bJustSnapped || bHitWalkable; + PhysicsVelocity.Z -= CurrentGravity * FixedDeltaTime; - // Prevent Z velocity accumulation when grounded - if (bIsGrounded && PhysicsVelocity.Z < 0.f) - { - PhysicsVelocity.Z = 0.f; - } + // Clamp to terminal velocity + if (PhysicsVelocity.Z < -MovementConfig->TerminalVelocity) + { + PhysicsVelocity.Z = -MovementConfig->TerminalVelocity; + } + } + + // ════════════════════════════════════════════════════════════════════ + // Phase 6: Collision Resolution + // ════════════════════════════════════════════════════════════════════ + + const FVector DesiredDelta = PhysicsVelocity * FixedDeltaTime; + + const FTengriSweepResult MoveResult = UTengriCollisionResolver::ResolveMovement( + this, + PhysicsLocation, + DesiredDelta, + OwnerCapsule, + CachedThresholds, + MovementConfig->MaxStepHeight, + MovementConfig->MaxSlideIterations, + false + ); + + PhysicsLocation = MoveResult.Location; + + // ════════════════════════════════════════════════════════════════════ + // Phase 7: Ground Snapping + // ════════════════════════════════════════════════════════════════════ + + bool bJustSnapped = false; + + // Only snap if we are NOT jumping upwards + if (PhysicsVelocity.Z <= 0.0f) + { + FHitResult SnapHit; + bJustSnapped = PerformGroundSnapping(PhysicsLocation, SnapHit); + + // Project velocity onto slope if snapped + if (bJustSnapped && !InputVector.IsNearlyZero()) + { + PhysicsVelocity = UTengriCollisionResolver::ProjectVelocity( + PhysicsVelocity, + SnapHit.ImpactNormal + ); + } + } + + // ════════════════════════════════════════════════════════════════════ + // Phase 8: State Update (IMPROVED) + // ════════════════════════════════════════════════════════════════════ + + const bool bWasGrounded = bIsGrounded; + + // We are grounded if: + // 1. We snapped to ground, OR + // 2. We hit a walkable surface during movement + const bool bHitWalkable = MoveResult.bBlocked && + CachedThresholds.IsWalkable(MoveResult.Hit.ImpactNormal.Z); + + const bool bNowGrounded = bJustSnapped || bHitWalkable; + + // ════════════════════════════════════════════════════════════════════ + // Phase 9: Landing Detection (ACCUMULATED) + // ════════════════════════════════════════════════════════════════════ + + if (!bWasGrounded && bNowGrounded) + { + // Store landing velocity BEFORE we zero it + const float LandingVelocityZ = PhysicsVelocity.Z; + const bool bIsHeavy = LandingVelocityZ < MovementConfig->HeavyLandVelocityThreshold; + + // Accumulate event instead of broadcasting immediately + FPendingLandingEvent LandingEvent; + LandingEvent.bIsHeavy = bIsHeavy; + LandingEvent.LandingVelocityZ = LandingVelocityZ; + OutPendingLandings.Add(LandingEvent); + } + + // Update grounded state + bIsGrounded = bNowGrounded; + + // Reset Z velocity if we landed or are on ground + if (bIsGrounded && PhysicsVelocity.Z < 0.f) + { + PhysicsVelocity.Z = 0.f; + } } // ============================================================================ diff --git a/Source/TengriPlatformer/Movement/TengriMovementComponent.h b/Source/TengriPlatformer/Movement/TengriMovementComponent.h index 9fcf7cf..0ae272a 100644 --- a/Source/TengriPlatformer/Movement/TengriMovementComponent.h +++ b/Source/TengriPlatformer/Movement/TengriMovementComponent.h @@ -1,3 +1,5 @@ +// Request Games © All rights reserved + // Source/TengriPlatformer/Movement/TengriMovementComponent.h #pragma once @@ -8,6 +10,9 @@ #include "TengriMovementComponent.generated.h" class UCapsuleComponent; +struct FPendingLandingEvent; // Forward declaration + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTengriLandingSignature, bool, bIsHeavy); /** * Custom movement component for deterministic 3D platformer physics. @@ -17,6 +22,7 @@ class UCapsuleComponent; * - Physics State: Updated at fixed rate (default 120Hz) for determinism * - Render State: Interpolated between physics states for smooth visuals * - Accumulator: Manages variable frame delta accumulation + * - Event Accumulation: Events during physics loop are broadcast after completion */ UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) class TENGRIPLATFORMER_API UTengriMovementComponent : public UActorComponent @@ -26,6 +32,10 @@ class TENGRIPLATFORMER_API UTengriMovementComponent : public UActorComponent public: UTengriMovementComponent(); + /** Event triggered when character lands (broadcast after physics loop completes) */ + UPROPERTY(BlueprintAssignable, Category = "Tengri Movement|Events") + FOnTengriLandingSignature OnLanded; + protected: virtual void BeginPlay() override; @@ -44,6 +54,13 @@ public: UFUNCTION(BlueprintCallable, Category = "Tengri Movement") void SetInputVector(FVector NewInput); + /** + * Updates jump input state. Call from PlayerController. + * @param bPressed - True if button just pressed or currently held + */ + UFUNCTION(BlueprintCallable, Category = "Tengri Movement") + void SetJumpInput(bool bPressed); + // ======================================================================== // CONFIGURATION // ======================================================================== @@ -125,6 +142,22 @@ private: /** Normalized input vector (Z always 0) */ FVector InputVector = FVector::ZeroVector; + // ======================================================================== + // JUMP STATE (Internal) + // ======================================================================== + + /** Timer for Jump Buffering. > 0 means jump was recently pressed. */ + float JumpBufferTimer = 0.0f; + + /** Timer for Coyote Time. > 0 means we can still jump even if in air. */ + float CoyoteTimer = 0.0f; + + /** True if jump button is currently held down (for variable jump height) */ + bool bIsJumpHeld = false; + + /** Flag to prevent Coyote Time reactivation immediately after jumping */ + bool bHasJumpedThisFrame = false; + // ======================================================================== // INITIALIZATION // ======================================================================== @@ -132,15 +165,16 @@ private: void InitializeSystem(); // ======================================================================== - // PHYSICS TICK + // PHYSICS TICK (UPDATED) // ======================================================================== /** * Deterministic physics update at fixed timestep. * All movement logic runs here with constant delta time. * @param FixedDeltaTime - Fixed timestep duration (e.g., 1/120 sec) + * @param OutPendingLandings - Accumulator for landing events (broadcast later) */ - void TickPhysics(float FixedDeltaTime); + void TickPhysics(float FixedDeltaTime, TArray& OutPendingLandings); /** Save current physics state before next physics step */ void SavePreviousPhysicsState(); diff --git a/Source/TengriPlatformer/TengriPlatformer.Build.cs b/Source/TengriPlatformer/TengriPlatformer.Build.cs index c5917b9..4b21d42 100644 --- a/Source/TengriPlatformer/TengriPlatformer.Build.cs +++ b/Source/TengriPlatformer/TengriPlatformer.Build.cs @@ -1,4 +1,4 @@ -// Fill out your copyright notice in the Description page of Project Settings. +// Request Games © All rights reserved using UnrealBuildTool; diff --git a/Source/TengriPlatformer/TengriPlatformer.cpp b/Source/TengriPlatformer/TengriPlatformer.cpp index fb7be69..0f99f9d 100644 --- a/Source/TengriPlatformer/TengriPlatformer.cpp +++ b/Source/TengriPlatformer/TengriPlatformer.cpp @@ -1,4 +1,4 @@ -// Fill out your copyright notice in the Description page of Project Settings. +// Request Games © All rights reserved #include "TengriPlatformer.h" #include "Modules/ModuleManager.h" diff --git a/Source/TengriPlatformer/TengriPlatformer.h b/Source/TengriPlatformer/TengriPlatformer.h index 90aad9e..7ff2054 100644 --- a/Source/TengriPlatformer/TengriPlatformer.h +++ b/Source/TengriPlatformer/TengriPlatformer.h @@ -1,4 +1,4 @@ -// Fill out your copyright notice in the Description page of Project Settings. +// Request Games © All rights reserved #pragma once