Working on implementing server side physics on gam
This commit is contained in:
parent
1348efb5fe
commit
e0453db063
@ -64,7 +64,7 @@ class Session : public std::enable_shared_from_this<Session> {
|
||||
|
||||
|
||||
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)) };
|
||||
state_.lastUpdateServerTime = uptime_timepoint;
|
||||
|
||||
|
||||
29
src/Game.cpp
29
src/Game.cpp
@ -568,7 +568,7 @@ namespace ZL
|
||||
|
||||
sparkEmitter.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;
|
||||
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();
|
||||
|
||||
for (auto& [id, rp] : latestRemotePlayers) {
|
||||
// Увеличиваем фактор интерполяции (базируется на частоте Snapshot = 1000мс)
|
||||
rp.interpolationFactor += deltaMs / 1000.0f;
|
||||
if (rp.interpolationFactor > 1.0f) rp.interpolationFactor = 1.0f;
|
||||
// 1. Рассчитываем, сколько времени прошло с момента получения последнего пакета (в секундах)
|
||||
float age_s = std::chrono::duration<float>(now - rp.lastUpdateServerTime).count();
|
||||
|
||||
// Линейная интерполяция позиции
|
||||
rp.state.position = rp.startPosition + (rp.targetPosition - rp.startPosition) * rp.interpolationFactor;
|
||||
// Ограничим экстраполяцию (например, не более 2 секунд),
|
||||
// чтобы в случае лага корабли не улетали в бесконечность
|
||||
if (age_s > 2.0f) age_s = 2.0f;
|
||||
|
||||
// Сферическая интерполяция вращения (Slerp)
|
||||
Eigen::Quaternionf currentQ = rp.startRotation.slerp(rp.interpolationFactor, rp.targetRotation);
|
||||
rp.state.rotation = currentQ.toRotationMatrix();
|
||||
// 2. Сбрасываем физическое состояние rp.state в значения из последнего пакета
|
||||
// (Это важно: мы всегда экстраполируем от последнего достоверного серверного состояния)
|
||||
// В WebSocketClient::updateRemotePlayer нужно убедиться, что rp.state обновляется данными из пакета.
|
||||
|
||||
// 3. Вызываем физику, чтобы "догнать" реальное время
|
||||
// Мы передаем age_s как один большой шаг симуляции
|
||||
rp.simulate_physics(age_s);
|
||||
|
||||
// Теперь rp.state.position и rp.state.rotation содержат актуальные
|
||||
// предсказанные данные для рендеринга в текущем кадре.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ namespace ZL {
|
||||
void handleUp(int mx, int my);
|
||||
void handleMotion(int mx, int my);
|
||||
|
||||
void updateRemotePlayers(float deltaMs);
|
||||
void extrapolateRemotePlayers(float deltaMs);
|
||||
|
||||
SDL_Window* window;
|
||||
SDL_GLContext glContext;
|
||||
@ -64,7 +64,7 @@ namespace ZL {
|
||||
std::vector<BoxCoords> boxCoordsArr;
|
||||
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_MAX_TIME_INTERVAL = 1000;
|
||||
|
||||
@ -20,7 +20,6 @@ struct ClientState {
|
||||
int discreteAngle = -1;
|
||||
|
||||
// Для расчета лага
|
||||
uint64_t lastClientTimestamp = 0;
|
||||
std::chrono::steady_clock::time_point lastUpdateServerTime;
|
||||
|
||||
void simulate_physics(float dt_s) {
|
||||
@ -103,14 +102,14 @@ struct ClientState {
|
||||
).count();
|
||||
|
||||
// 2. Вычисляем задержку
|
||||
double lag_ms = 0.0;
|
||||
float lag_ms = 0.0;
|
||||
if (nowTime > lastUpdateServerTime) {
|
||||
lag_ms = std::chrono::duration<float>(nowTime - lastUpdateServerTime).count();
|
||||
}
|
||||
|
||||
// 3. Защита от слишком больших скачков (Clamp)
|
||||
// Если лаг более 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) {
|
||||
// Доматываем симуляцию на величину задержки
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "RemotePlayer.h"
|
||||
#include "ClientState.h"
|
||||
|
||||
// NetworkInterface.h - Èíòåðôåéñ äëÿ ðàçíûõ òèïîâ ñîåäèíåíèé
|
||||
namespace ZL {
|
||||
@ -12,6 +12,6 @@ namespace ZL {
|
||||
virtual void Send(const std::string& message) = 0;
|
||||
virtual bool IsConnected() const = 0;
|
||||
virtual void Poll() = 0; // Äëÿ îáðàáîòêè âõîäÿùèõ ïàêåòîâ
|
||||
virtual std::unordered_map<int, RemotePlayer> getRemotePlayers() = 0;
|
||||
virtual std::unordered_map<int, ClientState> getRemotePlayers() = 0;
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,15 +1,2 @@
|
||||
#pragma once
|
||||
#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;
|
||||
};
|
||||
@ -96,52 +96,30 @@ namespace ZL {
|
||||
if (id == this->clientId) continue; // Пропускаем себя
|
||||
|
||||
// Логика обновления или создания 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)
|
||||
std::lock_guard<std::mutex> lock(playersMutex);
|
||||
|
||||
auto& rp = remotePlayers[id];
|
||||
rp.state.id = id;
|
||||
rp.id = id;
|
||||
|
||||
// 1. Ñîõðàíÿåì òåêóùèå âèçóàëüíûå êîîðäèíàòû êàê íà÷àëüíóþ òî÷êó äëÿ íîâîé èíòåðïîëÿöèè
|
||||
// Ýòî ïðåäîòâðàòèò "òåëåïîðòàöèþ", åñëè ïðåäûäóùàÿ èíòåðïîëÿöèÿ íå óñïåëà äîéòè äî 1.0
|
||||
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
|
||||
);
|
||||
rp.position = { std::stof(vals[1]), std::stof(vals[2]), std::stof(vals[3]) };
|
||||
rp.rotation = Eigen::Quaternionf(std::stof(vals[4]), std::stof(vals[5]), std::stof(vals[6]), std::stof(vals[7]));
|
||||
|
||||
// 3. Сохраняем остальные физические параметры (для экстраполяции или отладки)
|
||||
rp.state.velocity = std::stof(vals[8]);
|
||||
rp.state.currentAngularVelocity = { std::stof(vals[9]), std::stof(vals[10]), std::stof(vals[11]) };
|
||||
rp.state.selectedVelocity = std::stoi(vals[12]);
|
||||
rp.state.discreteMag = std::stoi(vals[13]);
|
||||
rp.state.discreteAngle = std::stoi(vals[14]);
|
||||
rp.velocity = std::stof(vals[8]);
|
||||
rp.currentAngularVelocity = { std::stof(vals[9]), std::stof(vals[10]), std::stof(vals[11]) };
|
||||
rp.selectedVelocity = std::stoi(vals[12]);
|
||||
rp.discreteMag = std::stoi(vals[13]);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ namespace ZL {
|
||||
bool connected = false;
|
||||
int clientId = -1;
|
||||
|
||||
std::unordered_map<int, RemotePlayer> remotePlayers;
|
||||
std::unordered_map<int, ClientState> remotePlayers;
|
||||
std::mutex playersMutex;
|
||||
|
||||
void startAsyncRead();
|
||||
@ -41,7 +41,7 @@ namespace ZL {
|
||||
|
||||
void Poll() override;
|
||||
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 doWrite();
|
||||
@ -49,7 +49,7 @@ namespace ZL {
|
||||
bool IsConnected() const override { return connected; }
|
||||
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);
|
||||
return remotePlayers;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user