Working on implementing server side physics on gam

This commit is contained in:
Vladislav Khorev 2026-01-16 00:01:44 +03:00
parent 1348efb5fe
commit e0453db063
8 changed files with 42 additions and 69 deletions

View File

@ -64,7 +64,7 @@ class Session : public std::enable_shared_from_this<Session> {
if (dt_server > 0) state_.simulate_physics(dt_server); if (dt_server > 0) state_.simulate_physics(dt_server);
using time_point = std::chrono::steady_clock::time_point;
std::chrono::steady_clock::time_point uptime_timepoint{ std::chrono::duration_cast<std::chrono::steady_clock::time_point::duration>(std::chrono::milliseconds(clientTimestamp)) }; 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; state_.lastUpdateServerTime = uptime_timepoint;

View File

@ -568,7 +568,7 @@ namespace ZL
sparkEmitter.update(static_cast<float>(delta)); sparkEmitter.update(static_cast<float>(delta));
planetObject.update(static_cast<float>(delta)); planetObject.update(static_cast<float>(delta));
updateRemotePlayers(static_cast<float>(delta)); extrapolateRemotePlayers(static_cast<float>(delta));
static float pingTimer = 0.0f; static float pingTimer = 0.0f;
pingTimer += delta; pingTimer += delta;
@ -1052,21 +1052,30 @@ namespace ZL
} }
} }
void Game::updateRemotePlayers(float deltaMs) { void Game::extrapolateRemotePlayers(float deltaMs) {
auto now = std::chrono::steady_clock::now();
latestRemotePlayers = networkClient->getRemotePlayers(); latestRemotePlayers = networkClient->getRemotePlayers();
for (auto& [id, rp] : latestRemotePlayers) { for (auto& [id, rp] : latestRemotePlayers) {
// Увеличиваем фактор интерполяции (базируется на частоте Snapshot = 1000мс) // 1. Рассчитываем, сколько времени прошло с момента получения последнего пакета (в секундах)
rp.interpolationFactor += deltaMs / 1000.0f; float age_s = std::chrono::duration<float>(now - rp.lastUpdateServerTime).count();
if (rp.interpolationFactor > 1.0f) rp.interpolationFactor = 1.0f;
// Линейная интерполяция позиции // Ограничим экстраполяцию (например, не более 2 секунд),
rp.state.position = rp.startPosition + (rp.targetPosition - rp.startPosition) * rp.interpolationFactor; // чтобы в случае лага корабли не улетали в бесконечность
if (age_s > 2.0f) age_s = 2.0f;
// Сферическая интерполяция вращения (Slerp) // 2. Сбрасываем физическое состояние rp.state в значения из последнего пакета
Eigen::Quaternionf currentQ = rp.startRotation.slerp(rp.interpolationFactor, rp.targetRotation); // (Это важно: мы всегда экстраполируем от последнего достоверного серверного состояния)
rp.state.rotation = currentQ.toRotationMatrix(); // В WebSocketClient::updateRemotePlayer нужно убедиться, что rp.state обновляется данными из пакета.
// 3. Вызываем физику, чтобы "догнать" реальное время
// Мы передаем age_s как один большой шаг симуляции
rp.simulate_physics(age_s);
// Теперь rp.state.position и rp.state.rotation содержат актуальные
// предсказанные данные для рендеринга в текущем кадре.
} }
} }

View File

@ -51,7 +51,7 @@ namespace ZL {
void handleUp(int mx, int my); void handleUp(int mx, int my);
void handleMotion(int mx, int my); void handleMotion(int mx, int my);
void updateRemotePlayers(float deltaMs); void extrapolateRemotePlayers(float deltaMs);
SDL_Window* window; SDL_Window* window;
SDL_GLContext glContext; SDL_GLContext glContext;
@ -64,7 +64,7 @@ namespace ZL {
std::vector<BoxCoords> boxCoordsArr; std::vector<BoxCoords> boxCoordsArr;
std::vector<VertexRenderStruct> boxRenderArr; std::vector<VertexRenderStruct> boxRenderArr;
std::unordered_map<int, RemotePlayer> latestRemotePlayers; std::unordered_map<int, ClientState> latestRemotePlayers;
static const size_t CONST_TIMER_INTERVAL = 10; static const size_t CONST_TIMER_INTERVAL = 10;
static const size_t CONST_MAX_TIME_INTERVAL = 1000; static const size_t CONST_MAX_TIME_INTERVAL = 1000;

View File

@ -20,7 +20,6 @@ struct ClientState {
int discreteAngle = -1; int discreteAngle = -1;
// Для расчета лага // Для расчета лага
uint64_t lastClientTimestamp = 0;
std::chrono::steady_clock::time_point lastUpdateServerTime; std::chrono::steady_clock::time_point lastUpdateServerTime;
void simulate_physics(float dt_s) { void simulate_physics(float dt_s) {
@ -103,14 +102,14 @@ struct ClientState {
).count(); ).count();
// 2. Вычисляем задержку // 2. Вычисляем задержку
double lag_ms = 0.0; float lag_ms = 0.0;
if (nowTime > lastUpdateServerTime) { if (nowTime > lastUpdateServerTime) {
lag_ms = std::chrono::duration<float>(nowTime - lastUpdateServerTime).count(); lag_ms = std::chrono::duration<float>(nowTime - lastUpdateServerTime).count();
} }
// 3. Защита от слишком больших скачков (Clamp) // 3. Защита от слишком больших скачков (Clamp)
// Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков // Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков
float final_lag_s = min(static_cast<float>(lag_ms), 0.5f); float final_lag_s = min(lag_ms, 0.5f);
if (final_lag_s > 0.001f) { if (final_lag_s > 0.001f) {
// Доматываем симуляцию на величину задержки // Доматываем симуляцию на величину задержки

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include "RemotePlayer.h" #include "ClientState.h"
// NetworkInterface.h - Èíòåðôåéñ äëÿ ðàçíûõ òèïîâ ñîåäèíåíèé // NetworkInterface.h - Èíòåðôåéñ äëÿ ðàçíûõ òèïîâ ñîåäèíåíèé
namespace ZL { namespace ZL {
@ -12,6 +12,6 @@ namespace ZL {
virtual void Send(const std::string& message) = 0; virtual void Send(const std::string& message) = 0;
virtual bool IsConnected() const = 0; virtual bool IsConnected() const = 0;
virtual void Poll() = 0; // Äëÿ îáðàáîòêè âõîäÿùèõ ïàêåòîâ virtual void Poll() = 0; // Äëÿ îáðàáîòêè âõîäÿùèõ ïàêåòîâ
virtual std::unordered_map<int, RemotePlayer> getRemotePlayers() = 0; virtual std::unordered_map<int, ClientState> getRemotePlayers() = 0;
}; };
} }

View File

@ -1,15 +1,2 @@
#pragma once #pragma once
#include "ClientState.h" #include "ClientState.h"
struct RemotePlayer {
ClientState state;
// Данные для интерполяции
Eigen::Vector3f startPosition;
Eigen::Vector3f targetPosition;
Eigen::Quaternionf startRotation;
Eigen::Quaternionf targetRotation;
float interpolationFactor = 0.0f;
uint64_t lastSnapshotTime = 0;
};

View File

@ -96,52 +96,30 @@ namespace ZL {
if (id == this->clientId) continue; // Пропускаем себя if (id == this->clientId) continue; // Пропускаем себя
// Логика обновления или создания RemotePlayer // Логика обновления или создания RemotePlayer
updateRemotePlayer(id, vals); updateRemotePlayer(id, vals, serverTime);
} }
} }
void WebSocketClient::updateRemotePlayer(int id, const std::vector<std::string>& vals) { void WebSocketClient::updateRemotePlayer(int id, const std::vector<std::string>& vals, uint64_t serverTime) {
// Используем мьютекс, так как этот метод вызывается из сетевого потока (TaskManager) // Используем мьютекс, так как этот метод вызывается из сетевого потока (TaskManager)
std::lock_guard<std::mutex> lock(playersMutex); std::lock_guard<std::mutex> lock(playersMutex);
auto& rp = remotePlayers[id]; auto& rp = remotePlayers[id];
rp.state.id = id; rp.id = id;
// 1. Ñîõðàíÿåì òåêóùèå âèçóàëüíûå êîîðäèíàòû êàê íà÷àëüíóþ òî÷êó äëÿ íîâîé èíòåðïîëÿöèè rp.position = { std::stof(vals[1]), std::stof(vals[2]), std::stof(vals[3]) };
// Ýòî ïðåäîòâðàòèò "òåëåïîðòàöèþ", åñëè ïðåäûäóùàÿ èíòåðïîëÿöèÿ íå óñïåëà äîéòè äî 1.0 rp.rotation = Eigen::Quaternionf(std::stof(vals[4]), std::stof(vals[5]), std::stof(vals[6]), std::stof(vals[7]));
if (rp.lastSnapshotTime == 0) {
// Ïåðâûé ïàêåò äëÿ ýòîãî èãðîêà
rp.startPosition = { std::stof(vals[1]), std::stof(vals[2]), std::stof(vals[3]) };
rp.startRotation = Eigen::Quaternionf(std::stof(vals[4]), std::stof(vals[5]), std::stof(vals[6]), std::stof(vals[7]));
}
else {
// Âû÷èñëÿåì òåêóùåå ïîëîæåíèå, íà êîòîðîì îñòàíîâèëèñü, ÷òîáû íà÷àòü îòòóäà
rp.startPosition = rp.state.position;
rp.startRotation = Eigen::Quaternionf(rp.state.rotation);
}
// 2. Óñòàíàâëèâàåì íîâûå öåëåâûå çíà÷åíèÿ îò ñåðâåðà
rp.targetPosition = { std::stof(vals[1]), std::stof(vals[2]), std::stof(vals[3]) };
rp.targetRotation = Eigen::Quaternionf(
std::stof(vals[4]), // w
std::stof(vals[5]), // x
std::stof(vals[6]), // y
std::stof(vals[7]) // z
);
// 3. Сохраняем остальные физические параметры (для экстраполяции или отладки) // 3. Сохраняем остальные физические параметры (для экстраполяции или отладки)
rp.state.velocity = std::stof(vals[8]); rp.velocity = std::stof(vals[8]);
rp.state.currentAngularVelocity = { std::stof(vals[9]), std::stof(vals[10]), std::stof(vals[11]) }; rp.currentAngularVelocity = { std::stof(vals[9]), std::stof(vals[10]), std::stof(vals[11]) };
rp.state.selectedVelocity = std::stoi(vals[12]); rp.selectedVelocity = std::stoi(vals[12]);
rp.state.discreteMag = std::stoi(vals[13]); rp.discreteMag = std::stoi(vals[13]);
rp.state.discreteAngle = std::stoi(vals[14]); rp.discreteAngle = std::stoi(vals[14]);
std::chrono::steady_clock::time_point uptime_timepoint{ std::chrono::duration_cast<std::chrono::steady_clock::time_point::duration>(std::chrono::milliseconds(serverTime)) };
rp.lastUpdateServerTime = uptime_timepoint;
// 4. Ñáðàñûâàåì òàéìåð èíòåðïîëÿöèè
rp.interpolationFactor = 0.0f;
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()
).count();
rp.lastSnapshotTime = now_ms;
} }

View File

@ -28,7 +28,7 @@ namespace ZL {
bool connected = false; bool connected = false;
int clientId = -1; int clientId = -1;
std::unordered_map<int, RemotePlayer> remotePlayers; std::unordered_map<int, ClientState> remotePlayers;
std::mutex playersMutex; std::mutex playersMutex;
void startAsyncRead(); void startAsyncRead();
@ -41,7 +41,7 @@ namespace ZL {
void Poll() override; void Poll() override;
void parseWorldUpdate(const std::string& msg); void parseWorldUpdate(const std::string& msg);
void updateRemotePlayer(int id, const std::vector<std::string>& vals); void updateRemotePlayer(int id, const std::vector<std::string>& vals, uint64_t serverTime);
void Send(const std::string& message) override; void Send(const std::string& message) override;
void doWrite(); void doWrite();
@ -49,7 +49,7 @@ namespace ZL {
bool IsConnected() const override { return connected; } bool IsConnected() const override { return connected; }
int GetClientId() const { return clientId; } int GetClientId() const { return clientId; }
std::unordered_map<int, RemotePlayer> getRemotePlayers() override { std::unordered_map<int, ClientState> getRemotePlayers() override {
std::lock_guard<std::mutex> lock(playersMutex); std::lock_guard<std::mutex> lock(playersMutex);
return remotePlayers; return remotePlayers;
} }