Working on server
This commit is contained in:
parent
ea26d6fb23
commit
1348efb5fe
@ -53,6 +53,8 @@ add_executable(space-game001
|
|||||||
../src/network/NetworkInterface.h
|
../src/network/NetworkInterface.h
|
||||||
../src/network/LocalClient.h
|
../src/network/LocalClient.h
|
||||||
../src/network/LocalClient.cpp
|
../src/network/LocalClient.cpp
|
||||||
|
../src/network/ClientState.h
|
||||||
|
../src/network/RemotePlayer.h
|
||||||
../src/network/WebSocketClient.h
|
../src/network/WebSocketClient.h
|
||||||
../src/network/WebSocketClient.cpp
|
../src/network/WebSocketClient.cpp
|
||||||
)
|
)
|
||||||
|
|||||||
@ -25,7 +25,10 @@ add_subdirectory("${BOOST_SRC_DIR}/libs/predef" boost-predef-build EXCLUDE_FROM_
|
|||||||
# EXCLUDE_FROM_ALL гарантирует, что мы собираем только то, что линкуем
|
# EXCLUDE_FROM_ALL гарантирует, что мы собираем только то, что линкуем
|
||||||
|
|
||||||
# Исполняемый файл сервера
|
# Исполняемый файл сервера
|
||||||
add_executable(Server main.cpp)
|
add_executable(Server
|
||||||
|
server.cpp
|
||||||
|
../src/network/ClientState.h
|
||||||
|
)
|
||||||
|
|
||||||
target_include_directories(Server PRIVATE ${BOOST_SRC_DIR})
|
target_include_directories(Server PRIVATE ${BOOST_SRC_DIR})
|
||||||
|
|
||||||
|
|||||||
@ -1,81 +0,0 @@
|
|||||||
#include <boost/beast/core.hpp>
|
|
||||||
#include <boost/beast/websocket.hpp>
|
|
||||||
#include <boost/asio/ip/tcp.hpp>
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace beast = boost::beast;
|
|
||||||
namespace http = beast::http;
|
|
||||||
namespace websocket = beast::websocket;
|
|
||||||
namespace net = boost::asio;
|
|
||||||
using tcp = net::ip::tcp;
|
|
||||||
|
|
||||||
class Session : public std::enable_shared_from_this<Session> {
|
|
||||||
websocket::stream<beast::tcp_stream> ws_;
|
|
||||||
beast::flat_buffer buffer_;
|
|
||||||
int id_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Session(tcp::socket&& socket, int id)
|
|
||||||
: ws_(std::move(socket)), id_(id) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void run() {
|
|
||||||
ws_.async_accept([self = shared_from_this()](beast::error_code ec) {
|
|
||||||
if (ec) return;
|
|
||||||
std::cout << "Client " << self->id_ << " connected\n";
|
|
||||||
// Ñðàçó îòïðàâëÿåì ID êëèåíòó
|
|
||||||
self->send_message("ID:" + std::to_string(self->id_));
|
|
||||||
self->do_read();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void do_read() {
|
|
||||||
ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
|
|
||||||
if (ec) return;
|
|
||||||
std::string msg = beast::buffers_to_string(self->buffer_.data());
|
|
||||||
|
|
||||||
if (msg == "PING") {
|
|
||||||
self->send_message("PONG");
|
|
||||||
}
|
|
||||||
|
|
||||||
self->buffer_.consume(self->buffer_.size());
|
|
||||||
self->do_read();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
try {
|
|
||||||
net::io_context ioc;
|
|
||||||
tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} };
|
|
||||||
int next_id = 1000;
|
|
||||||
|
|
||||||
std::cout << "Server started on port 8080...\n";
|
|
||||||
|
|
||||||
auto do_accept = [&](auto& self_fn) -> void {
|
|
||||||
acceptor.async_accept([&, self_fn](beast::error_code ec, tcp::socket socket) {
|
|
||||||
if (!ec) {
|
|
||||||
std::make_shared<Session>(std::move(socket), next_id++)->run();
|
|
||||||
}
|
|
||||||
self_fn(self_fn);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
do_accept(do_accept);
|
|
||||||
ioc.run();
|
|
||||||
}
|
|
||||||
catch (std::exception const& e) {
|
|
||||||
std::cerr << "Error: " << e.what() << std::endl;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
237
server/server.cpp
Normal file
237
server/server.cpp
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
#include <boost/beast/core.hpp>
|
||||||
|
#include <boost/beast/websocket.hpp>
|
||||||
|
#include <boost/asio/ip/tcp.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
#include <Eigen/Dense>
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
#include <math.h>
|
||||||
|
#include "../src/network/ClientState.h"
|
||||||
|
|
||||||
|
// Âñïîìîãàòåëüíûé split
|
||||||
|
std::vector<std::string> split(const std::string& s, char delimiter) {
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
std::string token;
|
||||||
|
std::istringstream tokenStream(s);
|
||||||
|
while (std::getline(tokenStream, token, delimiter)) {
|
||||||
|
tokens.push_back(token);
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace beast = boost::beast;
|
||||||
|
namespace http = beast::http;
|
||||||
|
namespace websocket = beast::websocket;
|
||||||
|
namespace net = boost::asio;
|
||||||
|
using tcp = net::ip::tcp;
|
||||||
|
|
||||||
|
class Session;
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Session>> g_sessions;
|
||||||
|
std::mutex g_sessions_mutex;
|
||||||
|
|
||||||
|
class Session : public std::enable_shared_from_this<Session> {
|
||||||
|
websocket::stream<beast::tcp_stream> ws_;
|
||||||
|
beast::flat_buffer buffer_;
|
||||||
|
int id_;
|
||||||
|
ClientState state_;
|
||||||
|
|
||||||
|
void process_message(const std::string& msg) {
|
||||||
|
auto now_server = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
// Ïðåäïîëîæèì ôîðìàò ñîîáùåíèé:
|
||||||
|
// ROT:ANGLE:MAG:TIMESTAMP
|
||||||
|
// VEL:SELECTED_VEL:TIMESTAMP
|
||||||
|
auto parts = split(msg, ':');
|
||||||
|
if (parts.empty()) return;
|
||||||
|
|
||||||
|
std::cout << msg << std::endl;
|
||||||
|
|
||||||
|
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
now_server.time_since_epoch()
|
||||||
|
).count();
|
||||||
|
|
||||||
|
uint64_t clientTimestamp = std::stoull(parts[1]);
|
||||||
|
|
||||||
|
// Ñíà÷àëà ïðîãîíÿåì ñòàíäàðòíóþ ñèìóëÿöèþ "äî òåêóùåãî ìîìåíòà ñåðâåðà"
|
||||||
|
float dt_server = 0.0f;
|
||||||
|
if (state_.lastUpdateServerTime.time_since_epoch().count() > 0) {
|
||||||
|
dt_server = (clientTimestamp - now_ms) * 1000.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
// Òåïåðü îáðàáàòûâàåì ñïåöèôèêó ñîîáùåíèÿ è ëàã
|
||||||
|
if (parts[0] == "ROT") {
|
||||||
|
state_.discreteAngle = std::stoi(parts[2]);
|
||||||
|
state_.discreteMag = std::stof(parts[3]);
|
||||||
|
state_.apply_lag_compensation(now_server);
|
||||||
|
}
|
||||||
|
else if (parts[0] == "VEL") {
|
||||||
|
state_.selectedVelocity = std::stoi(parts[2]);
|
||||||
|
state_.apply_lag_compensation(now_server);
|
||||||
|
}
|
||||||
|
else if (parts[0] == "PING") {
|
||||||
|
state_.handle_full_sync(parts);
|
||||||
|
state_.apply_lag_compensation(now_server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Session(tcp::socket&& socket, int id)
|
||||||
|
: ws_(std::move(socket)), id_(id) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
state_.lastUpdateServerTime = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_state_string() {
|
||||||
|
return state_.get_state_string(id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void run() {
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||||
|
g_sessions.push_back(shared_from_this());
|
||||||
|
}
|
||||||
|
|
||||||
|
ws_.async_accept([self = shared_from_this()](beast::error_code ec) {
|
||||||
|
if (ec) return;
|
||||||
|
std::cout << "Client " << self->id_ << " connected\n";
|
||||||
|
self->init();
|
||||||
|
self->send_message("ID:" + std::to_string(self->id_));
|
||||||
|
self->do_read();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void tick_physics_global(std::chrono::steady_clock::time_point now) {
|
||||||
|
float dt = 0.0f;
|
||||||
|
|
||||||
|
// Åñëè ýòî ñàìûé ïåðâûé òèê, ïðîñòî çàïîìèíàåì âðåìÿ
|
||||||
|
if (state_.lastUpdateServerTime.time_since_epoch().count() > 0) {
|
||||||
|
dt = std::chrono::duration<float>(now - state_.lastUpdateServerTime).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dt > 0.0001f) {
|
||||||
|
state_.simulate_physics(dt);
|
||||||
|
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) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void do_read() {
|
||||||
|
ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
|
||||||
|
if (ec)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||||
|
g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(),
|
||||||
|
[self](const std::shared_ptr<Session>& session) {
|
||||||
|
return session.get() == self.get();
|
||||||
|
}), g_sessions.end());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string msg = beast::buffers_to_string(self->buffer_.data());
|
||||||
|
self->process_message(msg);
|
||||||
|
|
||||||
|
self->buffer_.consume(self->buffer_.size());
|
||||||
|
self->do_read();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void update_world(net::steady_timer& timer, net::io_context& ioc) {
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||||
|
|
||||||
|
// 1. Ñèìóëÿöèÿ ôèçèêè äëÿ âñåõ
|
||||||
|
for (auto& session : g_sessions) {
|
||||||
|
session->tick_physics_global(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto last_broadcast = now;
|
||||||
|
if (std::chrono::duration<float>(now - last_broadcast).count() >= 1.0f) {
|
||||||
|
last_broadcast = now;
|
||||||
|
|
||||||
|
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch()
|
||||||
|
).count();
|
||||||
|
|
||||||
|
// Ñîáèðàåì äàííûå âñåõ èãðîêîâ â îäèí ïàêåò
|
||||||
|
std::string snapshot = "WORLD_UPDATE|";
|
||||||
|
snapshot += std::to_string(now_ms) + "|";
|
||||||
|
snapshot += std::to_string(g_sessions.size()) + "|";
|
||||||
|
for (size_t i = 0; i < g_sessions.size(); ++i) {
|
||||||
|
snapshot += g_sessions[i]->get_state_string();
|
||||||
|
if (i < g_sessions.size() - 1) snapshot += ";"; // Ðàçäåëèòåëü ìåæäó èãðîêàìè
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ðàññûëàåì âñåì
|
||||||
|
for (auto& session : g_sessions) {
|
||||||
|
session->send_message(snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ÂÀÆÍÎ: Òèêàåì ÷àñòî (50ìñ), à øëåì äàííûå ðåäêî (1000ìñ âûøå)
|
||||||
|
timer.expires_after(std::chrono::milliseconds(50));
|
||||||
|
timer.async_wait([&](const boost::system::error_code& ec) {
|
||||||
|
if (!ec) update_world(timer, ioc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
try {
|
||||||
|
net::io_context ioc;
|
||||||
|
tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} };
|
||||||
|
int next_id = 1000;
|
||||||
|
|
||||||
|
std::cout << "Server started on port 8080...\n";
|
||||||
|
|
||||||
|
auto do_accept = [&](auto& self_fn) -> void {
|
||||||
|
acceptor.async_accept([&, self_fn](beast::error_code ec, tcp::socket socket) {
|
||||||
|
if (!ec) {
|
||||||
|
std::make_shared<Session>(std::move(socket), next_id++)->run();
|
||||||
|
}
|
||||||
|
self_fn(self_fn);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
net::steady_timer timer(ioc);
|
||||||
|
update_world(timer, ioc);
|
||||||
|
|
||||||
|
do_accept(do_accept);
|
||||||
|
ioc.run();
|
||||||
|
}
|
||||||
|
catch (std::exception const& e) {
|
||||||
|
std::cerr << "Error: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@ -40,8 +40,13 @@ Eigen::Vector2f Environment::tapDownCurrentPos = { 0, 0 };
|
|||||||
Eigen::Vector3f Environment::shipPosition = {0,0,45000.f};
|
Eigen::Vector3f Environment::shipPosition = {0,0,45000.f};
|
||||||
|
|
||||||
float Environment::shipVelocity = 0.f;
|
float Environment::shipVelocity = 0.f;
|
||||||
|
int Environment::shipSelectedVelocity = 0;
|
||||||
|
Eigen::Vector3f Environment::currentAngularVelocity;
|
||||||
|
|
||||||
|
|
||||||
|
int Environment::lastSentAngle = -1;
|
||||||
|
float Environment::lastSentMagnitude = 0.0f;
|
||||||
|
|
||||||
const float Environment::CONST_Z_NEAR = 5.f;
|
const float Environment::CONST_Z_NEAR = 5.f;
|
||||||
const float Environment::CONST_Z_FAR = 5000.f;
|
const float Environment::CONST_Z_FAR = 5000.f;
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,11 @@ public:
|
|||||||
|
|
||||||
static Eigen::Vector3f shipPosition;
|
static Eigen::Vector3f shipPosition;
|
||||||
static float shipVelocity;
|
static float shipVelocity;
|
||||||
|
static int shipSelectedVelocity;
|
||||||
|
static Eigen::Vector3f currentAngularVelocity;
|
||||||
|
|
||||||
|
static int lastSentAngle;
|
||||||
|
static float lastSentMagnitude;
|
||||||
|
|
||||||
static const float CONST_Z_NEAR;
|
static const float CONST_Z_NEAR;
|
||||||
static const float CONST_Z_FAR;
|
static const float CONST_Z_FAR;
|
||||||
|
|||||||
307
src/Game.cpp
307
src/Game.cpp
@ -19,6 +19,8 @@
|
|||||||
#include "network/LocalClient.h"
|
#include "network/LocalClient.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "network/RemotePlayer.h"
|
||||||
|
|
||||||
namespace ZL
|
namespace ZL
|
||||||
{
|
{
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
@ -139,33 +141,6 @@ namespace ZL
|
|||||||
ZL::BindOpenGlFunctions();
|
ZL::BindOpenGlFunctions();
|
||||||
ZL::CheckGlError();
|
ZL::CheckGlError();
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
__android_log_print(ANDROID_LOG_INFO, "Game", "Start for Android");
|
|
||||||
|
|
||||||
const char* testFiles[] = {
|
|
||||||
"resources/shaders/default.vertex",
|
|
||||||
"resources/shaders/default_web.fragment",
|
|
||||||
nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; testFiles[i] != nullptr; i++) {
|
|
||||||
SDL_RWops* file = SDL_RWFromFile(testFiles[i], "rb");
|
|
||||||
if (file) {
|
|
||||||
Sint64 size = SDL_RWsize(file);
|
|
||||||
__android_log_print(ANDROID_LOG_INFO, "Game", "Found: %s (size: %lld)", testFiles[i], size);
|
|
||||||
SDL_RWclose(file);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
__android_log_print(ANDROID_LOG_WARN, "Game", "Not found: %s (SDL error: %s)",
|
|
||||||
testFiles[i], SDL_GetError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
__android_log_print(ANDROID_LOG_ERROR, "ShaderManager",
|
|
||||||
"Step 1xxxxxxx");
|
|
||||||
#endif
|
|
||||||
// Initialize renderer
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef SIMPLIFIED
|
#ifndef SIMPLIFIED
|
||||||
renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", CONST_ZIP_FILE);
|
||||||
@ -178,37 +153,11 @@ namespace ZL
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE);
|
||||||
#ifdef __ANDROID__
|
|
||||||
__android_log_print(ANDROID_LOG_ERROR, "ShaderManager",
|
|
||||||
"Step 2xxxxxxx");
|
|
||||||
#endif
|
|
||||||
renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/default_env.vertex", "resources/shaders/default_env_web.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/default_env.vertex", "resources/shaders/default_env_web.fragment", CONST_ZIP_FILE);
|
||||||
#ifdef __ANDROID__
|
|
||||||
__android_log_print(ANDROID_LOG_INFO, "ShaderManager",
|
|
||||||
"Step 2");
|
|
||||||
#endif
|
|
||||||
renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
|
||||||
#ifdef __ANDROID__
|
|
||||||
__android_log_print(ANDROID_LOG_INFO, "ShaderManager",
|
|
||||||
"Step 3");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
|
||||||
#ifdef __ANDROID__
|
|
||||||
__android_log_print(ANDROID_LOG_INFO, "ShaderManager",
|
|
||||||
"Step 4");
|
|
||||||
#endif
|
|
||||||
renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
|
||||||
#ifdef __ANDROID__
|
|
||||||
__android_log_print(ANDROID_LOG_INFO, "ShaderManager",
|
|
||||||
"Step 5");
|
|
||||||
#endif
|
|
||||||
renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
|
||||||
#ifdef __ANDROID__
|
|
||||||
__android_log_print(ANDROID_LOG_INFO, "ShaderManager",
|
|
||||||
"Step 1");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool cfgLoaded = sparkEmitter.loadFromJsonFile("resources/config/spark_config.json", renderer, CONST_ZIP_FILE);
|
bool cfgLoaded = sparkEmitter.loadFromJsonFile("resources/config/spark_config.json", renderer, CONST_ZIP_FILE);
|
||||||
@ -289,16 +238,17 @@ namespace ZL
|
|||||||
});
|
});
|
||||||
|
|
||||||
uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) {
|
uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) {
|
||||||
Environment::shipVelocity = value * 1000.f;
|
int newVel = roundf(value * 10);
|
||||||
|
if (newVel != Environment::shipSelectedVelocity) {
|
||||||
|
Environment::shipSelectedVelocity = newVel;
|
||||||
|
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch()
|
||||||
|
).count();
|
||||||
|
std::string msg = "VEL:" + std::to_string(now_ms) + ":" + std::to_string(Environment::shipSelectedVelocity);
|
||||||
|
networkClient->Send(msg);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/*uiManager.setSliderCallback("musicVolumeSlider", [this](const std::string& name, float value) {
|
|
||||||
std::cerr << "Music volume slider changed to: " << value << std::endl;
|
|
||||||
musicVolume = value;
|
|
||||||
Environment::shipVelocity = musicVolume * 20.0f;
|
|
||||||
});
|
|
||||||
//#endif*/
|
|
||||||
|
|
||||||
cubemapTexture = std::make_shared<Texture>(
|
cubemapTexture = std::make_shared<Texture>(
|
||||||
std::array<TextureDataStruct, 6>{
|
std::array<TextureDataStruct, 6>{
|
||||||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||||||
@ -330,10 +280,6 @@ namespace ZL
|
|||||||
spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());
|
spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());
|
||||||
|
|
||||||
spaceshipBase.Move(Vector3f{ 1.2, 0, -5 });
|
spaceshipBase.Move(Vector3f{ 1.2, 0, -5 });
|
||||||
//spaceshipBase.Move(Vector3f{ -0.52998, -13, 0 });
|
|
||||||
//spaceshipBase.Move(Vector3f{ -0.52998, -10, 10 });
|
|
||||||
//spaceshipBase.Move(Vector3f{ -0.52998, 0, 10 });
|
|
||||||
|
|
||||||
|
|
||||||
spaceship.AssignFrom(spaceshipBase);
|
spaceship.AssignFrom(spaceshipBase);
|
||||||
spaceship.RefreshVBO();
|
spaceship.RefreshVBO();
|
||||||
@ -343,16 +289,8 @@ namespace ZL
|
|||||||
boxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/box/box.png", CONST_ZIP_FILE));
|
boxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/box/box.png", CONST_ZIP_FILE));
|
||||||
boxBase = LoadFromTextFile02("resources/box/box.txt", CONST_ZIP_FILE);
|
boxBase = LoadFromTextFile02("resources/box/box.txt", CONST_ZIP_FILE);
|
||||||
|
|
||||||
std::cout << "Init step 1 " << std::endl;
|
|
||||||
|
|
||||||
boxCoordsArr = generateRandomBoxCoords(50);
|
boxCoordsArr = generateRandomBoxCoords(50);
|
||||||
|
|
||||||
std::cout << "Init step 2 " << std::endl;
|
|
||||||
|
|
||||||
boxRenderArr.resize(boxCoordsArr.size());
|
boxRenderArr.resize(boxCoordsArr.size());
|
||||||
|
|
||||||
std::cout << "Init step 3x " << std::endl;
|
|
||||||
|
|
||||||
for (int i = 0; i < boxCoordsArr.size(); i++)
|
for (int i = 0; i < boxCoordsArr.size(); i++)
|
||||||
{
|
{
|
||||||
boxRenderArr[i].AssignFrom(boxBase);
|
boxRenderArr[i].AssignFrom(boxBase);
|
||||||
@ -362,26 +300,17 @@ namespace ZL
|
|||||||
|
|
||||||
boxAlive.resize(boxCoordsArr.size(), true);
|
boxAlive.resize(boxCoordsArr.size(), true);
|
||||||
|
|
||||||
std::cout << "Init step 4 " << std::endl;
|
|
||||||
|
|
||||||
if (!cfgLoaded)
|
if (!cfgLoaded)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||||
}
|
}
|
||||||
std::cout << "Init step 5 " << std::endl;
|
|
||||||
|
|
||||||
renderer.InitOpenGL();
|
renderer.InitOpenGL();
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
std::cout << "Init step 6 " << std::endl;
|
|
||||||
planetObject.init();
|
planetObject.init();
|
||||||
|
|
||||||
std::cout << "Init step 7 " << std::endl;
|
|
||||||
//rockTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/rock.png", CONST_ZIP_FILE));
|
|
||||||
|
|
||||||
std::cout << "Init step 8 " << std::endl;
|
|
||||||
|
|
||||||
#ifdef NETWORK
|
#ifdef NETWORK
|
||||||
networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
|
networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
|
||||||
networkClient->Connect("127.0.0.1", 8080);
|
networkClient->Connect("127.0.0.1", 8080);
|
||||||
@ -639,14 +568,182 @@ 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));
|
||||||
|
|
||||||
static float pingTimer = 0.0f;
|
static float pingTimer = 0.0f;
|
||||||
pingTimer += delta;
|
pingTimer += delta;
|
||||||
if (pingTimer >= 1000.0f) {
|
if (pingTimer >= 1000.0f) {
|
||||||
networkClient->Send("PING");
|
|
||||||
|
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch()
|
||||||
|
).count();
|
||||||
|
|
||||||
|
// 1. Извлекаем кватернион из матрицы поворота
|
||||||
|
Eigen::Quaternionf q(Environment::shipMatrix);
|
||||||
|
|
||||||
|
// 2. Формируем строку PING согласно протоколу сервера
|
||||||
|
// Формат: PING:timestamp:posX:posY:posZ:qW:qX:qY:qZ:angVelX:angVelY:angVelZ:vel:selectedVel:discMag:discAngle
|
||||||
|
std::string pingMsg = "PING:" + std::to_string(now_ms) + ":"
|
||||||
|
+ std::to_string(Environment::shipPosition.x()) + ":"
|
||||||
|
+ std::to_string(Environment::shipPosition.y()) + ":"
|
||||||
|
+ std::to_string(Environment::shipPosition.z()) + ":"
|
||||||
|
+ std::to_string(q.w()) + ":"
|
||||||
|
+ std::to_string(q.x()) + ":"
|
||||||
|
+ std::to_string(q.y()) + ":"
|
||||||
|
+ std::to_string(q.z()) + ":"
|
||||||
|
+ std::to_string(Environment::currentAngularVelocity.x()) + ":"
|
||||||
|
+ std::to_string(Environment::currentAngularVelocity.y()) + ":"
|
||||||
|
+ std::to_string(Environment::currentAngularVelocity.z()) + ":"
|
||||||
|
+ std::to_string(Environment::shipVelocity) + ":"
|
||||||
|
+ std::to_string(Environment::shipSelectedVelocity) + ":"
|
||||||
|
+ std::to_string(Environment::lastSentMagnitude) + ":" // Используем те же static переменные из блока ROT
|
||||||
|
+ std::to_string(Environment::lastSentAngle);
|
||||||
|
|
||||||
|
networkClient->Send(pingMsg);
|
||||||
pingTimer = 0.0f;
|
pingTimer = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const float CONST_ACCELERATION = 1.f;
|
||||||
|
|
||||||
|
float shipDesiredVelocity = Environment::shipSelectedVelocity * 100.f;
|
||||||
|
|
||||||
|
if (!gameOver)
|
||||||
|
{
|
||||||
|
if (Environment::shipVelocity < shipDesiredVelocity)
|
||||||
|
{
|
||||||
|
Environment::shipVelocity += delta * CONST_ACCELERATION;
|
||||||
|
if (Environment::shipVelocity > shipDesiredVelocity)
|
||||||
|
{
|
||||||
|
Environment::shipVelocity = shipDesiredVelocity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Environment::shipVelocity > shipDesiredVelocity)
|
||||||
|
{
|
||||||
|
Environment::shipVelocity -= delta * CONST_ACCELERATION;
|
||||||
|
if (Environment::shipVelocity < shipDesiredVelocity)
|
||||||
|
{
|
||||||
|
Environment::shipVelocity = shipDesiredVelocity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const float ANGULAR_ACCEL = 0.005f;
|
||||||
|
|
||||||
|
if (Environment::tapDownHold) {
|
||||||
|
float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0);
|
||||||
|
float diffy = Environment::tapDownCurrentPos(1) - Environment::tapDownStartPos(1);
|
||||||
|
|
||||||
|
float rawMag = sqrtf(diffx * diffx + diffy * diffy);
|
||||||
|
float maxRadius = 200.0f; // Максимальный вынос джойстика
|
||||||
|
|
||||||
|
if (rawMag > 10.0f) { // Мертвая зона
|
||||||
|
// 1. Дискретизируем отклонение (0.0 - 1.0 с шагом 0.1)
|
||||||
|
float normalizedMag = min(rawMag / maxRadius, 1.0f);
|
||||||
|
float discreteMag = std::round(normalizedMag * 10.0f) / 10.0f;
|
||||||
|
|
||||||
|
// 2. Дискретизируем угол (0-359 градусов)
|
||||||
|
// atan2 возвращает радианы, переводим в градусы
|
||||||
|
float radians = atan2f(diffy, diffx);
|
||||||
|
int discreteAngle = static_cast<int>(radians * 180.0f / M_PI);
|
||||||
|
if (discreteAngle < 0) discreteAngle += 360;
|
||||||
|
|
||||||
|
// 3. Проверяем, изменились ли параметры значимо для отправки на сервер
|
||||||
|
if (discreteAngle != Environment::lastSentAngle || discreteMag != Environment::lastSentMagnitude) {
|
||||||
|
Environment::lastSentAngle = discreteAngle;
|
||||||
|
Environment::lastSentMagnitude = discreteMag;
|
||||||
|
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch()
|
||||||
|
).count();
|
||||||
|
|
||||||
|
// Формируем сетевой пакет
|
||||||
|
// Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации
|
||||||
|
std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag);
|
||||||
|
networkClient->Send(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Логика вращения (угловое ускорение)
|
||||||
|
// Теперь используем discreteAngle и discreteMag как "цель" для набора угловой скорости
|
||||||
|
// ... (код интерполяции скорости к этой цели)
|
||||||
|
|
||||||
|
// Вычисляем целевой вектор оси вращения из дискретного угла
|
||||||
|
// В 3D пространстве экранные X и Y преобразуются в оси вращения вокруг Y и X соответственно
|
||||||
|
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 - Environment::currentAngularVelocity;
|
||||||
|
float diffLen = diffVel.norm();
|
||||||
|
|
||||||
|
if (diffLen > 0.0001f) {
|
||||||
|
// Вычисляем, на сколько мы можем изменить скорость в этом кадре
|
||||||
|
float maxChange = ANGULAR_ACCEL * static_cast<float>(delta);
|
||||||
|
|
||||||
|
if (diffLen <= maxChange) {
|
||||||
|
// Если до цели осталось меньше, чем шаг ускорения — просто прыгаем в цель
|
||||||
|
Environment::currentAngularVelocity = targetAngularVelocity;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Линейно двигаемся в сторону целевого вектора
|
||||||
|
Environment::currentAngularVelocity += (diffVel / diffLen) * maxChange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяем вращение к матрице корабля
|
||||||
|
float speedScale = Environment::currentAngularVelocity.norm();
|
||||||
|
if (speedScale > 0.0001f) {
|
||||||
|
// Коэффициент чувствительности вращения
|
||||||
|
const float ROTATION_SENSITIVITY = 0.002f;
|
||||||
|
float deltaAlpha = speedScale * static_cast<float>(delta) * ROTATION_SENSITIVITY;
|
||||||
|
|
||||||
|
Eigen::Vector3f axis = Environment::currentAngularVelocity.normalized();
|
||||||
|
Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis));
|
||||||
|
|
||||||
|
Environment::shipMatrix = Environment::shipMatrix * rotateQuat.toRotationMatrix();
|
||||||
|
Environment::inverseShipMatrix = Environment::shipMatrix.inverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Если джойстик не зажат — сбрасываем дискретные значения и плавно замедляем вращение
|
||||||
|
Environment::lastSentAngle = -1;
|
||||||
|
Environment::lastSentMagnitude = 0.0f;
|
||||||
|
|
||||||
|
float currentSpeed = Environment::currentAngularVelocity.norm();
|
||||||
|
|
||||||
|
if (currentSpeed > 0.0001f) {
|
||||||
|
float drop = ANGULAR_ACCEL * static_cast<float>(delta);
|
||||||
|
if (currentSpeed <= drop) {
|
||||||
|
Environment::currentAngularVelocity = Eigen::Vector3f::Zero();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Уменьшаем модуль вектора, сохраняя направление
|
||||||
|
Environment::currentAngularVelocity -= (Environment::currentAngularVelocity / currentSpeed) * drop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяем остаточное вращение (инерция)
|
||||||
|
float speedScale = Environment::currentAngularVelocity.norm();
|
||||||
|
if (speedScale > 0.0001f) {
|
||||||
|
float deltaAlpha = speedScale * static_cast<float>(delta) * 0.002f;
|
||||||
|
Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, Environment::currentAngularVelocity.normalized()));
|
||||||
|
Environment::shipMatrix = Environment::shipMatrix * rotateQuat.toRotationMatrix();
|
||||||
|
Environment::inverseShipMatrix = Environment::shipMatrix.inverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Движение вперед (существующая логика)
|
||||||
|
if (fabs(Environment::shipVelocity) > 0.01f)
|
||||||
|
{
|
||||||
|
Vector3f velocityDirection = { 0,0, -Environment::shipVelocity * delta / 1000.f };
|
||||||
|
Vector3f velocityDirectionAdjusted = Environment::shipMatrix * velocityDirection;
|
||||||
|
Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
if (Environment::tapDownHold) {
|
if (Environment::tapDownHold) {
|
||||||
|
|
||||||
float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0);
|
float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0);
|
||||||
@ -671,14 +768,7 @@ namespace ZL
|
|||||||
Environment::inverseShipMatrix = Environment::shipMatrix.inverse();
|
Environment::inverseShipMatrix = Environment::shipMatrix.inverse();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
if (fabs(Environment::shipVelocity) > 0.01f)
|
|
||||||
{
|
|
||||||
Vector3f velocityDirection = { 0,0, -Environment::shipVelocity * delta / 1000.f };
|
|
||||||
Vector3f velocityDirectionAdjusted = Environment::shipMatrix * velocityDirection;
|
|
||||||
Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& p : projectiles) {
|
for (auto& p : projectiles) {
|
||||||
if (p && p->isActive()) {
|
if (p && p->isActive()) {
|
||||||
@ -896,21 +986,7 @@ namespace ZL
|
|||||||
{
|
{
|
||||||
if (event.key.keysym.sym == SDLK_i)
|
if (event.key.keysym.sym == SDLK_i)
|
||||||
{
|
{
|
||||||
Environment::shipVelocity += 500.f;
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_k)
|
|
||||||
{
|
|
||||||
Environment::shipVelocity -= 500.f;
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_o)
|
|
||||||
{
|
|
||||||
Environment::shipVelocity += 50.f;
|
|
||||||
//x = x + 2.0;
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_l)
|
|
||||||
{
|
|
||||||
Environment::shipVelocity -= 50.f;
|
|
||||||
//x = x - 2.0;
|
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
#endif
|
#endif
|
||||||
@ -976,5 +1052,24 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Game::updateRemotePlayers(float deltaMs) {
|
||||||
|
|
||||||
|
latestRemotePlayers = networkClient->getRemotePlayers();
|
||||||
|
|
||||||
|
for (auto& [id, rp] : latestRemotePlayers) {
|
||||||
|
// Увеличиваем фактор интерполяции (базируется на частоте Snapshot = 1000мс)
|
||||||
|
rp.interpolationFactor += deltaMs / 1000.0f;
|
||||||
|
if (rp.interpolationFactor > 1.0f) rp.interpolationFactor = 1.0f;
|
||||||
|
|
||||||
|
// Линейная интерполяция позиции
|
||||||
|
rp.state.position = rp.startPosition + (rp.targetPosition - rp.startPosition) * rp.interpolationFactor;
|
||||||
|
|
||||||
|
// Сферическая интерполяция вращения (Slerp)
|
||||||
|
Eigen::Quaternionf currentQ = rp.startRotation.slerp(rp.interpolationFactor, rp.targetRotation);
|
||||||
|
rp.state.rotation = currentQ.toRotationMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
|
|||||||
@ -44,13 +44,15 @@ namespace ZL {
|
|||||||
void drawShip();
|
void drawShip();
|
||||||
void drawBoxes();
|
void drawBoxes();
|
||||||
void drawUI();
|
void drawUI();
|
||||||
|
|
||||||
void fireProjectiles();
|
void fireProjectiles();
|
||||||
|
|
||||||
void handleDown(int mx, int my);
|
void handleDown(int mx, int my);
|
||||||
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);
|
||||||
|
|
||||||
SDL_Window* window;
|
SDL_Window* window;
|
||||||
SDL_GLContext glContext;
|
SDL_GLContext glContext;
|
||||||
|
|
||||||
@ -62,6 +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;
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
152
src/network/ClientState.h
Normal file
152
src/network/ClientState.h
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <chrono>
|
||||||
|
#include <Eigen/Dense>
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
using std::min;
|
||||||
|
using std::max;
|
||||||
|
|
||||||
|
struct ClientState {
|
||||||
|
int id;
|
||||||
|
Eigen::Vector3f position = { 0, 0, 45000.0f };
|
||||||
|
Eigen::Matrix3f rotation = Eigen::Matrix3f::Identity();
|
||||||
|
Eigen::Vector3f currentAngularVelocity = Eigen::Vector3f::Zero();
|
||||||
|
float velocity = 0.0f;
|
||||||
|
int selectedVelocity = 0;
|
||||||
|
float discreteMag = 0;
|
||||||
|
int discreteAngle = -1;
|
||||||
|
|
||||||
|
// Для расчета лага
|
||||||
|
uint64_t lastClientTimestamp = 0;
|
||||||
|
std::chrono::steady_clock::time_point lastUpdateServerTime;
|
||||||
|
|
||||||
|
void simulate_physics(float dt_s) {
|
||||||
|
// Константы из 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
|
||||||
|
|
||||||
|
// 1. Вычисляем targetAngularVelocity на лету из дискретных значений
|
||||||
|
Eigen::Vector3f targetAngularVelocity = Eigen::Vector3f::Zero();
|
||||||
|
|
||||||
|
if (discreteMag > 0.001f) {
|
||||||
|
float rad = static_cast<float>(discreteAngle) * static_cast<float>(M_PI) / 180.0f;
|
||||||
|
// Направление из угла (как в твоем обновленном клиентском коде)
|
||||||
|
Eigen::Vector3f targetDir(sinf(rad), cosf(rad), 0.0f);
|
||||||
|
targetAngularVelocity = targetDir * discreteMag;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Линейное изменение текущей угловой скорости к вычисленной цели
|
||||||
|
Eigen::Vector3f diffVel = targetAngularVelocity - currentAngularVelocity;
|
||||||
|
float diffLen = diffVel.norm();
|
||||||
|
|
||||||
|
if (diffLen > 0.0001f) {
|
||||||
|
float maxChange = ANGULAR_ACCEL * dt_s;
|
||||||
|
if (diffLen <= maxChange) {
|
||||||
|
currentAngularVelocity = targetAngularVelocity;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
currentAngularVelocity += (diffVel / diffLen) * maxChange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (discreteMag < 0.001f && currentAngularVelocity.norm() > 0.0001f) {
|
||||||
|
// Если джойстик отпущен, используем ту же логику торможения (или ANGULAR_ACCEL)
|
||||||
|
float currentSpeed = currentAngularVelocity.norm();
|
||||||
|
float drop = ANGULAR_ACCEL * dt_s;
|
||||||
|
if (currentSpeed <= drop) {
|
||||||
|
currentAngularVelocity = Eigen::Vector3f::Zero();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
currentAngularVelocity -= (currentAngularVelocity / currentSpeed) * drop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Применение вращения к матрице (Интеграция)
|
||||||
|
float speedScale = currentAngularVelocity.norm();
|
||||||
|
if (speedScale > 0.0001f) {
|
||||||
|
float deltaAlpha = speedScale * dt_s * ROTATION_SENSITIVITY;
|
||||||
|
Eigen::Vector3f axis = currentAngularVelocity.normalized();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Обновление позиции
|
||||||
|
if (std::abs(velocity) > 0.01f) {
|
||||||
|
// Движение вперед по локальной оси Z (в твоем коде это {0, 0, -1})
|
||||||
|
Eigen::Vector3f localMove(0.0f, 0.0f, -velocity * dt_s);
|
||||||
|
position += rotation * localMove;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply_lag_compensation(std::chrono::steady_clock::time_point nowTime) {
|
||||||
|
|
||||||
|
// 1. Получаем текущее время сервера в той же шкале (мс)
|
||||||
|
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch()
|
||||||
|
).count();
|
||||||
|
|
||||||
|
// 2. Вычисляем задержку
|
||||||
|
double 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);
|
||||||
|
|
||||||
|
if (final_lag_s > 0.001f) {
|
||||||
|
// Доматываем симуляцию на величину задержки
|
||||||
|
// Мы предполагаем, что за это время параметры управления не менялись
|
||||||
|
simulate_physics(final_lag_s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_state_string(int id) {
|
||||||
|
// Используем кватернион для передачи вращения (4 числа вместо 9)
|
||||||
|
Eigen::Quaternionf q(rotation);
|
||||||
|
|
||||||
|
std::string s = std::to_string(id) + ","
|
||||||
|
+ std::to_string(position.x()) + "," + std::to_string(position.y()) + "," + std::to_string(position.z()) + ","
|
||||||
|
+ std::to_string(q.w()) + "," + std::to_string(q.x()) + "," + std::to_string(q.y()) + "," + std::to_string(q.z()) + ","
|
||||||
|
+ std::to_string(velocity) + ","
|
||||||
|
+ std::to_string(currentAngularVelocity.x()) + "," + std::to_string(currentAngularVelocity.y()) + "," + std::to_string(currentAngularVelocity.z()) + ","
|
||||||
|
+ std::to_string(selectedVelocity) + ","
|
||||||
|
+ std::to_string(discreteMag) + ","
|
||||||
|
+ std::to_string(discreteAngle);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_full_sync(const std::vector<std::string>& parts) {
|
||||||
|
// Позиция
|
||||||
|
position = { std::stof(parts[2]), std::stof(parts[3]), std::stof(parts[4]) };
|
||||||
|
|
||||||
|
// Для вращения клиент должен прислать либо кватернион, либо углы Эйлера.
|
||||||
|
// Предположим, мы передаем 4 значения кватерниона для экономии:
|
||||||
|
Eigen::Quaternionf q(std::stof(parts[5]), std::stof(parts[6]), std::stof(parts[7]), std::stof(parts[8]));
|
||||||
|
rotation = q.toRotationMatrix();
|
||||||
|
|
||||||
|
currentAngularVelocity = Eigen::Vector3f{ std::stof(parts[9]), std::stof(parts[10]), std::stof(parts[11]) };
|
||||||
|
velocity = std::stof(parts[12]);
|
||||||
|
selectedVelocity = std::stoi(parts[13]);
|
||||||
|
discreteMag = std::stof(parts[14]);
|
||||||
|
discreteAngle = std::stoi(parts[15]);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include "RemotePlayer.h"
|
||||||
|
|
||||||
// NetworkInterface.h - Èíòåðôåéñ äëÿ ðàçíûõ òèïîâ ñîåäèíåíèé
|
// NetworkInterface.h - Èíòåðôåéñ äëÿ ðàçíûõ òèïîâ ñîåäèíåíèé
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
@ -10,5 +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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/network/RemotePlayer.h
Normal file
15
src/network/RemotePlayer.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#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;
|
||||||
|
};
|
||||||
@ -1,6 +1,18 @@
|
|||||||
#include "WebSocketClient.h"
|
#include "WebSocketClient.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include "RemotePlayer.h"
|
||||||
|
|
||||||
|
// Âñïîìîãàòåëüíûé split
|
||||||
|
std::vector<std::string> split(const std::string& s, char delimiter) {
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
std::string token;
|
||||||
|
std::istringstream tokenStream(s);
|
||||||
|
while (std::getline(tokenStream, token, delimiter)) {
|
||||||
|
tokens.push_back(token);
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
@ -55,19 +67,121 @@ namespace ZL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketClient::Poll() {
|
void WebSocketClient::Poll() {
|
||||||
// Забираем сообщения из очереди и обрабатываем их в главном потоке
|
|
||||||
std::lock_guard<std::mutex> lock(queueMutex);
|
std::lock_guard<std::mutex> lock(queueMutex);
|
||||||
while (!messageQueue.empty()) {
|
while (!messageQueue.empty()) {
|
||||||
std::string msg = messageQueue.front();
|
std::string msg = messageQueue.front();
|
||||||
messageQueue.pop();
|
messageQueue.pop();
|
||||||
// Тут можно вызвать колбэки игры или обновить состояние
|
|
||||||
|
if (msg.rfind("WORLD_UPDATE|", 0) == 0) {
|
||||||
|
parseWorldUpdate(msg);
|
||||||
|
std::cout << msg << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::parseWorldUpdate(const std::string& msg) {
|
||||||
|
// Ôîðìàò: WORLD_UPDATE|server_now_ms|count|id,x,y,z,w,qx,qy,qz,v;...
|
||||||
|
auto parts = split(msg, '|');
|
||||||
|
if (parts.size() < 4) return;
|
||||||
|
|
||||||
|
uint64_t serverTime = std::stoull(parts[1]);
|
||||||
|
int count = std::stoi(parts[2]);
|
||||||
|
auto playersData = split(parts[3], ';');
|
||||||
|
|
||||||
|
for (const auto& pData : playersData) {
|
||||||
|
auto vals = split(pData, ',');
|
||||||
|
if (vals.size() < 9) continue;
|
||||||
|
|
||||||
|
int id = std::stoi(vals[0]);
|
||||||
|
if (id == this->clientId) continue; // Ïðîïóñêàåì ñåáÿ
|
||||||
|
|
||||||
|
// Ëîãèêà îáíîâëåíèÿ èëè ñîçäàíèÿ RemotePlayer
|
||||||
|
updateRemotePlayer(id, vals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::updateRemotePlayer(int id, const std::vector<std::string>& vals) {
|
||||||
|
// Èñïîëüçóåì ìüþòåêñ, òàê êàê ýòîò ìåòîä âûçûâàåòñÿ èç ñåòåâîãî ïîòîêà (TaskManager)
|
||||||
|
std::lock_guard<std::mutex> lock(playersMutex);
|
||||||
|
|
||||||
|
auto& rp = remotePlayers[id];
|
||||||
|
rp.state.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
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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]);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void WebSocketClient::Send(const std::string& message) {
|
void WebSocketClient::Send(const std::string& message) {
|
||||||
if (!connected) return;
|
if (!connected) return;
|
||||||
// Отправку тоже можно сделать асинхронной, чтобы не ждать в главном потоке
|
|
||||||
auto ss = std::make_shared<std::string>(message);
|
auto ss = std::make_shared<std::string>(message);
|
||||||
ws_->async_write(boost::asio::buffer(*ss), [ss](boost::beast::error_code, std::size_t) {});
|
|
||||||
|
std::lock_guard<std::mutex> lock(writeMutex_);
|
||||||
|
writeQueue_.push(ss);
|
||||||
|
|
||||||
|
// Åñëè ñåé÷àñ íè÷åãî íå çàïèñûâàåòñÿ, èíèöèèðóåì ïåðâóþ çàïèñü
|
||||||
|
if (!isWriting_) {
|
||||||
|
doWrite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::doWrite() {
|
||||||
|
// Ýòà ôóíêöèÿ âñåãäà âûçûâàåòñÿ ïîä ìüþòåêñîì èëè èç êîëáýêà
|
||||||
|
if (writeQueue_.empty()) {
|
||||||
|
isWriting_ = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isWriting_ = true;
|
||||||
|
auto message = writeQueue_.front();
|
||||||
|
|
||||||
|
// Çàõâàòûâàåì self (shared_from_this), ÷òîáû îáúåêò íå óäàëèëñÿ âî âðåìÿ çàïèñè
|
||||||
|
ws_->async_write(
|
||||||
|
boost::asio::buffer(*message),
|
||||||
|
[this, message](boost::beast::error_code ec, std::size_t) {
|
||||||
|
if (ec) {
|
||||||
|
connected = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(writeMutex_);
|
||||||
|
writeQueue_.pop(); // Óäàëÿåì îòïðàâëåííîå ñîîáùåíèå
|
||||||
|
doWrite(); // Ïðîâåðÿåì ñëåäóþùåå
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -21,9 +21,16 @@ namespace ZL {
|
|||||||
std::queue<std::string> messageQueue;
|
std::queue<std::string> messageQueue;
|
||||||
std::mutex queueMutex; // Çàùèòà äëÿ messageQueue
|
std::mutex queueMutex; // Çàùèòà äëÿ messageQueue
|
||||||
|
|
||||||
|
std::queue<std::shared_ptr<std::string>> writeQueue_;
|
||||||
|
bool isWriting_ = false;
|
||||||
|
std::mutex writeMutex_; // Îòäåëüíûé ìüþòåêñ äëÿ î÷åðåäè çàïèñè
|
||||||
|
|
||||||
bool connected = false;
|
bool connected = false;
|
||||||
int clientId = -1;
|
int clientId = -1;
|
||||||
|
|
||||||
|
std::unordered_map<int, RemotePlayer> remotePlayers;
|
||||||
|
std::mutex playersMutex;
|
||||||
|
|
||||||
void startAsyncRead();
|
void startAsyncRead();
|
||||||
void processIncomingMessage(const std::string& msg);
|
void processIncomingMessage(const std::string& msg);
|
||||||
|
|
||||||
@ -33,10 +40,18 @@ namespace ZL {
|
|||||||
void Connect(const std::string& host, uint16_t port) override;
|
void Connect(const std::string& host, uint16_t port) override;
|
||||||
|
|
||||||
void Poll() override;
|
void Poll() override;
|
||||||
|
void parseWorldUpdate(const std::string& msg);
|
||||||
|
void updateRemotePlayer(int id, const std::vector<std::string>& vals);
|
||||||
|
|
||||||
void Send(const std::string& message) override;
|
void Send(const std::string& message) override;
|
||||||
|
void doWrite();
|
||||||
|
|
||||||
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::lock_guard<std::mutex> lock(playersMutex);
|
||||||
|
return remotePlayers;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user