feat(movement): implement jump system and air physics (Phases 13-14)
Implemented a responsive, deterministic jump system with "game feel" enhancements and advanced air physics. Changes: - **Jump Logic**: Added variable jump height (hold/release control). - **Game Feel**: Implemented Coyote Time (jump after leaving ledge) and Jump Buffering (early input registration). - **Air Physics**: Added non-linear gravity (FallingGravityScale) for snappy jumps and Terminal Velocity clamping. - **Landing**: Added OnLanded delegate with heavy/light landing detection based on impact velocity. - **Config**: Added auto-calculation logic in PostEditChangeProperty to derive Gravity and JumpVelocity from desired JumpHeight and TimeToApex. - **Debug**: Added on-screen debug messages for Velocity Z and movement state. - **Fix**: Moved delegate declaration to global scope to fix blueprint visibility issues. Relates to: Phase 13, Phase 14main
parent
b83388e74e
commit
c54c9fd6ae
BIN
Content/Blueprints/BP_MainCharacter.uasset (Stored with Git LFS)
BIN
Content/Blueprints/BP_MainCharacter.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -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'));
|
||||
Binary file not shown.
|
|
@ -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'));
|
||||
|
|
|
|||
BIN
Content/Input/IMC_Default.uasset (Stored with Git LFS)
BIN
Content/Input/IMC_Default.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/Movement/DA_TengriMovementConfig.uasset (Stored with Git LFS)
BIN
Content/Movement/DA_TengriMovementConfig.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -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. **Итерации** — после каждого этапа можно показать прогресс
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
// Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.h
|
||||
// Request Games © All rights reserved
|
||||
|
||||
// Source/TengriPlatformer/Movement/Collision/TengriCollisionResolver.h
|
||||
|
||||
#pragma once
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
// Source/TengriPlatformer/Movement/Collision/TengriSweepResult.h
|
||||
// Request Games © All rights reserved
|
||||
|
||||
// Source/TengriPlatformer/Movement/Collision/TengriSweepResult.h
|
||||
|
||||
#pragma once
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
// ========================================================================
|
||||
|
|
|
|||
|
|
@ -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<FPendingLandingEvent> 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<FPendingLandingEvent>& 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;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -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<FPendingLandingEvent>& OutPendingLandings);
|
||||
|
||||
/** Save current physics state before next physics step */
|
||||
void SavePreviousPhysicsState();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
// Request Games © All rights reserved
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
// Request Games © All rights reserved
|
||||
|
||||
#pragma once
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue