space-game001/src/Game.cpp
2026-01-17 15:03:37 +03:00

1186 lines
43 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "Game.h"
#include "AnimatedModel.h"
#include "BoneAnimatedModel.h"
#include "utils/Utils.h"
#include "render/OpenGlExtensions.h"
#include <iostream>
#include "render/TextureManager.h"
#include "TextModel.h"
#include <random>
#include <cmath>
#include <algorithm>
#ifdef __ANDROID__
#include <android/log.h>
#endif
#ifdef NETWORK
#include "network/WebSocketClient.h"
#else
#include "network/LocalClient.h"
#endif
#include "network/RemotePlayer.h"
namespace ZL
{
#ifdef EMSCRIPTEN
const char* CONST_ZIP_FILE = "resources.zip";
#else
const char* CONST_ZIP_FILE = "";
#endif
static bool g_exitBgAnimating = false;
float x = 0;
Eigen::Quaternionf generateRandomQuaternion(std::mt19937& gen)
{
std::normal_distribution<> distrib(0.0, 1.0);
Eigen::Quaternionf randomQuat = {
(float)distrib(gen),
(float)distrib(gen),
(float)distrib(gen),
(float)distrib(gen)
};
return randomQuat.normalized();
}
std::vector<BoxCoords> generateRandomBoxCoords(int N)
{
const float MIN_DISTANCE = 3.0f;
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
const float MIN_COORD = -100.0f;
const float MAX_COORD = 100.0f;
const int MAX_ATTEMPTS = 1000;
std::vector<BoxCoords> boxCoordsArr;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> distrib(MIN_COORD, MAX_COORD);
int generatedCount = 0;
while (generatedCount < N)
{
bool accepted = false;
int attempts = 0;
while (!accepted && attempts < MAX_ATTEMPTS)
{
Vector3f newPos(
(float)distrib(gen),
(float)distrib(gen),
(float)distrib(gen)
);
accepted = true;
for (const auto& existingBox : boxCoordsArr)
{
Vector3f diff = newPos - existingBox.pos;
float distanceSquared = diff.squaredNorm();
if (distanceSquared < MIN_DISTANCE_SQUARED)
{
accepted = false;
break;
}
}
if (accepted)
{
Eigen::Quaternionf randomQuat = generateRandomQuaternion(gen);
Matrix3f randomMatrix = randomQuat.toRotationMatrix();
boxCoordsArr.emplace_back(BoxCoords{ newPos, randomMatrix });
generatedCount++;
}
attempts++;
}
if (!accepted) {
break;
}
}
return boxCoordsArr;
}
Game::Game()
: window(nullptr)
, glContext(nullptr)
, newTickCount(0)
, lastTickCount(0)
, planetObject(renderer, taskManager, mainThreadHandler)
{
projectiles.reserve(maxProjectiles);
for (int i = 0; i < maxProjectiles; ++i) {
projectiles.emplace_back(std::make_unique<Projectile>());
}
}
Game::~Game() {
if (glContext) {
SDL_GL_DeleteContext(glContext);
}
if (window) {
SDL_DestroyWindow(window);
}
SDL_Quit();
}
void Game::setup() {
glContext = SDL_GL_CreateContext(ZL::Environment::window);
ZL::BindOpenGlFunctions();
ZL::CheckGlError();
#ifndef SIMPLIFIED
renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_web.fragment", CONST_ZIP_FILE);
#else
renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/default_env.vertex", "resources/shaders/default_env_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "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);
renderer.shaderManager.AddShaderFromFiles("planetStone", "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);
#endif
bool cfgLoaded = sparkEmitter.loadFromJsonFile("resources/config/spark_config.json", renderer, CONST_ZIP_FILE);
bool projCfgLoaded = projectileEmitter.loadFromJsonFile("resources/config/spark_projectile_config.json", renderer, CONST_ZIP_FILE);
bool explosionCfgLoaded = explosionEmitter.loadFromJsonFile("resources/config/explosion_config.json", renderer, CONST_ZIP_FILE);
explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
uiManager.loadFromFile("resources/config/ui.json", renderer, CONST_ZIP_FILE);
uiManager.startAnimationOnNode("backgroundNode", "bgScroll");
static bool isExitButtonAnimating = false;
uiManager.setAnimationCallback("settingsButton", "buttonsExit", [this]() {
std::cerr << "Settings button animation finished -> переход в настройки" << std::endl;
if (uiManager.pushMenuFromFile("resources/config/settings.json", this->renderer, CONST_ZIP_FILE)) {
uiManager.setButtonCallback("Opt1", [this](const std::string& n) {
std::cerr << "Opt1 pressed: " << n << std::endl;
});
uiManager.setButtonCallback("Opt2", [this](const std::string& n) {
std::cerr << "Opt2 pressed: " << n << std::endl;
});
uiManager.setButtonCallback("backButton", [this](const std::string& n) {
uiManager.stopAllAnimations();
uiManager.popMenu();
});
}
else {
std::cerr << "Failed to open settings menu after animations" << std::endl;
}
});
uiManager.setAnimationCallback("exitButton", "bgScroll", []() {
std::cerr << "Exit button bgScroll animation finished" << std::endl;
g_exitBgAnimating = false;
});
// Set UI button callbacks
uiManager.setButtonCallback("playButton", [this](const std::string& name) {
std::cerr << "Play button pressed: " << name << std::endl;
});
uiManager.setButtonCallback("settingsButton", [this](const std::string& name) {
std::cerr << "Settings button pressed: " << name << std::endl;
uiManager.startAnimationOnNode("playButton", "buttonsExit");
uiManager.startAnimationOnNode("settingsButton", "buttonsExit");
uiManager.startAnimationOnNode("exitButton", "buttonsExit");
});
uiManager.setButtonCallback("exitButton", [this](const std::string& name) {
std::cerr << "Exit button pressed: " << name << std::endl;
if (!g_exitBgAnimating) {
std::cerr << "start repeat anim bgScroll on exitButton" << std::endl;
g_exitBgAnimating = true;
uiManager.startAnimationOnNode("exitButton", "bgScroll");
}
else {
std::cerr << "stop repeat anim bgScroll on exitButton" << std::endl;
g_exitBgAnimating = false;
uiManager.stopAnimationOnNode("exitButton", "bgScroll");
auto exitButton = uiManager.findButton("exitButton");
if (exitButton) {
exitButton->animOffsetX = 0.0f;
exitButton->animOffsetY = 0.0f;
exitButton->animScaleX = 1.0f;
exitButton->animScaleY = 1.0f;
exitButton->buildMesh();
}
}
});
uiManager.setButtonCallback("shootButton", [this](const std::string& name) {
uint64_t now = SDL_GetTicks64();
if (now - lastProjectileFireTime >= static_cast<uint64_t>(projectileCooldownMs)) {
lastProjectileFireTime = now;
fireProjectiles();
}
});
uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) {
int newVel = roundf(value * 10);
if (newVel != Environment::shipSelectedVelocity) {
Environment::shipSelectedVelocity = newVel;
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()
).count();
std::string msg = "VEL:" + std::to_string(now_ms) + ":" + std::to_string(Environment::shipSelectedVelocity);
msg = msg + ":" + formPingMessageContent();
networkClient->Send(msg);
}
});
cubemapTexture = std::make_shared<Texture>(
std::array<TextureDataStruct, 6>{
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE)
});
cubemap.data = ZL::CreateCubemap(500);
cubemap.RefreshVBO();
//Load texture
//spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/DefaultMaterial_BaseColor_shine.png", CONST_ZIP_FILE));
//spaceshipBase = LoadFromTextFile02("resources/spaceship006.txt", CONST_ZIP_FILE);
//spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());
//spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/cap_D.png", CONST_ZIP_FILE));
//spaceshipBase = LoadFromTextFile02("./resources/spaceship006x.txt", CONST_ZIP_FILE);
//spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());
spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/MainCharacter_Base_color_sRGB.png", CONST_ZIP_FILE));
spaceshipBase = LoadFromTextFile02("resources/spaceshipnew001.txt", CONST_ZIP_FILE);
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 });
spaceship.AssignFrom(spaceshipBase);
spaceship.RefreshVBO();
//Boxes
boxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/box/box.png", CONST_ZIP_FILE));
boxBase = LoadFromTextFile02("resources/box/box.txt", CONST_ZIP_FILE);
boxCoordsArr = generateRandomBoxCoords(50);
boxRenderArr.resize(boxCoordsArr.size());
for (int i = 0; i < boxCoordsArr.size(); i++)
{
boxRenderArr[i].AssignFrom(boxBase);
//boxRenderArr[i].data = CreateBaseConvexPolyhedron(1999);
boxRenderArr[i].RefreshVBO();
}
boxAlive.resize(boxCoordsArr.size(), true);
if (!cfgLoaded)
{
throw std::runtime_error("Failed to load spark emitter config file!");
}
renderer.InitOpenGL();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
planetObject.init();
#ifdef NETWORK
networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
networkClient->Connect("127.0.0.1", 8080);
#else
networkClient = std::make_unique<LocalClient>();
networkClient->Connect("", 0);
#endif
}
void Game::drawCubemap(float skyPercent)
{
static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env_sky";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
static const std::string skyPercentUniformName = "skyPercent";
renderer.shaderManager.PushShader(envShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1f(skyPercentUniformName, skyPercent);
renderer.EnableVertexAttribArray(vPositionName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.RotateMatrix(Environment::inverseShipMatrix);
Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();
Matrix3f viewMatrix = Environment::inverseShipMatrix;
Vector3f viewLightDir = (viewMatrix * worldLightDir).normalized();
// Передаем вектор НА источник света
Vector3f lightToSource = -viewLightDir;
renderer.RenderUniform3fv("uLightDirView", lightToSource.data());
// 2. Базовый цвет атмосферы (голубой)
Vector3f skyColor = { 0.0f, 0.5f, 1.0f };
renderer.RenderUniform3fv("uSkyColor", skyColor.data());
// 1. Вектор направления от центра планеты к игроку (в мировых координатах)
// Предполагаем, что планета в (0,0,0). Если нет, то (shipPosition - planetCenter)
Vector3f playerDirWorld = Environment::shipPosition.normalized();
// 2. Направление света в мировом пространстве
//Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();
// 3. Считаем глобальную освещенность для игрока (насколько он на свету)
// Это одно число для всего кадра
float playerLightFactor = playerDirWorld.dot(-worldLightDir);
// Ограничиваем и делаем переход мягче
playerLightFactor = std::clamp((playerLightFactor + 0.2f) / 1.2f, 0.0f, 1.0f);
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
renderer.RenderUniform1f("skyPercent", skyPercent);
CheckGlError();
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture->getTexID());
renderer.DrawVertexRenderStruct(cubemap);
CheckGlError();
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader();
CheckGlError();
}
void Game::drawShip()
{
static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset
if (shipAlive) {
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
renderer.DrawVertexRenderStruct(spaceship);
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
for (const auto& p : projectiles) {
if (p && p->isActive()) {
p->draw(renderer);
}
}
if (shipAlive) {
renderer.PushMatrix();
renderer.TranslateMatrix({ 0, 0, 16 });
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
renderer.PopMatrix();
projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
}
if (showExplosion) {
explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
}
glDisable(GL_BLEND);
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader();
CheckGlError();
}
void Game::drawBoxes()
{
static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
for (int i = 0; i < boxCoordsArr.size(); i++)
{
if (!boxAlive[i]) continue;
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipPosition);
renderer.TranslateMatrix({ 0.f, 0.f, 45000.f });
renderer.TranslateMatrix(boxCoordsArr[i].pos);
renderer.RotateMatrix(boxCoordsArr[i].m);
glBindTexture(GL_TEXTURE_2D, boxTexture->getTexID());
//glBindTexture(GL_TEXTURE_2D, rockTexture->getTexID());
renderer.DrawVertexRenderStruct(boxRenderArr[i]);
renderer.PopMatrix();
}
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader();
CheckGlError();
}
void Game::drawUI()
{
static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
glClear(GL_DEPTH_BUFFER_BIT);
renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
glEnable(GL_BLEND);
uiManager.draw(renderer);
glDisable(GL_BLEND);
renderer.shaderManager.PopShader();
CheckGlError();
}
void Game::drawScene() {
static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glViewport(0, 0, Environment::width, Environment::height);
CheckGlError();
float skyPercent = 0.0;
float distance = planetObject.distanceToPlanetSurface(Environment::shipPosition);
if (distance > 1500.f)
{
skyPercent = 0.0f;
}
else if (distance < 800.f)
{
skyPercent = 1.0f;
}
else
{
skyPercent = (1500.f - distance) / (1500.f - 800.f);
}
drawCubemap(skyPercent);
planetObject.draw(renderer);
if (planetObject.distanceToPlanetSurface(Environment::shipPosition) > 100.f)
{
glClear(GL_DEPTH_BUFFER_BIT);
}
drawShip();
drawRemoteShips();
drawBoxes();
drawUI();
CheckGlError();
}
void Game::drawRemoteShips() {
// Используем те же константы имен для шейдеров, что и в drawShip
static const std::string defaultShaderName = "default";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
// Активируем шейдер и текстуру (предполагаем, что меш у всех одинаковый)
renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
// Биндим текстуру корабля один раз для всех удаленных игроков (оптимизация батчинга)
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
// Итерируемся по актуальным данным из extrapolateRemotePlayers
for (auto const& [id, playerState] : latestRemotePlayers) {
//if (id == networkClient->GetClientId()) continue; // Не рисуем себя через этот цикл
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipPosition);
Eigen::Vector3f relativePos = playerState.position;// -Environment::shipPosition;
renderer.TranslateMatrix(relativePos);
// 3. Поворот врага
renderer.RotateMatrix(playerState.rotation);
/*
// 1. Камера и вид игрока
renderer.TranslateMatrix({ 0, 0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Environment::inverseShipMatrix);
// 2. Относительная позиция (ОЧЕНЬ ВАЖНО)
// Мы перемещаем объект в позицию (Враг - Я)
// Если враг в 44928, а я в 45000, результат будет -72 по Z.
// Поскольку камера смотрит в -Z, корабль должен быть ПЕРЕД нами.
Eigen::Vector3f relativePos = playerState.position - Environment::shipPosition;
renderer.TranslateMatrix(relativePos);
// 3. Поворот врага
renderer.RotateMatrix(playerState.rotation);
// 4. Смещение самого меша (если оно есть в drawShip, оно должно быть и тут)
// В твоем drawShip() есть: renderer.TranslateMatrix({ 0, -6.f, 0 });
renderer.TranslateMatrix({ 0, -6.f, 0 });
*/
renderer.DrawVertexRenderStruct(spaceship);
renderer.PopMatrix();
}
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader();
CheckGlError();
}
void Game::processTickCount() {
if (lastTickCount == 0) {
lastTickCount = SDL_GetTicks64();
return;
}
newTickCount = SDL_GetTicks64();
if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) {
size_t delta = (newTickCount - lastTickCount > CONST_MAX_TIME_INTERVAL) ?
CONST_MAX_TIME_INTERVAL : newTickCount - lastTickCount;
sparkEmitter.update(static_cast<float>(delta));
planetObject.update(static_cast<float>(delta));
extrapolateRemotePlayers();
static float pingTimer = 0.0f;
pingTimer += delta;
if (pingTimer >= 1000.0f) {
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_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);
*/
std::string pingMsg = "PING:" + std::to_string(now_ms) + ":" + formPingMessageContent();
networkClient->Send(pingMsg);
std::cout << "Sending: " << pingMsg << std::endl;
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 * SHIP_ACCEL;
if (Environment::shipVelocity > shipDesiredVelocity)
{
Environment::shipVelocity = shipDesiredVelocity;
}
}
else if (Environment::shipVelocity > shipDesiredVelocity)
{
Environment::shipVelocity -= delta * SHIP_ACCEL;
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;
std::cout << "OUTPUT discreteAngle=" << discreteAngle << std::endl;
// 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::system_clock::now().time_since_epoch()
).count();
// Формируем сетевой пакет
// Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации
std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag);
msg = msg + ":" + formPingMessageContent();
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 {
// Если джойстик не зажат — сбрасываем дискретные значения и плавно замедляем вращение
int discreteAngle = -1;
float discreteMag = 0.0f;
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::system_clock::now().time_since_epoch()
).count();
// Формируем сетевой пакет
// Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации
std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag);
msg = msg + ":" + formPingMessageContent();
networkClient->Send(msg);
}
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();
}
}
//std::cout << "shipVelocity=" << Environment::shipVelocity << " delta=" << delta << std::endl;
// Движение вперед (существующая логика)
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) {
if (p && p->isActive()) {
p->update(static_cast<float>(delta), renderer);
}
}
std::vector<Vector3f> projCameraPoints;
for (const auto& p : projectiles) {
if (p && p->isActive()) {
Vector3f worldPos = p->getPosition();
Vector3f rel = worldPos - Environment::shipPosition;
Vector3f camPos = Environment::inverseShipMatrix * rel;
projCameraPoints.push_back(camPos);
}
}
if (!projCameraPoints.empty()) {
projectileEmitter.setEmissionPoints(projCameraPoints);
projectileEmitter.emit();
}
else {
projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
}
std::vector<Vector3f> shipCameraPoints;
for (const auto& lp : shipLocalEmissionPoints) {
Vector3f adjusted = lp + Vector3f{ 0.0f, -Environment::zoom * 0.03f, 0.0f };
shipCameraPoints.push_back(adjusted);
}
if (!shipCameraPoints.empty()) {
sparkEmitter.setEmissionPoints(shipCameraPoints);
}
sparkEmitter.update(static_cast<float>(delta));
projectileEmitter.update(static_cast<float>(delta));
explosionEmitter.update(static_cast<float>(delta));
if (shipAlive) {
float distToSurface = planetObject.distanceToPlanetSurface(Environment::shipPosition);
if (distToSurface <= 0.0f) {
Vector3f localForward = { 0,0,-1 };
Vector3f worldForward = (Environment::shipMatrix * localForward).normalized();
const float backDistance = 400.0f;
Environment::shipPosition = Environment::shipPosition - worldForward * backDistance;
shipAlive = false;
gameOver = true;
Environment::shipVelocity = 0.0f;
showExplosion = true;
explosionEmitter.setUseWorldSpace(false);
explosionEmitter.setEmissionPoints(std::vector<Vector3f>{ Vector3f{ 0.0f,0.0f,0.0f } });
explosionEmitter.emit();
std::cerr << "GAME OVER: collision with planet (moved back and exploded)\n";
if (!uiGameOverShown) {
if (uiManager.pushMenuFromFile("resources/config/game_over.json", this->renderer, CONST_ZIP_FILE)) {
uiManager.setButtonCallback("restartButton", [this](const std::string& name) {
this->shipAlive = true;
this->gameOver = false;
this->uiGameOverShown = false;
this->showExplosion = false;
this->explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
Environment::shipPosition = Vector3f{ 0, 0, 45000.f };
Environment::shipVelocity = 0.0f;
uiManager.popMenu();
std::cerr << "Game restarted\n";
});
uiManager.setButtonCallback("gameOverExitButton", [this](const std::string& name) {
Environment::exitGameLoop = true;
});
uiGameOverShown = true;
}
else {
std::cerr << "Failed to load game_over.json\n";
}
}
}
}
for (int i = 0; i < boxCoordsArr.size(); ++i) {
if (!boxAlive[i]) continue;
Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.0f, 0.0f, 45000.0f };
Vector3f diff = Environment::shipPosition - boxWorld;
float thresh = shipCollisionRadius + boxCollisionRadius;
if (diff.squaredNorm() <= thresh * thresh) {
boxAlive[i] = false;
boxRenderArr[i].data.PositionData.clear();
boxRenderArr[i].vao.reset();
boxRenderArr[i].positionVBO.reset();
boxRenderArr[i].texCoordVBO.reset();
showExplosion = true;
Vector3f rel = boxWorld - Environment::shipPosition;
Vector3f camPos = Environment::inverseShipMatrix * rel;
explosionEmitter.setUseWorldSpace(true);
explosionEmitter.setEmissionPoints(std::vector<Vector3f>{ boxWorld });
explosionEmitter.emit();
std::cerr << "Box destroyed at index " << i << std::endl;
}
}
uiManager.update(static_cast<float>(delta));
lastTickCount = newTickCount;
}
}
void Game::fireProjectiles() {
std::vector<Vector3f> localOffsets = {
Vector3f{ -1.5f, 0.9f, 5.0f },
Vector3f{ 1.5f, 0.9f, 5.0f }
};
const float projectileSpeed = 60.0f;
const float lifeMs = 5000.0f;
const float size = 0.5f;
Vector3f localForward = { 0,0,-1 };
Vector3f worldForward = (Environment::shipMatrix * localForward).normalized();
for (const auto& lo : localOffsets) {
Vector3f worldPos = Environment::shipPosition + Environment::shipMatrix * lo;
Vector3f worldVel = worldForward * (projectileSpeed + Environment::shipVelocity);
for (auto& p : projectiles) {
if (!p->isActive()) {
p->init(worldPos, worldVel, lifeMs, size, projectileTexture, renderer);
break;
}
}
}
}
void Game::render() {
SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
ZL::CheckGlError();
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
drawScene();
processTickCount();
SDL_GL_SwapWindow(ZL::Environment::window);
}
void Game::update() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
Environment::exitGameLoop = true;
}
#ifdef __ANDROID__
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) {
Environment::exitGameLoop = true;
}
#endif
#ifdef __ANDROID__
if (event.type == SDL_FINGERDOWN) {
// Координаты Finger в SDL нормализованы от 0.0 до 1.0
int mx = static_cast<int>(event.tfinger.x * Environment::width);
int my = static_cast<int>(event.tfinger.y * Environment::height);
handleDown(mx, my);
}
else if (event.type == SDL_FINGERUP) {
int mx = static_cast<int>(event.tfinger.x * Environment::width);
int my = static_cast<int>(event.tfinger.y * Environment::height);
handleUp(mx, my);
}
else if (event.type == SDL_FINGERMOTION) {
int mx = static_cast<int>(event.tfinger.x * Environment::width);
int my = static_cast<int>(event.tfinger.y * Environment::height);
handleMotion(mx, my);
}
#else
if (event.type == SDL_MOUSEBUTTONDOWN) {
int mx = event.button.x;
int my = event.button.y;
handleDown(mx, my);
}
if (event.type == SDL_MOUSEBUTTONUP) {
int mx = event.button.x;
int my = event.button.y;
handleUp(mx, my);
}
if (event.type == SDL_MOUSEMOTION) {
int mx = event.motion.x;
int my = event.motion.y;
handleMotion(mx, my);
}
/*
if (event.type == SDL_MOUSEWHEEL) {
static const float zoomstep = 2.0f;
if (event.wheel.y > 0) {
Environment::zoom -= zoomstep;
}
else if (event.wheel.y < 0) {
Environment::zoom += zoomstep;
}
if (Environment::zoom < zoomstep) {
Environment::zoom = zoomstep;
}
}
if (event.type == SDL_KEYUP)
{
if (event.key.keysym.sym == SDLK_i)
{
}
}*/
#endif
}
render();
mainThreadHandler.processMainThreadTasks();
networkClient->Poll();
}
void Game::handleDown(int mx, int my)
{
int uiX = mx;
int uiY = Environment::height - my;
uiManager.onMouseDown(uiX, uiY);
bool uiHandled = false;
for (const auto& button : uiManager.findButton("") ? std::vector<std::shared_ptr<UiButton>>{} : std::vector<std::shared_ptr<UiButton>>{}) {
(void)button;
}
auto pressedSlider = [&]() -> std::shared_ptr<UiSlider> {
for (const auto& slider : uiManager.findSlider("") ? std::vector<std::shared_ptr<UiSlider>>{} : std::vector<std::shared_ptr<UiSlider>>{}) {
(void)slider;
}
return nullptr;
}();
if (!uiManager.isUiInteraction()) {
Environment::tapDownHold = true;
Environment::tapDownStartPos(0) = mx;
Environment::tapDownStartPos(1) = my;
Environment::tapDownCurrentPos(0) = mx;
Environment::tapDownCurrentPos(1) = my;
}
}
void Game::handleUp(int mx, int my)
{
int uiX = mx;
int uiY = Environment::height - my;
uiManager.onMouseUp(uiX, uiY);
if (!uiManager.isUiInteraction()) {
Environment::tapDownHold = false;
}
}
void Game::handleMotion(int mx, int my)
{
int uiX = mx;
int uiY = Environment::height - my;
uiManager.onMouseMove(uiX, uiY);
if (Environment::tapDownHold && !uiManager.isUiInteraction()) {
Environment::tapDownCurrentPos(0) = mx;
Environment::tapDownCurrentPos(1) = my;
}
}
void Game::extrapolateRemotePlayers() {
auto now = std::chrono::system_clock::now();
latestRemotePlayers = networkClient->getRemotePlayers();
for (auto& [id, rp] : latestRemotePlayers) {
// 1. Рассчитываем, сколько времени прошло с момента получения последнего пакета (в секундах)
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 (deltaMs > 2000) deltaMs = 2000;
// 2. Сбрасываем физическое состояние rp.state в значения из последнего пакета
// (Это важно: мы всегда экстраполируем от последнего достоверного серверного состояния)
// В WebSocketClient::updateRemotePlayer нужно убедиться, что rp.state обновляется данными из пакета.
// 3. Вызываем физику, чтобы "догнать" реальное время
// Мы передаем age_s как один большой шаг симуляции
rp.simulate_physics(deltaMs);
rp.lastUpdateServerTime = now;
// Теперь rp.state.position и rp.state.rotation содержат актуальные
// предсказанные данные для рендеринга в текущем кадре.
}
networkClient->updateRemotePlayers(latestRemotePlayers);
}
std::string Game::formPingMessageContent()
{
Eigen::Quaternionf q(Environment::shipMatrix);
std::string pingMsg = 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);
return pingMsg;
}
} // namespace ZL