Fixing bugs, unify velocity

This commit is contained in:
Vladislav Khorev 2026-01-17 12:43:41 +03:00
parent fbc420e894
commit f967807296
7 changed files with 153 additions and 54 deletions

View File

@ -57,13 +57,13 @@ class Session : public std::enable_shared_from_this<Session> {
uint64_t clientTimestamp = std::stoull(parts[1]);
// Ñíà÷àëà ïðîãîíÿåì ñòàíäàðòíóþ ñèìóëÿöèþ "äî òåêóùåãî ìîìåíòà ñåðâåðà"
float dt_server = 0.0f;
long long deltaMs = 0.0f;
if (state_.lastUpdateServerTime.time_since_epoch().count() > 0) {
dt_server = (clientTimestamp - now_ms) * 1000.f;
deltaMs = (clientTimestamp - now_ms);
}
if (dt_server > 0) state_.simulate_physics(dt_server);
if (deltaMs > 0) state_.simulate_physics(deltaMs);
std::chrono::steady_clock::time_point uptime_timepoint{ std::chrono::duration_cast<std::chrono::steady_clock::time_point::duration>(std::chrono::milliseconds(clientTimestamp)) };
state_.lastUpdateServerTime = uptime_timepoint;
@ -74,16 +74,19 @@ class Session : public std::enable_shared_from_this<Session> {
state_.discreteAngle = std::stoi(parts[2]);
state_.discreteMag = std::stof(parts[3]);
state_.apply_lag_compensation(now_server);
state_.lastUpdateServerTime = now_server;
retranslateMessage(msg);
}
else if (parts[0] == "VEL") {
state_.selectedVelocity = std::stoi(parts[2]);
state_.apply_lag_compensation(now_server);
state_.lastUpdateServerTime = now_server;
retranslateMessage(msg);
}
else if (parts[0] == "PING") {
state_.handle_full_sync(parts);
state_.apply_lag_compensation(now_server);
state_.lastUpdateServerTime = now_server;
}
}
@ -131,23 +134,21 @@ public:
}
void tick_physics_global(std::chrono::steady_clock::time_point now) {
float dt = 0.0f;
long long deltaMs = 0;
// Åñëè ýòî ñàìûé ïåðâûé òèê, ïðîñòî çàïîìèíàåì âðåìÿ
if (state_.lastUpdateServerTime.time_since_epoch().count() > 0) {
dt = std::chrono::duration<float>(now - state_.lastUpdateServerTime).count();
deltaMs = std::chrono::duration_cast<std::chrono::milliseconds>(
now - state_.lastUpdateServerTime
).count();
}
if (dt > 0.0001f) {
state_.simulate_physics(dt);
if (deltaMs > 0) {
state_.simulate_physics(deltaMs);
state_.lastUpdateServerTime = now; // Îáíîâëÿåì âðåìÿ ïîñëå ñèìóëÿöèè
}
}
void tick_physics(float dt_s) {
state_.simulate_physics(dt_s);
}
void send_message(std::string msg) {
auto ss = std::make_shared<std::string>(std::move(msg));
ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {});

View File

@ -627,7 +627,7 @@ namespace ZL
sparkEmitter.update(static_cast<float>(delta));
planetObject.update(static_cast<float>(delta));
extrapolateRemotePlayers(static_cast<float>(delta));
extrapolateRemotePlayers();
static float pingTimer = 0.0f;
pingTimer += delta;
@ -659,10 +659,11 @@ namespace ZL
+ std::to_string(Environment::lastSentAngle);
networkClient->Send(pingMsg);
std::cout << "Sending: " << pingMsg << std::endl;
pingTimer = 0.0f;
}
static const float CONST_ACCELERATION = 1.f;
//static const float CONST_ACCELERATION = 1.f;
float shipDesiredVelocity = Environment::shipSelectedVelocity * 100.f;
@ -670,7 +671,7 @@ namespace ZL
{
if (Environment::shipVelocity < shipDesiredVelocity)
{
Environment::shipVelocity += delta * CONST_ACCELERATION;
Environment::shipVelocity += delta * SHIP_ACCEL;
if (Environment::shipVelocity > shipDesiredVelocity)
{
Environment::shipVelocity = shipDesiredVelocity;
@ -678,7 +679,7 @@ namespace ZL
}
else if (Environment::shipVelocity > shipDesiredVelocity)
{
Environment::shipVelocity -= delta * CONST_ACCELERATION;
Environment::shipVelocity -= delta * SHIP_ACCEL;
if (Environment::shipVelocity < shipDesiredVelocity)
{
Environment::shipVelocity = shipDesiredVelocity;
@ -784,6 +785,7 @@ namespace ZL
networkClient->Send(msg);
}
float currentSpeed = Environment::currentAngularVelocity.norm();
if (currentSpeed > 0.0001f) {
@ -807,6 +809,7 @@ namespace ZL
}
}
std::cout << "shipVelocity=" << Environment::shipVelocity << " delta=" << delta << std::endl;
// Движение вперед (существующая логика)
if (fabs(Environment::shipVelocity) > 0.01f)
{
@ -1097,7 +1100,7 @@ namespace ZL
}
}
void Game::extrapolateRemotePlayers(float deltaMs) {
void Game::extrapolateRemotePlayers() {
auto now = std::chrono::steady_clock::now();
@ -1105,11 +1108,16 @@ namespace ZL
for (auto& [id, rp] : latestRemotePlayers) {
// 1. Рассчитываем, сколько времени прошло с момента получения последнего пакета (в секундах)
float age_s = std::chrono::duration<float>(now - rp.lastUpdateServerTime).count();
auto deltaMs = std::chrono::duration_cast<std::chrono::milliseconds>(
now - rp.lastUpdateServerTime
).count();
//float age_s = std::chrono::duration<float>(now - rp.lastUpdateServerTime).count();
// Ограничим экстраполяцию (например, не более 2 секунд),
// чтобы в случае лага корабли не улетали в бесконечность
if (age_s > 2.0f) age_s = 2.0f;
if (deltaMs > 2000) deltaMs = 2000;
// 2. Сбрасываем физическое состояние rp.state в значения из последнего пакета
// (Это важно: мы всегда экстраполируем от последнего достоверного серверного состояния)
@ -1117,12 +1125,15 @@ namespace ZL
// 3. Вызываем физику, чтобы "догнать" реальное время
// Мы передаем age_s как один большой шаг симуляции
rp.simulate_physics(age_s);
rp.simulate_physics(deltaMs);
rp.lastUpdateServerTime = now;
// Теперь rp.state.position и rp.state.rotation содержат актуальные
// предсказанные данные для рендеринга в текущем кадре.
}
networkClient->updateRemotePlayers(latestRemotePlayers);
}

View File

@ -51,7 +51,7 @@ namespace ZL {
void handleUp(int mx, int my);
void handleMotion(int mx, int my);
void extrapolateRemotePlayers(float deltaMs);
void extrapolateRemotePlayers();
SDL_Window* window;
SDL_GLContext glContext;

View File

@ -3,12 +3,16 @@
#include <Eigen/Dense>
#define _USE_MATH_DEFINES
#include <math.h>
#include <iostream>
using std::min;
using std::max;
constexpr float ANGULAR_ACCEL = 0.005f * 1000.0f;
constexpr float SHIP_ACCEL = 1.0f * 1000.0f;
constexpr float ROTATION_SENSITIVITY = 0.002f;
struct ClientState {
int id;
Eigen::Vector3f position = { 0, 0, 45000.0f };
@ -22,13 +26,70 @@ struct ClientState {
// Для расчета лага
std::chrono::steady_clock::time_point lastUpdateServerTime;
void simulate_physics(float dt_s) {
void simulate_physics(size_t delta) {
// Константы из Game.cpp, приведенные к секундам (умножаем на 1000)
const float ANGULAR_ACCEL = 0.005f * 1000.0f;
const float ROTATION_SENSITIVITY = 0.002f * 1000.0f;
const float SHIP_ACCEL = 1.0f * 1000.0f; // CONST_ACCELERATION
//const float ANGULAR_ACCEL = 0.005f * 1000.0f;
//const float ROTATION_SENSITIVITY = 0.002f;
//const float SHIP_ACCEL = 1.0f * 1000.0f; // CONST_ACCELERATION
// 1. Вычисляем targetAngularVelocity на лету из дискретных значений
if (discreteMag > 0.01f)
{
float rad = static_cast<float>(discreteAngle) * static_cast<float>(M_PI) / 180.0f;
// Целевая угловая скорость (дискретная сила определяет модуль вектора)
// Вектор {cos, sin, 0} дает нам направление отклонения джойстика
Eigen::Vector3f targetAngularVelDir(sinf(rad), cosf(rad), 0.0f);
Eigen::Vector3f targetAngularVelocity = targetAngularVelDir * discreteMag;
Eigen::Vector3f diffVel = targetAngularVelocity - currentAngularVelocity;
float diffLen = diffVel.norm();
if (diffLen > 0.0001f) {
// Вычисляем, на сколько мы можем изменить скорость в этом кадре
float maxChange = ANGULAR_ACCEL * static_cast<float>(delta);
if (diffLen <= maxChange) {
// Если до цели осталось меньше, чем шаг ускорения — просто прыгаем в цель
currentAngularVelocity = targetAngularVelocity;
}
else {
// Линейно двигаемся в сторону целевого вектора
currentAngularVelocity += (diffVel / diffLen) * maxChange;
}
}
}
else
{
float currentSpeed = currentAngularVelocity.norm();
if (currentSpeed > 0.0001f) {
float drop = ANGULAR_ACCEL * static_cast<float>(delta);
if (currentSpeed <= drop) {
currentAngularVelocity = Eigen::Vector3f::Zero();
}
else {
// Уменьшаем модуль вектора, сохраняя направление
currentAngularVelocity -= (currentAngularVelocity / currentSpeed) * drop;
}
}
}
float speedScale = currentAngularVelocity.norm();
if (speedScale > 0.0001f) {
// Коэффициент чувствительности вращения
float deltaAlpha = speedScale * static_cast<float>(delta) * ROTATION_SENSITIVITY;
Eigen::Vector3f axis = currentAngularVelocity.normalized();
Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis));
rotation = rotation * rotateQuat.toRotationMatrix();
}
/*
Eigen::Vector3f targetAngularVelocity = Eigen::Vector3f::Zero();
if (discreteMag > 0.001f) {
@ -61,8 +122,9 @@ struct ClientState {
else {
currentAngularVelocity -= (currentAngularVelocity / currentSpeed) * drop;
}
}
}*/
/*
// 3. Применение вращения к матрице (Интеграция)
float speedScale = currentAngularVelocity.norm();
if (speedScale > 0.0001f) {
@ -71,50 +133,62 @@ struct ClientState {
Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis));
rotation = rotation * rotateQuat.toRotationMatrix();
}
}*/
// 4. Линейное изменение линейной скорости
float shipDesiredVelocity = static_cast<float>(selectedVelocity) * 100.f;
float speedDiff = shipDesiredVelocity - velocity;
if (std::abs(speedDiff) > 0.001f) {
float speedStep = SHIP_ACCEL * dt_s;
if (std::abs(speedDiff) <= speedStep) {
velocity = shipDesiredVelocity;
}
else {
velocity += (speedDiff > 0 ? 1.0f : -1.0f) * speedStep;
}
}
float shipDesiredVelocity = selectedVelocity * 100.f;
// 5. Обновление позиции
if (std::abs(velocity) > 0.01f) {
// Движение вперед по локальной оси Z (в твоем коде это {0, 0, -1})
Eigen::Vector3f localMove(0.0f, 0.0f, -velocity * dt_s);
position += rotation * localMove;
if (velocity < shipDesiredVelocity)
{
velocity += delta * SHIP_ACCEL;
if (velocity > shipDesiredVelocity)
{
velocity = shipDesiredVelocity;
}
}
else if (velocity > shipDesiredVelocity)
{
velocity -= delta * SHIP_ACCEL;
if (velocity < shipDesiredVelocity)
{
velocity = shipDesiredVelocity;
}
}
std::cout << "velocity=" << velocity << " delta=" << delta << std::endl;
if (fabs(velocity) > 0.01f)
{
Eigen::Vector3f velocityDirection = { 0,0, -velocity * delta / 1000.f };
Eigen::Vector3f velocityDirectionAdjusted = rotation * velocityDirection;
position = position + velocityDirectionAdjusted;
}
}
void apply_lag_compensation(std::chrono::steady_clock::time_point nowTime) {
// 1. Получаем текущее время сервера в той же шкале (мс)
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
/*uto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()
).count();
long long deltaMs = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch() - lastUpdateServerTime
).count();*/
// 2. Вычисляем задержку
float lag_ms = 0.0;
long long deltaMs = 0;
if (nowTime > lastUpdateServerTime) {
lag_ms = std::chrono::duration<float>(nowTime - lastUpdateServerTime).count();
deltaMs = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - lastUpdateServerTime).count();
}
// 3. Защита от слишком больших скачков (Clamp)
// Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков
float final_lag_s = min(lag_ms, 0.5f);
long long final_lag_ms = min(deltaMs, 500ll);
if (final_lag_s > 0.001f) {
if (final_lag_ms > 0) {
// Доматываем симуляцию на величину задержки
// Мы предполагаем, что за это время параметры управления не менялись
simulate_physics(final_lag_s);
simulate_physics(final_lag_ms);
}
}

View File

@ -13,5 +13,6 @@ namespace ZL {
virtual bool IsConnected() const = 0;
virtual void Poll() = 0; // Äëÿ îáðàáîòêè âõîäÿùèõ ïàêåòîâ
virtual std::unordered_map<int, ClientState> getRemotePlayers() = 0;
virtual void updateRemotePlayers(const std::unordered_map<int, ClientState>& newRemotePlayers) = 0;
};
}

View File

@ -69,8 +69,10 @@ namespace ZL {
void WebSocketClient::Poll() {
std::lock_guard<std::mutex> lock(queueMutex);
auto nowTime = std::chrono::steady_clock::now();
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()
nowTime.time_since_epoch()
).count();
while (!messageQueue.empty()) {
@ -99,21 +101,22 @@ namespace ZL {
// --- КОМПЕНСАЦИЯ ЛАГА ---
// Вычисляем задержку в секундах
float lag_s = static_cast<float>(now_ms - sentTime) / 1000.0f;
long long lag_s = now_ms - sentTime;
// Защита от отрицательного лага (разница часов) или слишком огромного
lag_s = std::max(0.0f, std::min(lag_s, 1.0f));
lag_s = std::max(0ll, std::min(lag_s, 1000ll));
if (lag_s > 0.001f) {
if (lag_s > 0) {
// "Докручиваем" физику конкретного игрока на величину задержки
// В ClientState.h simulate_physics должен использовать
// актуальные discreteAngle/Mag
rp.simulate_physics(lag_s);
}
// Обновляем метку времени, чтобы обычная экстраполяция в Game.cpp
// знала, что состояние уже актуально на момент now_ms
rp.lastUpdateServerTime = std::chrono::steady_clock::now();
rp.lastUpdateServerTime = nowTime;
}
}
else if (msg.rfind("WORLD_UPDATE|", 0) == 0) {

View File

@ -53,5 +53,14 @@ namespace ZL {
std::lock_guard<std::mutex> lock(playersMutex);
return remotePlayers;
}
void updateRemotePlayers(const std::unordered_map<int, ClientState>& newRemotePlayers) override {
std::lock_guard<std::mutex> lock(playersMutex);
for (const auto& [id, newPlayer] : newRemotePlayers) {
remotePlayers[id] = newPlayer;
}
}
};
}