docs: изучен CGameMovement из Source SDK 2013 — расхождения с нашей физикой
Доки Valve закрыты Anubis, читал первоисточник ValveSoftware/source-sdk-2013, src/game/shared/gamemovement.cpp. Записаны точные причины двух симптомов пользователя и ещё трёх расхождений: 1. StepMove у оригинала пробует оба варианта (вниз и вверх на flStepSize) и берёт тот, что ушёл дальше по горизонтали; вертикальную скорость берёт из движения ВНИЗ (m_vecVelocity.z = vecDownVel.z). У нас подшаг всегда идёт путём «поднять 18 и уронить», режет путь на рампе и добавляет паразитную вертикаль — отсюда и потеря скорости вверх, и «прыжки с рампы» вниз. 2. Friction() вызывается каждый кадр на земле безусловно, формула control = max(speed, sv_stopspeed), drop = control*friction*frametime. У нас трение только при отсутствии ввода — по рампе на бегу его нет. 3. Гравитация делится 0.5 до и 0.5 после движения (StartGravity/FinishGravity). 4. Ускорение — Quake-формула accelspeed = accel*frametime*wishspeed, а в воздухе ещё и ограничение sv_air_max_wishspeed = 30. Отсюда «вязкое» управление в воздухе, которого у нас нет. 5. Прыжок по фронту: в оригинале при выходе из воды ставится m_nOldButtons |= IN_JUMP с комментарием «don't jump again until released» — наша защёлка воспроизводит штатный приём движка. Код не менял: сначала фиксирую расхождения, чтобы не подгонять тесты.
This commit is contained in:
parent
2e906e034b
commit
570f4bb9d0
1 changed files with 111 additions and 0 deletions
111
docs/PARITY.md
111
docs/PARITY.md
|
|
@ -783,3 +783,114 @@ Source делает наоборот: при `FL_ONGROUND` вертикальн
|
|||
в Source движение просто останавливается.
|
||||
- `sv_maxspeed = 320`, `sv_maxvelocity = 350` как отдельные ограничения.
|
||||
- Ускорение/торможение поворота камеры, инерция при стрейфе.
|
||||
|
||||
## Часть B6. Изучение `CGameMovement` (Source SDK 2013)
|
||||
|
||||
Источник: `src/game/shared/gamemovement.cpp` из `ValveSoftware/source-sdk-2013`
|
||||
(доки Valve под Anubis, читался исходник). Ниже — расхождения нашей реализации
|
||||
с оригиналом, найденные **чтением первоисточника**, а не догадками.
|
||||
|
||||
### B6.1. Почему теряется скорость на рампе вверх
|
||||
|
||||
`CGameMovement::StepMove` устроен принципиально иначе, чем наш подшаг:
|
||||
|
||||
```cpp
|
||||
// Try sliding forward both on ground and up 16 pixels
|
||||
// take the move that goes farthest
|
||||
TryPlayerMove( &vecEndPos, &trace ); // сначала ВНИЗ — получаем vecDownPos/vecDownVel
|
||||
...подъём на flStepSize...
|
||||
TryPlayerMove(); // потом ВВЕРХ — получаем vecUpPos
|
||||
// decide which one went farther
|
||||
float flDownDist = ( vecDownPos.x - vecPos.x )^2 + ( vecDownPos.y - vecPos.y )^2;
|
||||
float flUpDist = ( vecUpPos.x - vecPos.x )^2 + ( vecUpPos.y - vecPos.y )^2;
|
||||
if ( flDownDist > flUpDist ) { едем вниз; }
|
||||
else { /* copy z value from slide move */ mv->m_vecVelocity.z = vecDownVel.z; }
|
||||
```
|
||||
|
||||
Три отличия от нашего кода, каждое даёт симптом:
|
||||
|
||||
1. **Пробуются оба варианта, побеждает тот, что ушёл дальше по горизонтали.**
|
||||
Мы всегда идём путём «поднять на 18 и уронить». На рампе вверх путь вниз
|
||||
(без подшага) уходит дальше — значит оригинал его и берёт, а подшаг не
|
||||
выполняется вовсе. У нас он выполняется всегда и режет путь.
|
||||
2. **Вертикальная скорость берётся из движения ВНИЗ**
|
||||
(`m_vecVelocity.z = vecDownVel.z`), а не из движения вверх. Иначе на
|
||||
рампе появляется паразитная вертикальная составляющая.
|
||||
3. Отказ от подшага при `trace.plane.normal[2] < 0.7` — то есть на крутых
|
||||
поверхностях подшаг не применяется вовсе.
|
||||
|
||||
### B6.2. Почему при спуске «прыгает с рампы»
|
||||
|
||||
Наш подшаг выполняется по признаку «движение заблокировано» и всегда с
|
||||
подъёмом на `STEP_HEIGHT = 18`. На рампе вниз игрок кадр за кадром
|
||||
подскакивает на 18 юнитов и падает обратно — визуально это «прыжки с
|
||||
рампы». Оригинал при спуске идёт по пути `flDownDist`, который дальше, и
|
||||
подшага не делает.
|
||||
|
||||
### B6.3. Трение: у нас не так
|
||||
|
||||
`CGameMovement::Friction()` вызывается **каждый кадр на земле** безусловно
|
||||
(проверка только `GetGroundEntity() != NULL`), а не «если нет ввода».
|
||||
Формула оригинала:
|
||||
|
||||
```cpp
|
||||
control = (speed < sv_stopspeed) ? sv_stopspeed : speed; // 100 по умолчанию
|
||||
drop += control * friction * frametime; // sv_friction = 4
|
||||
newspeed = max(0, speed - drop);
|
||||
```
|
||||
|
||||
У нас трение срабатывает только при `wish.length_squared() < 1.0`, то есть
|
||||
при беге по рампе его нет вовсе.
|
||||
|
||||
### B6.4. Гравитация делится пополам до и после движения
|
||||
|
||||
```cpp
|
||||
void CGameMovement::StartGravity( void ) {
|
||||
mv->m_vecVelocity[2] -= ent_gravity * GetCurrentGravity() * 0.5 * frametime;
|
||||
}
|
||||
void CGameMovement::FinishGravity( void ) {
|
||||
mv->m_vecVelocity[2] -= ent_gravity * GetCurrentGravity() * frametime * 0.5;
|
||||
}
|
||||
```
|
||||
|
||||
В комментарии Valve: «yes, this 0.5 looks wrong, but it's not». Суммарно
|
||||
гравитация та же, но момент приложения другой — это и даёт правильное
|
||||
ощущение веса на дуге.
|
||||
|
||||
### B6.5. Ускорение: Quake-алгоритм, а не линейная интерполяция
|
||||
|
||||
```cpp
|
||||
currentspeed = velocity.Dot(wishdir);
|
||||
addspeed = wishspeed - currentspeed;
|
||||
if (addspeed <= 0) return;
|
||||
accelspeed = accel * frametime * wishspeed * friction; // НЕ accel*dt!
|
||||
if (accelspeed > addspeed) accelspeed = addspeed;
|
||||
velocity += accelspeed * wishdir;
|
||||
```
|
||||
|
||||
`AirAccelerate` отличается одной строкой — ограничением желаемой скорости:
|
||||
|
||||
```cpp
|
||||
if ( wishspd > GetAirSpeedCap() ) wishspd = GetAirSpeedCap(); // sv_air_max_wishspeed = 30
|
||||
```
|
||||
|
||||
Вот почему в воздухе управление «вязкое»: прирост скорости за кадр
|
||||
ограничен, а ускорение не накапливается. У нас `AIR_ACCEL = 12` при
|
||||
`GROUND_ACCEL = 10` и линейная интерполяция к целевой скорости — в воздухе
|
||||
у нас **быстрее**, чем на земле.
|
||||
|
||||
### B6.6. Прыжок по фронту
|
||||
|
||||
В чистом Source `IN_JUMP` проверяется по уровню, но при выходе из воды
|
||||
ставится `mv->m_nOldButtons |= IN_JUMP; // Don't jump again until released`.
|
||||
То есть «не прыгать, пока не отпустят» — штатный приём движка, а не
|
||||
изобретение: наша защёлка `jump_latched` воспроизводит именно его.
|
||||
|
||||
### B6.7. Порядок работы на будущее
|
||||
|
||||
1. `StepMove` по оригиналу: оба варианта, побеждает дальний, `z` из
|
||||
вниз. Чинит и потерю скорости, и «прыжки с рампы».
|
||||
2. `Friction()` безусловно на земле.
|
||||
3. `StartGravity` / `FinishGravity` пополам.
|
||||
4. `Accelerate` / `AirAccelerate` квото-формулой с `sv_air_max_wishspeed`.
|
||||
5. `CheckVelocity` — ограничиватель `sv_maxvelocity = 350`.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue