Merge branch 'main' into linux

This commit is contained in:
Vladislav Khorev 2026-03-09 10:52:04 +00:00
commit e5d7885a12
48 changed files with 1109 additions and 895 deletions

View File

@ -73,6 +73,8 @@ set(SOURCES
../src/MenuManager.cpp ../src/MenuManager.cpp
../src/Space.h ../src/Space.h
../src/Space.cpp ../src/Space.cpp
../src/GameConstants.h
../src/GameConstants.cpp
) )
add_executable(space-game001 ${SOURCES}) add_executable(space-game001 ${SOURCES})

View File

@ -67,6 +67,8 @@ add_executable(space-game001
../src/MenuManager.cpp ../src/MenuManager.cpp
../src/Space.h ../src/Space.h
../src/Space.cpp ../src/Space.cpp
../src/GameConstants.h
../src/GameConstants.cpp
) )
# Установка проекта по умолчанию для Visual Studio # Установка проекта по умолчанию для Visual Studio

BIN
resources/button_minus_disabled.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/button_plus_disabled.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,22 @@
{
"root": {
"type": "FrameLayout",
"x": 0,
"y": 0,
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "StaticImage",
"name": "connecting",
"x" : 0,
"y" : 0,
"width": 488,
"height": 154,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"texture": "resources/connecting.png"
}
]
}
}

View File

@ -0,0 +1,52 @@
{
"root": {
"type": "FrameLayout",
"x": 0,
"y": 0,
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "StaticImage",
"name": "connectionFailed",
"x" : 0,
"y" : 0,
"width": 488,
"height": 308,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"texture": "resources/connection_failed.png"
},
{
"type": "Button",
"name": "connectionFailedReconnectButton",
"width": 382,
"height": 56,
"x" : 0,
"y" : -20,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"textures": {
"normal": "resources/game_over/Filledbuttons.png",
"hover": "resources/game_over/Variant5.png",
"pressed": "resources/game_over/Variant6.png"
}
},
{
"type": "Button",
"name": "connectionFailedGoBack",
"width": 382,
"height": 56,
"x" : 0,
"y" : -86,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"textures": {
"normal": "resources/game_over/Secondarybutton.png",
"hover": "resources/game_over/Secondarybutton.png",
"pressed": "resources/game_over/Secondarybutton.png"
}
}
]
}
}

View File

@ -0,0 +1,52 @@
{
"root": {
"type": "FrameLayout",
"x": 0,
"y": 0,
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "StaticImage",
"name": "connectionLost",
"x" : 0,
"y" : 0,
"width": 488,
"height": 308,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"texture": "resources/connection_lost.png"
},
{
"type": "Button",
"name": "reconnectButton",
"width": 382,
"height": 56,
"x" : 0,
"y" : -20,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"textures": {
"normal": "resources/game_over/Filledbuttons.png",
"hover": "resources/game_over/Variant5.png",
"pressed": "resources/game_over/Variant6.png"
}
},
{
"type": "Button",
"name": "exitServerButton",
"width": 382,
"height": 56,
"x" : 0,
"y" : -86,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"textures": {
"normal": "resources/game_over/Secondarybutton.png",
"hover": "resources/game_over/Secondarybutton.png",
"pressed": "resources/game_over/Secondarybutton.png"
}
}
]
}
}

View File

@ -5,7 +5,7 @@
"texture": "resources/spark_white.png", "texture": "resources/spark_white.png",
"speedRange": [10.0, 30.0], "speedRange": [10.0, 30.0],
"zSpeedRange": [-1.0, 1.0], "zSpeedRange": [-1.0, 1.0],
"scaleRange": [0.5, 1.0], "scaleRange": [5.0, 10.0],
"lifeTimeRange": [200.0, 800.0], "lifeTimeRange": [200.0, 800.0],
"emissionRate": 50.0, "emissionRate": 50.0,
"maxParticles": 5, "maxParticles": 5,

View File

@ -11,37 +11,25 @@
"height": "match_parent", "height": "match_parent",
"children": [ "children": [
{ {
"type": "Button", "type": "StaticImage",
"name": "titleBtn", "name": "titleBtn",
"width": 254, "width": 434,
"height": 35, "height": 35,
"textures": { "texture": "resources/main_menu/title.png"
"normal": "resources/main_menu/title.png",
"hover": "resources/main_menu/title.png",
"pressed": "resources/main_menu/title.png"
}
}, },
{ {
"type": "Button", "type": "StaticImage",
"name": "underlineBtn", "name": "underlineBtn",
"width": 168, "width": 168,
"height": 44, "height": 44,
"textures": { "texture": "resources/main_menu/line.png"
"normal": "resources/main_menu/line.png",
"hover": "resources/main_menu/line.png",
"pressed": "resources/main_menu/line.png"
}
}, },
{ {
"type": "Button", "type": "StaticImage",
"name": "subtitleBtn", "name": "subtitleBtn",
"width": 144, "width": 144,
"height": 11, "height": 11,
"textures": { "texture": "resources/main_menu/subtitle.png"
"normal": "resources/main_menu/subtitle.png",
"hover": "resources/main_menu/subtitle.png",
"pressed": "resources/main_menu/subtitle.png"
}
}, },
{ {
"type": "Button", "type": "Button",
@ -66,15 +54,11 @@
} }
}, },
{ {
"type": "Button", "type": "StaticImage",
"name": "versionLabel", "name": "versionLabel",
"width": 81, "width": 81,
"height": 9, "height": 9,
"textures": { "texture": "resources/main_menu/version.png"
"normal": "resources/main_menu/version.png",
"hover": "resources/main_menu/version.png",
"pressed": "resources/main_menu/version.png"
}
} }
] ]
} }

View File

@ -10,6 +10,13 @@
"width": "match_parent", "width": "match_parent",
"height": "match_parent", "height": "match_parent",
"children": [ "children": [
{
"type": "StaticImage",
"name": "titleBtn",
"width": 266,
"height": 66,
"texture": "resources/select_your_ship.png"
},
{ {
"type": "LinearLayout", "type": "LinearLayout",
"orientation": "horizontal", "orientation": "horizontal",

View File

@ -1,20 +1,14 @@
{ {
"emissionRate": 10.0, "emissionRate": 1.2,
"maxParticles": 200, "maxParticles": 400,
"particleSize": 0.3, "particleSize": 0.3,
"biasX": 0.3, "biasX": 0.3,
"emissionPoints": [ "emissionPoints": [
{
"position": [-1.0, 1.4, -3.5]
},
{
"position": [1.0, 1.4, -3.5]
}
], ],
"speedRange": [0.5, 2.0], "speedRange": [0.5, 2.0],
"zSpeedRange": [1.0, 3.0], "zSpeedRange": [1.0, 3.0],
"scaleRange": [0.8, 1.2], "scaleRange": [0.8, 1.2],
"lifeTimeRange": [600.0, 1400.0], "lifeTimeRange": [300.0, 500.0],
"texture": "resources/spark.png", "texture": "resources/spark.png",
"shaderProgramName": "spark" "shaderProgramName": "spark"
} }

View File

@ -1,6 +1,6 @@
{ {
"emissionRate": 10.0, "emissionRate": 1.2,
"maxParticles": 200, "maxParticles": 400,
"particleSize": 0.3, "particleSize": 0.3,
"biasX": 0.3, "biasX": 0.3,
"emissionPoints": [ "emissionPoints": [

View File

@ -31,7 +31,7 @@
"vertical_gravity": "bottom", "vertical_gravity": "bottom",
"textures": { "textures": {
"normal": "resources/fire.png", "normal": "resources/fire.png",
"hover": "resources/fire2.png", "hover": "resources/fire.png",
"pressed": "resources/fire2.png", "pressed": "resources/fire2.png",
"disabled": "resources/fire_disabled.png" "disabled": "resources/fire_disabled.png"
} }
@ -47,7 +47,7 @@
"vertical_gravity": "bottom", "vertical_gravity": "bottom",
"textures": { "textures": {
"normal": "resources/fire.png", "normal": "resources/fire.png",
"hover": "resources/fire2.png", "hover": "resources/fire.png",
"pressed": "resources/fire2.png", "pressed": "resources/fire2.png",
"disabled": "resources/fire_disabled.png" "disabled": "resources/fire_disabled.png"
} }
@ -65,7 +65,8 @@
"textures": { "textures": {
"normal": "resources/button_minus.png", "normal": "resources/button_minus.png",
"hover": "resources/button_minus_pressed.png", "hover": "resources/button_minus_pressed.png",
"pressed": "resources/button_minus_pressed.png" "pressed": "resources/button_minus_pressed.png",
"disabled" : "resources/button_minus_disabled.png"
} }
}, },
{ {
@ -81,7 +82,8 @@
"textures": { "textures": {
"normal": "resources/button_plus.png", "normal": "resources/button_plus.png",
"hover": "resources/button_plus_pressed.png", "hover": "resources/button_plus_pressed.png",
"pressed": "resources/button_plus_pressed.png" "pressed": "resources/button_plus_pressed.png",
"disabled" : "resources/button_plus_disabled.png"
} }
} }
] ]

BIN
resources/connecting.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/connection_failed.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/connection_lost.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/loading.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/main_menu/about.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/main_menu/about_hover.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/main_menu/about_pressed.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resources/main_menu/title.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/select_your_ship.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -13,8 +13,8 @@ namespace ZL {
int Environment::windowHeaderHeight = 0; int Environment::windowHeaderHeight = 0;
int Environment::width = 0; int Environment::width = CONST_DEFAULT_WIDTH;
int Environment::height = 0; int Environment::height = CONST_DEFAULT_HEIGHT;
float Environment::zoom = DEFAULT_ZOOM; float Environment::zoom = DEFAULT_ZOOM;
@ -49,10 +49,10 @@ void Environment::computeProjectionDimensions()
if (width >= height) { if (width >= height) {
// Landscape: fix height to 720, scale width to preserve aspect // Landscape: fix height to 720, scale width to preserve aspect
projectionHeight = refShortSide; projectionHeight = refShortSide;
projectionWidth = refShortSide * aspect; projectionWidth = refShortSide * aspect;
} else { } else {
// Portrait: fix width to 720, scale height to preserve aspect // Portrait: fix width to 720, scale height to preserve aspect
projectionWidth = refShortSide; projectionWidth = refShortSide;
projectionHeight = refShortSide / aspect; projectionHeight = refShortSide / aspect;
} }
} }

View File

@ -14,6 +14,8 @@ namespace ZL {
class Environment { class Environment {
public: public:
static constexpr int CONST_DEFAULT_WIDTH = 1280;
static constexpr int CONST_DEFAULT_HEIGHT = 720;
static int windowHeaderHeight; static int windowHeaderHeight;
static int width; static int width;
static int height; static int height;

View File

@ -30,7 +30,7 @@
#include "network/LocalClient.h" #include "network/LocalClient.h"
#include "network/ClientState.h" #include "network/ClientState.h"
#include "GameConstants.h"
namespace ZL namespace ZL
{ {
@ -41,6 +41,10 @@ namespace ZL
const char* CONST_ZIP_FILE = ""; const char* CONST_ZIP_FILE = "";
#endif #endif
float x = 0;
float y = 0;
float z = 0;
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
Game* Game::s_instance = nullptr; Game* Game::s_instance = nullptr;
@ -86,6 +90,8 @@ namespace ZL
//glContext = SDL_GL_CreateContext(ZL::Environment::window); //glContext = SDL_GL_CreateContext(ZL::Environment::window);
//glContext = in_glContext; //glContext = in_glContext;
Environment::width = Environment::CONST_DEFAULT_WIDTH;
Environment::height = Environment::CONST_DEFAULT_HEIGHT;
Environment::computeProjectionDimensions(); Environment::computeProjectionDimensions();
ZL::BindOpenGlFunctions(); ZL::BindOpenGlFunctions();
@ -104,7 +110,20 @@ namespace ZL
loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE)); loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE));
#endif #endif
loadingMesh.data = CreateRect2D({ 0.5f, 0.5f }, { 0.5f, 0.5f }, 3); float minDimension;
float width = Environment::projectionWidth;
float height = Environment::projectionHeight;
if (width >= height)
{
minDimension = height;
}
else
{
minDimension = width;
}
loadingMesh.data = CreateRect2D({ 0.0f, 0.0f }, { minDimension*0.5f, minDimension*0.5f }, 3);
loadingMesh.RefreshVBO(); loadingMesh.RefreshVBO();
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
@ -146,11 +165,17 @@ namespace ZL
menuManager.setupMenu(); menuManager.setupMenu();
menuManager.onMainMenuEntered = [this]() {
if (networkClient) {
networkClient->Disconnect();
networkClient.reset();
}
};
menuManager.onSingleplayerPressed = [this](const std::string& nickname, int shipType) { menuManager.onSingleplayerPressed = [this](const std::string& nickname, int shipType) {
Environment::shipState.nickname = nickname; Environment::shipState.nickname = nickname;
Environment::shipState.shipType = shipType; Environment::shipState.shipType = shipType;
if (Environment::shipState.shipType == 1) if (Environment::shipState.shipType == 1)
{ {
menuManager.uiManager.findButton("shootButton")->state = ButtonState::Disabled; menuManager.uiManager.findButton("shootButton")->state = ButtonState::Disabled;
@ -170,15 +195,14 @@ namespace ZL
networkClient = std::unique_ptr<INetworkClient>(localClient); networkClient = std::unique_ptr<INetworkClient>(localClient);
networkClient->Connect("", 0); networkClient->Connect("", 0);
space.resetPlayerState();
lastTickCount = 0; lastTickCount = 0;
spaceGameStarted = 1;
}; };
menuManager.onMultiplayerPressed = [this](const std::string& nickname, int shipType) { menuManager.onMultiplayerPressed = [this](const std::string& nickname, int shipType) {
Environment::shipState.nickname = nickname; Environment::shipState.nickname = nickname;
Environment::shipState.shipType = shipType; Environment::shipState.shipType = shipType;
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
networkClient = std::make_unique<WebSocketClientEmscripten>(); networkClient = std::make_unique<WebSocketClientEmscripten>();
networkClient->Connect("localhost", 8081); networkClient->Connect("localhost", 8081);
@ -186,7 +210,7 @@ namespace ZL
networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext()); networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
networkClient->Connect("localhost", 8081); networkClient->Connect("localhost", 8081);
#endif #endif
if (networkClient) { if (networkClient) {
std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType); std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType);
networkClient->Send(joinMsg); networkClient->Send(joinMsg);
@ -195,12 +219,12 @@ namespace ZL
space.boxCoordsArr.clear(); space.boxCoordsArr.clear();
space.boxRenderArr.clear(); space.boxRenderArr.clear();
//space.boxLabels.clear();
space.boxAlive.clear(); space.boxAlive.clear();
space.serverBoxesApplied = false; space.serverBoxesApplied = false;
space.resetPlayerState();
connectingStartTicks = SDL_GetTicks();
lastTickCount = 0; lastTickCount = 0;
spaceGameStarted = 1;
}; };
@ -212,21 +236,10 @@ namespace ZL
void Game::drawUI() 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); glClear(GL_DEPTH_BUFFER_BIT);
renderer.shaderManager.PushShader(defaultShaderName); renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
glEnable(GL_BLEND); glEnable(GL_BLEND);
menuManager.uiManager.draw(renderer); menuManager.uiManager.draw(renderer);
glDisable(GL_BLEND); glDisable(GL_BLEND);
@ -242,17 +255,14 @@ namespace ZL
} }
void Game::drawScene() { void Game::drawScene() {
//For low quality:
glViewport(0, 0, Environment::width, Environment::height); glViewport(0, 0, Environment::width, Environment::height);
//For high quality:
//glViewport(0, 0, Environment::projectionWidth, Environment::projectionHeight);
if (!loadingCompleted) { if (!loadingCompleted) {
drawLoading(); drawLoading();
} }
else else
{ {
if (spaceGameStarted) { if (menuManager.shouldRenderSpace()) {
space.drawScene(); space.drawScene();
} }
else else
@ -266,24 +276,17 @@ namespace ZL
void Game::drawLoading() void Game::drawLoading()
{ {
static const std::string defaultShaderName = "default";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
glClear(GL_DEPTH_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT);
renderer.shaderManager.PushShader(defaultShaderName); renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
//float width = Environment::projectionWidth; float width = Environment::projectionWidth;
//float height = Environment::projectionHeight; float height = Environment::projectionHeight;
renderer.PushProjectionMatrix( renderer.PushProjectionMatrix(
0, 1, -width * 0.5f, width*0.5f,
0, 1, -height * 0.5f, height * 0.5f,
-10, 10); -10, 10);
renderer.PushMatrix(); renderer.PushMatrix();
@ -294,9 +297,6 @@ namespace ZL
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
} }
@ -336,7 +336,7 @@ namespace ZL
//throw std::runtime_error("Synchronization is lost"); //throw std::runtime_error("Synchronization is lost");
} }
if (spaceGameStarted) { if (menuManager.shouldRenderSpace()) {
space.processTickCount(newTickCount, delta); space.processTickCount(newTickCount, delta);
} }
menuManager.uiManager.update(static_cast<float>(delta)); menuManager.uiManager.update(static_cast<float>(delta));
@ -348,11 +348,13 @@ namespace ZL
//SDL_GL_MakeCurrent(ZL::Environment::window, glContext); //SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
ZL::CheckGlError(); ZL::CheckGlError();
glClearColor(0.0f, 0.0f, 1.0f, 1.0f); glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//processTickCount();
drawScene(); drawScene();
processTickCount(); processTickCount();
//std::this_thread::sleep_for(std::chrono::milliseconds(50));
SDL_GL_SwapWindow(ZL::Environment::window); SDL_GL_SwapWindow(ZL::Environment::window);
} }
@ -386,51 +388,55 @@ namespace ZL
if (event.type == SDL_FINGERDOWN) { if (event.type == SDL_FINGERDOWN) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth); int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight); int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleDown(mx, my); handleDown(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
} }
else if (event.type == SDL_FINGERUP) { else if (event.type == SDL_FINGERUP) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth); int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight); int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleUp(mx, my); handleUp(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
} }
else if (event.type == SDL_FINGERMOTION) { else if (event.type == SDL_FINGERMOTION) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth); int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight); int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleMotion(mx, my); handleMotion(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
} }
#else #else
// Emscripten on mobile browser: handle real touch events with per-finger IDs.
// SDL_HINT_TOUCH_MOUSE_EVENTS="0" is set in main.cpp so these don't
// also fire SDL_MOUSEBUTTONDOWN, preventing double-processing.
#ifdef EMSCRIPTEN
if (event.type == SDL_FINGERDOWN) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleDown(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
}
else if (event.type == SDL_FINGERUP) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleUp(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
}
else if (event.type == SDL_FINGERMOTION) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleMotion(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
}
#endif
if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) { if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
// Преобразуем экранные пиксели в проекционные единицы // Преобразуем экранные пиксели в проекционные единицы
int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth); int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth);
int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight); int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight);
if (event.type == SDL_MOUSEBUTTONDOWN) handleDown(mx, my); if (event.type == SDL_MOUSEBUTTONDOWN) handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
else handleUp(mx, my); else handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
//std::cout << "Mouse button " << (event.type == SDL_MOUSEBUTTONDOWN ? "down" : "up") << ": x=" << mx << " y=" << my << std::endl;
} }
else if (event.type == SDL_MOUSEMOTION) { else if (event.type == SDL_MOUSEMOTION) {
int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth); int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth);
int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight); int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
handleMotion(mx, my); handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
} }
/*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) { if (event.type == SDL_MOUSEWHEEL) {
static const float zoomstep = 2.0f; static const float zoomstep = 2.0f;
if (event.wheel.y > 0) { if (event.wheel.y > 0) {
@ -459,8 +465,8 @@ namespace ZL
} }
if (event.type == SDL_KEYUP) { if (event.type == SDL_KEYUP) {
if (event.key.keysym.sym == SDLK_a) { if (event.key.keysym.sym == SDLK_r) {
//Environment::shipState.position = { 9466.15820, 1046.00159, 18531.2090 }; std::cout << "Camera position: x=" << x << " y=" << y << " z=" << z << std::endl;
} }
} }
#endif #endif
@ -475,6 +481,22 @@ namespace ZL
} }
//#endif //#endif
networkClient->Poll(); networkClient->Poll();
if (menuManager.getState() == GameState::Connecting) {
if (networkClient->IsConnected()) {
menuManager.notifyConnected();
// Enable/disable shoot buttons based on ship type
if (Environment::shipState.shipType == 1) {
if (auto b = menuManager.uiManager.findButton("shootButton")) b->state = ButtonState::Disabled;
if (auto b = menuManager.uiManager.findButton("shootButton2")) b->state = ButtonState::Disabled;
} else {
if (auto b = menuManager.uiManager.findButton("shootButton")) b->state = ButtonState::Normal;
if (auto b = menuManager.uiManager.findButton("shootButton2")) b->state = ButtonState::Normal;
}
} else if (SDL_GetTicks() - connectingStartTicks > CONNECTING_TIMEOUT_MS) {
menuManager.notifyConnectionFailed();
}
}
#ifdef NETWORK #ifdef NETWORK
auto* wsBase = dynamic_cast<ZL::WebSocketClientBase*>(networkClient.get()); auto* wsBase = dynamic_cast<ZL::WebSocketClientBase*>(networkClient.get());
if (wsBase) { if (wsBase) {
@ -503,67 +525,58 @@ namespace ZL
} }
mainThreadHandler.processMainThreadTasks(); mainThreadHandler.processMainThreadTasks();
if (spaceGameStarted) { if (menuManager.shouldRenderSpace()) {
space.update(); space.update();
} }
} }
void Game::handleDown(int mx, int my) void Game::handleDown(int64_t fingerId, int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::projectionHeight - my; int uiY = Environment::projectionHeight - my;
menuManager.uiManager.onMouseDown(uiX, uiY); menuManager.uiManager.onTouchDown(fingerId, uiX, uiY);
bool uiHandled = false; if (!menuManager.uiManager.isUiInteractionForFinger(fingerId)) {
if (menuManager.shouldRenderSpace()) {
for (const auto& button : menuManager.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 : menuManager.uiManager.findSlider("") ? std::vector<std::shared_ptr<UiSlider>>{} : std::vector<std::shared_ptr<UiSlider>>{}) {
(void)slider;
}
return nullptr;
}();
if (!menuManager.uiManager.isUiInteraction()) {
if (spaceGameStarted) {
space.handleDown(mx, my); space.handleDown(mx, my);
} }
} }
} }
void Game::handleUp(int mx, int my) void Game::handleUp(int64_t fingerId, int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::projectionHeight - my; int uiY = Environment::projectionHeight - my;
menuManager.uiManager.onMouseUp(uiX, uiY); // Check BEFORE onTouchUp erases the finger from the map.
// If this finger started on a UI element, don't notify space —
// otherwise space would think the ship-control finger was released.
bool wasUiInteraction = menuManager.uiManager.isUiInteractionForFinger(fingerId);
menuManager.uiManager.onTouchUp(fingerId, uiX, uiY);
if (!menuManager.uiManager.isUiInteraction()) { if (!wasUiInteraction) {
if (spaceGameStarted) { if (menuManager.shouldRenderSpace()) {
space.handleUp(mx, my); space.handleUp(mx, my);
} }
} }
} }
void Game::handleMotion(int mx, int my) void Game::handleMotion(int64_t fingerId, int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::projectionHeight - my; int uiY = Environment::projectionHeight - my;
menuManager.uiManager.onMouseMove(uiX, uiY); // Check before onTouchMove so the "started on UI" state is preserved
// regardless of what onTouchMove does internally.
bool wasUiInteraction = menuManager.uiManager.isUiInteractionForFinger(fingerId);
menuManager.uiManager.onTouchMove(fingerId, uiX, uiY);
if (!menuManager.uiManager.isUiInteraction()) { if (!wasUiInteraction) {
if (spaceGameStarted) { if (menuManager.shouldRenderSpace()) {
space.handleMotion(mx, my); space.handleMotion(mx, my);
} }
} }
} }

View File

@ -13,6 +13,7 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <memory> #include <memory>
#include <cstdint>
#include <render/TextRenderer.h> #include <render/TextRenderer.h>
#include "MenuManager.h" #include "MenuManager.h"
#include "Space.h" #include "Space.h"
@ -50,9 +51,9 @@ namespace ZL {
void drawUI(); void drawUI();
void drawUnderMainMenu(); void drawUnderMainMenu();
void drawLoading(); void drawLoading();
void handleDown(int mx, int my); void handleDown(int64_t fingerId, int mx, int my);
void handleUp(int mx, int my); void handleUp(int64_t fingerId, int mx, int my);
void handleMotion(int mx, int my); void handleMotion(int64_t fingerId, int mx, int my);
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
static Game* s_instance; static Game* s_instance;
@ -65,14 +66,14 @@ namespace ZL {
int64_t newTickCount; int64_t newTickCount;
int64_t lastTickCount; int64_t lastTickCount;
uint32_t connectingStartTicks = 0;
static constexpr uint32_t CONNECTING_TIMEOUT_MS = 10000;
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;
MenuManager menuManager; MenuManager menuManager;
Space space; Space space;
int spaceGameStarted = 0;
}; };

9
src/GameConstants.cpp Normal file
View File

@ -0,0 +1,9 @@
#include "GameConstants.h"
namespace ZL
{
const std::string defaultShaderName = "default";
const std::string envShaderName = "env";
const std::string textureUniformName = "Texture";
}

11
src/GameConstants.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include "render/Renderer.h"
namespace ZL
{
extern const std::string defaultShaderName;
extern const std::string envShaderName;
extern const std::string textureUniformName;
}

View File

@ -1,5 +1,5 @@
#include "MenuManager.h" #include "MenuManager.h"
#include <iostream>
namespace ZL { namespace ZL {
@ -10,294 +10,246 @@ namespace ZL {
void MenuManager::setupMenu() void MenuManager::setupMenu()
{ {
mainMenuRoot = loadUiFromFile("resources/config/main_menu.json", renderer, CONST_ZIP_FILE);
shipSelectionRoot = loadUiFromFile("resources/config/ship_selection_menu.json", renderer, CONST_ZIP_FILE);
connectingRoot = loadUiFromFile("resources/config/connecting.json", renderer, CONST_ZIP_FILE);
connectionFailedRoot= loadUiFromFile("resources/config/connection_failed.json", renderer, CONST_ZIP_FILE);
gameplayRoot = loadUiFromFile("resources/config/ui.json", renderer, CONST_ZIP_FILE);
gameOverRoot = loadUiFromFile("resources/config/game_over.json", renderer, CONST_ZIP_FILE);
connectionLostRoot = loadUiFromFile("resources/config/connection_lost.json", renderer, CONST_ZIP_FILE);
uiManager.loadFromFile("resources/config/main_menu.json", renderer, CONST_ZIP_FILE); enterMainMenu();
}
uiSavedRoot = loadUiFromFile("resources/config/ui.json", renderer, CONST_ZIP_FILE); bool MenuManager::shouldRenderSpace() const
{
settingsSavedRoot = loadUiFromFile("resources/config/settings.json", renderer, CONST_ZIP_FILE); return state == GameState::Gameplay
|| state == GameState::GameOver
|| state == GameState::ConnectionLost;
}
multiplayerSavedRoot = loadUiFromFile("resources/config/multiplayer_menu.json", renderer, CONST_ZIP_FILE); // ── State: MainMenu ──────────────────────────────────────────────────────
gameOverSavedRoot = loadUiFromFile("resources/config/game_over.json", renderer, CONST_ZIP_FILE);
auto shipSelectionRoot = loadUiFromFile("resources/config/ship_selection_menu.json", renderer, CONST_ZIP_FILE);
std::function<void()> loadGameplayUI;
loadGameplayUI = [this]() {
uiManager.replaceRoot(uiSavedRoot);
auto velocityTv = uiManager.findTextView("velocityText"); void MenuManager::enterMainMenu()
if (velocityTv) { {
velocityTv->rect.x = 10.0f; state = GameState::MainMenu;
velocityTv->rect.y = static_cast<float>(Environment::height) - velocityTv->rect.h - 10.0f; uiManager.replaceRoot(mainMenuRoot);
}
else {
std::cerr << "Failed to find velocityText in UI" << std::endl;
}
uiManager.startAnimationOnNode("backgroundNode", "bgScroll"); if (onMainMenuEntered) onMainMenuEntered();
static bool isExitButtonAnimating = false;
uiManager.setAnimationCallback("settingsButton", "buttonsExit", [this]() {
std::cerr << "Settings button animation finished -> ??????? ? ?????????" << std::endl;
if (uiManager.pushMenuFromSavedRoot(settingsSavedRoot)) {
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", [this]() { uiManager.setButtonCallback("singleButton", [this](const std::string&) {
std::cerr << "Exit button bgScroll animation finished" << std::endl; enterShipSelectionSingle();
g_exitBgAnimating = false;
});
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) {
onFirePressed();
});
uiManager.setButtonCallback("shootButton2", [this](const std::string& name) {
onFirePressed();
});
uiManager.setButtonCallback("plusButton", [this](const std::string& name) {
int newVel = Environment::shipState.selectedVelocity+1;
if (newVel > 4)
{
newVel = 4;
}
onVelocityChanged(newVel);
});
uiManager.setButtonCallback("minusButton", [this](const std::string& name) {
int newVel = Environment::shipState.selectedVelocity-1;
if (newVel < 0)
{
newVel = 0;
}
onVelocityChanged(newVel);
});
uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) {
int newVel = roundf(value * 10);
if (newVel > 2)
{
newVel = 2;
}
if (newVel != Environment::shipState.selectedVelocity) {
onVelocityChanged(newVel);
}
});
};
uiManager.setButtonCallback("singleButton", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) {
std::cerr << "Single button pressed: " << name << " -> open ship selection UI\n";
if (!shipSelectionRoot) {
std::cerr << "Failed to load ship selection UI\n";
return;
}
if (uiManager.pushMenuFromSavedRoot(shipSelectionRoot)) {
uiManager.setButtonCallback("spaceshipButton", [this, loadGameplayUI](const std::string& btnName) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
int shipType = 0;
uiManager.popMenu();
loadGameplayUI();
if (onSingleplayerPressed) onSingleplayerPressed(nick, shipType);
});
uiManager.setButtonCallback("cargoshipButton", [this, loadGameplayUI](const std::string& btnName) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
int shipType = 1;
uiManager.popMenu();
loadGameplayUI();
if (onSingleplayerPressed) onSingleplayerPressed(nick, shipType);
});
uiManager.setButtonCallback("backButton", [this](const std::string& btnName) {
uiManager.popMenu();
});
}
else {
std::cerr << "Failed to push ship selection menu\n";
}
}); });
uiManager.setButtonCallback("multiplayerButton", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) { uiManager.setButtonCallback("multiplayerButton", [this](const std::string&) {
std::cerr << "Multiplayer button pressed: " << name << " -> open ship selection UI\n"; enterShipSelectionMulti();
if (!shipSelectionRoot) { });
std::cerr << "Failed to load ship selection UI\n"; }
return;
}
if (uiManager.pushMenuFromSavedRoot(shipSelectionRoot)) {
uiManager.setButtonCallback("spaceshipButton", [this, loadGameplayUI](const std::string& btnName) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
int shipType = 0;
uiManager.popMenu();
loadGameplayUI();
if (onMultiplayerPressed) onMultiplayerPressed(nick, shipType);
});
uiManager.setButtonCallback("cargoshipButton", [this, loadGameplayUI](const std::string& btnName) { // ── State: ShipSelectionSingle ───────────────────────────────────────────
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
int shipType = 1;
uiManager.popMenu();
loadGameplayUI();
if (onMultiplayerPressed) onMultiplayerPressed(nick, shipType);
});
uiManager.setButtonCallback("backButton", [this](const std::string& btnName) { void MenuManager::enterShipSelectionSingle()
uiManager.popMenu(); {
}); state = GameState::ShipSelectionSingle;
} uiManager.replaceRoot(shipSelectionRoot);
else {
std::cerr << "Failed to push ship selection menu\n"; uiManager.setButtonCallback("spaceshipButton", [this](const std::string&) {
} std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
enterGameplay();
if (onSingleplayerPressed) onSingleplayerPressed(nick, 0);
}); });
/*uiManager.setButtonCallback("multiplayerButton2", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) { uiManager.setButtonCallback("cargoshipButton", [this](const std::string&) {
std::cerr << "Multiplayer button pressed → opening multiplayer menu\n"; std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
enterGameplay();
if (onSingleplayerPressed) onSingleplayerPressed(nick, 1);
});
uiManager.startAnimationOnNode("playButton", "buttonsExit"); uiManager.setButtonCallback("backButton", [this](const std::string&) {
uiManager.startAnimationOnNode("settingsButton", "buttonsExit"); enterMainMenu();
uiManager.startAnimationOnNode("multiplayerButton", "buttonsExit"); });
uiManager.startAnimationOnNode("exitButton", "buttonsExit"); }
if (uiManager.pushMenuFromSavedRoot(multiplayerSavedRoot)) { // ── State: ShipSelectionMulti ─────────────────────────────────────────────
uiManager.setButtonCallback("connectButton", [this](const std::string& buttonName) { void MenuManager::enterShipSelectionMulti()
std::string serverAddress = uiManager.getTextFieldValue("serverInputField"); {
state = GameState::ShipSelectionMulti;
uiManager.replaceRoot(shipSelectionRoot);
if (serverAddress.empty()) { uiManager.setButtonCallback("spaceshipButton", [this](const std::string&) {
uiManager.setText("statusText", "Please enter server address"); std::string nick = uiManager.getTextFieldValue("nicknameInput");
return; if (nick.empty()) nick = "Player";
} pendingMultiNick = nick;
pendingMultiShipType = 0;
enterConnecting();
if (onMultiplayerPressed) onMultiplayerPressed(nick, 0);
});
uiManager.setText("statusText", "Connecting to " + serverAddress + "..."); uiManager.setButtonCallback("cargoshipButton", [this](const std::string&) {
std::cerr << "Connecting to server: " << serverAddress << std::endl; std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
pendingMultiNick = nick;
pendingMultiShipType = 1;
enterConnecting();
if (onMultiplayerPressed) onMultiplayerPressed(nick, 1);
});
}); uiManager.setButtonCallback("backButton", [this](const std::string&) {
enterMainMenu();
});
}
uiManager.setButtonCallback("backButton", [this](const std::string& buttonName) { // ── State: Connecting ────────────────────────────────────────────────────
uiManager.popMenu();
});
uiManager.setTextFieldCallback("serverInputField", void MenuManager::enterConnecting()
[this](const std::string& fieldName, const std::string& newText) { {
std::cout << "Server input field changed to: " << newText << std::endl; state = GameState::Connecting;
}); uiManager.replaceRoot(connectingRoot);
// No interactive elements — just a static "Connecting..." image
}
std::cerr << "Multiplayer menu loaded successfully\n"; // ── State: ConnectionFailed ───────────────────────────────────────────────
void MenuManager::enterConnectionFailed()
{
state = GameState::ConnectionFailed;
uiManager.replaceRoot(connectionFailedRoot);
uiManager.setButtonCallback("connectionFailedReconnectButton", [this](const std::string&) {
enterConnecting();
if (onMultiplayerPressed) onMultiplayerPressed(pendingMultiNick, pendingMultiShipType);
});
uiManager.setButtonCallback("connectionFailedGoBack", [this](const std::string&) {
enterMainMenu();
});
}
// ── State: Gameplay ──────────────────────────────────────────────────────
void MenuManager::enterGameplay()
{
state = GameState::Gameplay;
uiManager.replaceRoot(gameplayRoot);
uiManager.findButton("minusButton")->state = ButtonState::Disabled;
auto velocityTv = uiManager.findTextView("velocityText");
if (velocityTv) {
velocityTv->rect.x = 10.0f;
velocityTv->rect.y = static_cast<float>(Environment::height) - velocityTv->rect.h - 10.0f;
}
//uiManager.startAnimationOnNode("backgroundNode", "bgScroll");
uiManager.setButtonPressCallback("shootButton", [this](const std::string&) {
if (onFirePressed) onFirePressed();
});
uiManager.setButtonPressCallback("shootButton2", [this](const std::string&) {
if (onFirePressed) onFirePressed();
});
uiManager.setButtonPressCallback("plusButton", [this](const std::string&) {
int newVel = Environment::shipState.selectedVelocity + 1;
if (newVel > 4) newVel = 4;
uiManager.findButton("minusButton")->state = ButtonState::Normal;
if (newVel == 4)
{
uiManager.findButton("plusButton")->state = ButtonState::Disabled;
} }
else { else
std::cerr << "Failed to load multiplayer menu\n"; {
uiManager.findButton("plusButton")->state = ButtonState::Normal;
} }
std::cerr << "Single button pressed: " << name << " -> open ship selection UI\n"; if (onVelocityChanged) onVelocityChanged(newVel);
if (!shipSelectionRoot) { });
std::cerr << "Failed to load ship selection UI\n"; uiManager.setButtonPressCallback("minusButton", [this](const std::string&) {
return; int newVel = Environment::shipState.selectedVelocity - 1;
if (newVel < 0) newVel = 0;
uiManager.findButton("plusButton")->state = ButtonState::Normal;
if (newVel == 0)
{
uiManager.findButton("minusButton")->state = ButtonState::Disabled;
} }
if (uiManager.pushMenuFromSavedRoot(shipSelectionRoot)) { else
uiManager.setButtonCallback("spaceshipButton", [this, loadGameplayUI](const std::string& btnName) { {
std::string nick = uiManager.getTextFieldValue("nicknameInput"); uiManager.findButton("minusButton")->state = ButtonState::Normal;
if (nick.empty()) nick = "Player";
int shipType = 0;
uiManager.popMenu();
loadGameplayUI();
if (onSingleplayerPressed) onSingleplayerPressed(nick, shipType);
});
uiManager.setButtonCallback("cargoshipButton", [this, loadGameplayUI](const std::string& btnName) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
int shipType = 1;
uiManager.popMenu();
loadGameplayUI();
if (onSingleplayerPressed) onSingleplayerPressed(nick, shipType);
});
uiManager.setButtonCallback("backButton", [this](const std::string& btnName) {
uiManager.popMenu();
});
} }
else { if (onVelocityChanged) onVelocityChanged(newVel);
std::cerr << "Failed to push ship selection menu\n"; });
uiManager.setSliderCallback("velocitySlider", [this](const std::string&, float value) {
int newVel = static_cast<int>(roundf(value * 10));
if (newVel > 2) newVel = 2;
if (newVel != Environment::shipState.selectedVelocity) {
if (onVelocityChanged) onVelocityChanged(newVel);
} }
}); });
uiManager.setButtonCallback("exitButton", [](const std::string& name) { }
std::cerr << "Exit from main menu pressed: " << name << " -> exiting\n";
Environment::exitGameLoop = true; // ── State: GameOver ──────────────────────────────────────────────────────
});*/
void MenuManager::enterGameOver(int score)
{
state = GameState::GameOver;
uiManager.replaceRoot(gameOverRoot);
uiManager.setText("scoreText", "Score: " + std::to_string(score));
uiManager.setButtonCallback("restartButton", [this](const std::string&) {
if (onRestartPressed) onRestartPressed();
enterGameplay();
});
uiManager.setButtonCallback("gameOverExitButton", [this](const std::string&) {
enterMainMenu();
});
}
// ── State: ConnectionLost ─────────────────────────────────────────────────
void MenuManager::enterConnectionLost()
{
state = GameState::ConnectionLost;
uiManager.replaceRoot(connectionLostRoot);
uiManager.setButtonCallback("reconnectButton", [this](const std::string&) {
// TODO: reconnect logic
});
uiManager.setButtonCallback("exitServerButton", [this](const std::string&) {
enterMainMenu();
});
}
// ── Public event API ──────────────────────────────────────────────────────
void MenuManager::notifyConnected()
{
if (state == GameState::Connecting) {
enterGameplay();
}
}
void MenuManager::notifyConnectionFailed()
{
if (state == GameState::Connecting) {
enterConnectionFailed();
}
} }
void MenuManager::showGameOver(int score) void MenuManager::showGameOver(int score)
{ {
if (!uiGameOverShown) { if (state == GameState::Gameplay) {
if (uiManager.pushMenuFromSavedRoot(gameOverSavedRoot)) { enterGameOver(score);
uiManager.setText("scoreText", std::string("Score: ") + std::to_string(score));
uiManager.setButtonCallback("restartButton", [this](const std::string& name) {
uiManager.setText("scoreText", "");
uiGameOverShown = false;
uiManager.popMenu();
if (onRestartPressed) onRestartPressed();
});
uiManager.setButtonCallback("gameOverExitButton", [this](const std::string& name) {
Environment::exitGameLoop = true;
});
uiGameOverShown = true;
}
else {
std::cerr << "Failed to load game_over.json\n";
}
} }
} }
}
void MenuManager::showConnectionLost()
{
if (state == GameState::Gameplay) {
enterConnectionLost();
}
}
} // namespace ZL

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"
#include "Environment.h" #include "Environment.h"
#include "render/TextureManager.h" #include "render/TextureManager.h"
@ -7,37 +7,71 @@
namespace ZL { namespace ZL {
extern const char* CONST_ZIP_FILE; extern const char* CONST_ZIP_FILE;
//extern bool g_exitBgAnimating;
class MenuManager enum class GameState {
{ MainMenu,
ShipSelectionSingle,
ShipSelectionMulti,
Connecting,
ConnectionFailed,
Gameplay,
GameOver,
ConnectionLost
};
class MenuManager {
protected: protected:
Renderer& renderer; Renderer& renderer;
std::shared_ptr<UiNode> uiSavedRoot;
std::shared_ptr<UiNode> gameOverSavedRoot; // Pre-loaded UI roots (loaded once in setupMenu)
std::shared_ptr<UiNode> settingsSavedRoot; std::shared_ptr<UiNode> mainMenuRoot;
std::shared_ptr<UiNode> multiplayerSavedRoot; std::shared_ptr<UiNode> shipSelectionRoot;
std::shared_ptr<UiNode> connectingRoot;
std::shared_ptr<UiNode> connectionFailedRoot;
std::shared_ptr<UiNode> gameplayRoot;
std::shared_ptr<UiNode> gameOverRoot;
std::shared_ptr<UiNode> connectionLostRoot;
// Stored for multiplayer retry
std::string pendingMultiNick;
int pendingMultiShipType = 0;
GameState state = GameState::MainMenu;
// State transition methods
void enterMainMenu();
void enterShipSelectionSingle();
void enterShipSelectionMulti();
void enterConnecting();
void enterConnectionFailed();
void enterGameplay();
void enterGameOver(int score);
void enterConnectionLost();
public: public:
bool uiGameOverShown = false;
bool g_exitBgAnimating = false;
UiManager uiManager; UiManager uiManager;
MenuManager(Renderer& iRenderer); MenuManager(Renderer& iRenderer);
void setupMenu(); void setupMenu();
//void showGameOver(); // Returns true for states where Space should render and run (Gameplay, GameOver, ConnectionLost)
void showGameOver(int score); bool shouldRenderSpace() const;
GameState getState() const { return state; }
// Called by game events
void showGameOver(int score);
void showConnectionLost();
void notifyConnected();
void notifyConnectionFailed();
// Callbacks set by Game/Space
std::function<void()> onMainMenuEntered;
std::function<void()> onRestartPressed; std::function<void()> onRestartPressed;
std::function<void(float)> onVelocityChanged; std::function<void(float)> onVelocityChanged;
std::function<void()> onFirePressed; std::function<void()> onFirePressed;
std::function<void(const std::string&, int)> onSingleplayerPressed; std::function<void(const std::string&, int)> onSingleplayerPressed;
std::function<void(const std::string&, int)> onMultiplayerPressed; std::function<void(const std::string&, int)> onMultiplayerPressed;
}; };
};
} // namespace ZL

View File

@ -25,12 +25,16 @@
#include "network/LocalClient.h" #include "network/LocalClient.h"
#endif #endif
#include "GameConstants.h"
namespace ZL namespace ZL
{ {
extern const char* CONST_ZIP_FILE; extern const char* CONST_ZIP_FILE;
extern float x; extern float x;
extern float y;
extern float z;
Eigen::Quaternionf generateRandomQuaternion(std::mt19937& gen) Eigen::Quaternionf generateRandomQuaternion(std::mt19937& gen)
{ {
@ -252,20 +256,32 @@ namespace ZL
Space::~Space() { Space::~Space() {
} }
void Space::resetPlayerState()
{
shipAlive = true;
gameOver = false;
showExplosion = false;
explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
Environment::shipState.position = Vector3f{ 0, 0, 45000.f };
Environment::shipState.velocity = 0.0f;
Environment::shipState.selectedVelocity = 0;
Environment::shipState.rotation = Eigen::Matrix3f::Identity();
Environment::inverseShipMatrix = Eigen::Matrix3f::Identity();
Environment::zoom = DEFAULT_ZOOM;
Environment::tapDownHold = false;
playerScore = 0;
if (menuManager.uiManager.findButton("minusButton"))
{
menuManager.uiManager.findButton("minusButton")->state = ButtonState::Disabled;
}
}
void Space::setup() { void Space::setup() {
menuManager.onRestartPressed = [this]() { menuManager.onRestartPressed = [this]() {
this->shipAlive = true; resetPlayerState();
this->gameOver = false;
this->showExplosion = false;
this->explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
Environment::shipState.position = Vector3f{ 0, 0, 45000.f };
Environment::shipState.velocity = 0.0f;
Environment::shipState.rotation = Eigen::Matrix3f::Identity();
Environment::inverseShipMatrix = Eigen::Matrix3f::Identity();
Environment::zoom = DEFAULT_ZOOM;
Environment::tapDownHold = false;
if (networkClient) { if (networkClient) {
try { try {
@ -276,7 +292,6 @@ namespace ZL
std::cerr << "Client: Failed to send RESPAWN\n"; std::cerr << "Client: Failed to send RESPAWN\n";
} }
} }
this->playerScore = 0;
std::cerr << "Game restarted\n"; std::cerr << "Game restarted\n";
}; };
@ -304,6 +319,9 @@ namespace ZL
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);
bool cfgLoaded2 = sparkEmitterCargo.loadFromJsonFile("resources/config/spark_config_cargo.json", renderer, CONST_ZIP_FILE); bool cfgLoaded2 = sparkEmitterCargo.loadFromJsonFile("resources/config/spark_config_cargo.json", renderer, CONST_ZIP_FILE);
sparkEmitter.setIsActive(false);
sparkEmitterCargo.setIsActive(false);
bool projCfgLoaded = projectileEmitter.loadFromJsonFile("resources/config/spark_projectile_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); bool explosionCfgLoaded = explosionEmitter.loadFromJsonFile("resources/config/explosion_config.json", renderer, CONST_ZIP_FILE);
explosionEmitter.setEmissionPoints(std::vector<Vector3f>()); explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
@ -413,17 +431,13 @@ namespace ZL
void Space::drawCubemap(float skyPercent) void Space::drawCubemap(float skyPercent)
{ {
static const std::string defaultShaderName = "default"; static const std::string envSkyShaderName = "env_sky";
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"; static const std::string skyPercentUniformName = "skyPercent";
renderer.shaderManager.PushShader(envShaderName); renderer.shaderManager.PushShader(envSkyShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1f(skyPercentUniformName, skyPercent); renderer.RenderUniform1f(skyPercentUniformName, skyPercent);
renderer.EnableVertexAttribArray(vPositionName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
@ -471,24 +485,14 @@ namespace ZL
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
} }
void Space::drawShip() void Space::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.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
@ -509,15 +513,17 @@ namespace ZL
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
renderer.DrawVertexRenderStruct(spaceship); renderer.DrawVertexRenderStruct(spaceship);
} }
drawShipSparkEmitters();
} }
renderer.PopMatrix(); renderer.PopMatrix();
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
renderer.shaderManager.PushShader("default"); renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendFunc(GL_SRC_ALPHA, GL_ONE);
renderer.PushMatrix(); renderer.PushMatrix();
@ -527,62 +533,29 @@ namespace ZL
renderer.TranslateMatrix(-Environment::shipState.position); renderer.TranslateMatrix(-Environment::shipState.position);
for (const auto& p : projectiles) { for (const auto& p : projectiles) {
if (p && p->isActive()) { if (p && p->isActive()) {
//p->draw(renderer);
p->projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); p->projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
} }
} }
renderer.PopMatrix(); renderer.PopMatrix();
glDisable(GL_BLEND); glDisable(GL_BLEND);
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
//projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
if (shipAlive) {
renderer.PushMatrix();
renderer.TranslateMatrix({ 0, 0, 16 });
renderer.TranslateMatrix({ 0, -6.f, 0 });
if (Environment::shipState.shipType == 1) {
sparkEmitterCargo.draw(renderer, Environment::zoom, Environment::width, Environment::height, false);
}
else {
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height, false);
}
renderer.PopMatrix();
}
if (showExplosion) { if (showExplosion) {
explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height, false); explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height, false);
} }
//glBindTexture(GL_TEXTURE_2D, basePlatformTexture->getTexID());
//renderer.DrawVertexRenderStruct(basePlatform);
glDisable(GL_BLEND); glDisable(GL_BLEND);
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
} }
void Space::drawBoxes() void Space::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.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
@ -601,31 +574,23 @@ namespace ZL
renderer.RotateMatrix(boxCoordsArr[i].m); renderer.RotateMatrix(boxCoordsArr[i].m);
glBindTexture(GL_TEXTURE_2D, boxTexture->getTexID()); glBindTexture(GL_TEXTURE_2D, boxTexture->getTexID());
//glBindTexture(GL_TEXTURE_2D, rockTexture->getTexID());
renderer.DrawVertexRenderStruct(boxRenderArr[i]); renderer.DrawVertexRenderStruct(boxRenderArr[i]);
renderer.PopMatrix(); renderer.PopMatrix();
} }
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
} }
void Space::drawScene() { void Space::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); glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glViewport(0, 0, Environment::width, Environment::height); glViewport(0, 0, Environment::width, Environment::height);
prepareSparkEmittersForDraw();
CheckGlError(); CheckGlError();
float skyPercent = 0.0; float skyPercent = 0.0;
@ -663,17 +628,9 @@ namespace ZL
} }
void Space::drawRemoteShips() { void Space::drawRemoteShips() {
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.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
@ -714,7 +671,6 @@ namespace ZL
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
//renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset
renderer.RotateMatrix(Environment::inverseShipMatrix); renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position); renderer.TranslateMatrix(-Environment::shipState.position);
@ -736,8 +692,6 @@ namespace ZL
} }
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
@ -991,17 +945,11 @@ namespace ZL
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.EnableVertexAttribArray("vPosition");
renderer.EnableVertexAttribArray("vColor");
Eigen::Vector4f uColor(crosshairCfg.color.x(), crosshairCfg.color.y(), crosshairCfg.color.z(), crosshairCfg.alpha); Eigen::Vector4f uColor(crosshairCfg.color.x(), crosshairCfg.color.y(), crosshairCfg.color.z(), crosshairCfg.alpha);
renderer.RenderUniform4fv("uColor", uColor.data()); renderer.RenderUniform4fv("uColor", uColor.data());
renderer.DrawVertexRenderStruct(crosshairMesh); renderer.DrawVertexRenderStruct(crosshairMesh);
renderer.DisableVertexAttribArray("vPosition");
renderer.DisableVertexAttribArray("vColor");
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
@ -1330,7 +1278,6 @@ namespace ZL
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.EnableVertexAttribArray("vPosition");
renderer.RenderUniform4fv("uColor", enemyColor.data()); renderer.RenderUniform4fv("uColor", enemyColor.data());
// рамки // рамки
@ -1353,7 +1300,6 @@ namespace ZL
drawLeadRing2D(lx, ly); drawLeadRing2D(lx, ly);
} }
renderer.DisableVertexAttribArray("vPosition");
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
@ -1439,7 +1385,6 @@ namespace ZL
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.EnableVertexAttribArray("vPosition");
renderer.RenderUniform4fv("uColor", enemyColor.data()); renderer.RenderUniform4fv("uColor", enemyColor.data());
// стрелка // стрелка
@ -1457,7 +1402,6 @@ namespace ZL
drawLeadRing2D(lx, ly); drawLeadRing2D(lx, ly);
} }
renderer.DisableVertexAttribArray("vPosition");
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
@ -1476,23 +1420,93 @@ namespace ZL
targetWasVisible = false; targetWasVisible = false;
} }
void Space::updateSparkEmitters(float deltaMs)
{
// Local ship
SparkEmitter* sparkEmitterPtr;
if (Environment::shipState.shipType == 1) {
sparkEmitterPtr = &sparkEmitterCargo;
static std::vector<Vector3f> emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) };
emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 2.8, -6.5 + 16.0 };
emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 1.5, -6.5 + 16.0 };
sparkEmitterPtr->setEmissionPoints(emissionPoints);
}
else {
sparkEmitterPtr = &sparkEmitter;
static std::vector<Vector3f> emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) };
emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ -0.9, 1.4 - 1.0, -8.5 + 16.0 };
emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.9, 1.4 - 1.0, -8.5 + 16.0 };
sparkEmitterPtr->setEmissionPoints(emissionPoints);
}
sparkEmitterPtr->setIsActive(Environment::shipState.velocity > 0.1f);
sparkEmitterPtr->update(deltaMs);
// Remote ships
for (auto const& [id, playerState] : remotePlayerStates) {
if (deadRemotePlayers.count(id)) continue;
if (!remoteShipSparkEmitters.count(id)) {
remoteShipSparkEmitters.emplace(id, playerState.shipType == 1 ? sparkEmitterCargo : sparkEmitter);
}
auto& remEmitter = remoteShipSparkEmitters.at(id);
std::vector<Vector3f> remEmitPts(2);
if (playerState.shipType == 1) {
remEmitPts[0] = playerState.position + playerState.rotation * Vector3f{ 0.0f, -0.4f+2.8f, 8.4f };
remEmitPts[1] = playerState.position + playerState.rotation * Vector3f{ 0.0f, -0.4f+1.5f, 8.4f };
} else {
remEmitPts[0] = playerState.position + playerState.rotation * Vector3f{ -0.9f, -0.2,5.6 };
remEmitPts[1] = playerState.position + playerState.rotation * Vector3f{ 0.9f,-0.2,5.6 };
}
remEmitter.setEmissionPoints(remEmitPts);
remEmitter.setIsActive(playerState.velocity > 0.1f);
remEmitter.update(deltaMs);
}
}
void Space::prepareSparkEmittersForDraw()
{
sparkEmitter.prepareForDraw(true);
sparkEmitterCargo.prepareForDraw(true);
for (auto& [id, emitter] : remoteShipSparkEmitters) {
if (!deadRemotePlayers.count(id)) emitter.prepareForDraw(true);
}
explosionEmitter.prepareForDraw(false);
for (const auto& p : projectiles) {
if (p && p->isActive()) {
p->projectileEmitter.prepareForDraw(true);
}
}
}
void Space::drawShipSparkEmitters()
{
renderer.PushMatrix();
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position);
if (Environment::shipState.shipType == 1) {
sparkEmitterCargo.draw(renderer, Environment::zoom, Environment::width, Environment::height);
} else {
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
}
for (auto& [id, emitter] : remoteShipSparkEmitters) {
if (!deadRemotePlayers.count(id)) {
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position);
emitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
renderer.PopMatrix();
}
}
renderer.PopMatrix();
}
void Space::processTickCount(int64_t newTickCount, int64_t delta) { void Space::processTickCount(int64_t newTickCount, int64_t delta) {
auto now_ms = newTickCount; auto now_ms = newTickCount;
SparkEmitter* sparkEmitterPtr;
if (Environment::shipState.shipType == 1) {
sparkEmitterPtr = &sparkEmitterCargo;
}
else
{
sparkEmitterPtr = &sparkEmitter;
}
sparkEmitterPtr->update(static_cast<float>(delta));
planetObject.update(static_cast<float>(delta));
if (firePressed) if (firePressed)
{ {
firePressed = false; firePressed = false;
@ -1580,7 +1594,7 @@ namespace ZL
std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent(); std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent();
networkClient->Send(msg); networkClient->Send(msg);
std::cout << "Sending: " << msg << std::endl; //std::cout << "Sending: " << msg << std::endl;
} }
long long leftoverDelta = delta; long long leftoverDelta = delta;
@ -1624,6 +1638,8 @@ namespace ZL
remotePlayerStates[id] = playerState; remotePlayerStates[id] = playerState;
} }
updateSparkEmitters(static_cast<float>(delta));
for (auto& p : projectiles) { for (auto& p : projectiles) {
if (p && p->isActive()) { if (p && p->isActive()) {
p->update(static_cast<float>(delta), renderer); p->update(static_cast<float>(delta), renderer);
@ -1634,42 +1650,11 @@ namespace ZL
for (const auto& p : projectiles) { for (const auto& p : projectiles) {
if (p && p->isActive()) { if (p && p->isActive()) {
Vector3f worldPos = p->getPosition(); Vector3f worldPos = p->getPosition();
//Vector3f rel = worldPos - Environment::shipState.position; p->projectileEmitter.resetEmissionPoints({ worldPos });
//Vector3f camPos = Environment::inverseShipMatrix * rel;
p->projectileEmitter.setEmissionPoints({ worldPos });
p->projectileEmitter.emit();
p->projectileEmitter.update(static_cast<float>(delta)); p->projectileEmitter.update(static_cast<float>(delta));
} }
} }
/*
if (!projCameraPoints.empty()) {
projectileEmitter.setEmissionPoints(projCameraPoints);
projectileEmitter.emit();
}
else {
projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
}*/
if (Environment::shipState.velocity > 0.1f) {
sparkEmitterPtr->setIsActive(true);
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()) {
sparkEmitterPtr->setEmissionPoints(shipCameraPoints);
}
}
else
{
sparkEmitterPtr->setIsActive(false);
}
sparkEmitterPtr->update(static_cast<float>(delta));
//projectileEmitter.update(static_cast<float>(delta));
explosionEmitter.update(static_cast<float>(delta)); explosionEmitter.update(static_cast<float>(delta));
if (showExplosion) { if (showExplosion) {
@ -1690,6 +1675,7 @@ namespace ZL
shipAlive = false; shipAlive = false;
gameOver = true; gameOver = true;
Environment::shipState.selectedVelocity = 0;
Environment::shipState.velocity = 0.0f; Environment::shipState.velocity = 0.0f;
showExplosion = true; showExplosion = true;
@ -1780,6 +1766,8 @@ namespace ZL
} }
} }
planetObject.update(static_cast<float>(delta));
// update velocity text // update velocity text
if (shipAlive && !gameOver) { if (shipAlive && !gameOver) {
@ -1831,7 +1819,7 @@ namespace ZL
gameOver = true; gameOver = true;
Environment::shipState.velocity = 0.0f; Environment::shipState.velocity = 0.0f;
std::cout << "Client: Lost connection to server\n"; std::cout << "Client: Lost connection to server\n";
menuManager.showGameOver(this->playerScore); menuManager.showConnectionLost();
} }
auto pending = networkClient->getPendingProjectiles(); auto pending = networkClient->getPendingProjectiles();
@ -1927,6 +1915,7 @@ namespace ZL
for (int pid : disconnects) { for (int pid : disconnects) {
remotePlayerStates.erase(pid); remotePlayerStates.erase(pid);
deadRemotePlayers.erase(pid); deadRemotePlayers.erase(pid);
remoteShipSparkEmitters.erase(pid);
if (trackedTargetId == pid) { if (trackedTargetId == pid) {
trackedTargetId = -1; trackedTargetId = -1;
targetAcquireAnim = 0.f; targetAcquireAnim = 0.f;

View File

@ -66,6 +66,7 @@ namespace ZL {
std::unique_ptr<TextRenderer> textRenderer; std::unique_ptr<TextRenderer> textRenderer;
std::unordered_map<int, ClientState> remotePlayerStates; std::unordered_map<int, ClientState> remotePlayerStates;
std::unordered_map<int, SparkEmitter> remoteShipSparkEmitters;
float newShipVelocity = 0; float newShipVelocity = 0;
@ -98,7 +99,7 @@ namespace ZL {
float projectileCooldownMs = 500.0f; float projectileCooldownMs = 500.0f;
int64_t lastProjectileFireTime = 0; int64_t lastProjectileFireTime = 0;
int maxProjectiles = 500; int maxProjectiles = 500;
std::vector<Vector3f> shipLocalEmissionPoints; //std::vector<Vector3f> shipLocalEmissionPoints;
bool shipAlive = true; bool shipAlive = true;
@ -141,8 +142,13 @@ namespace ZL {
void drawTargetHud(); // рисует рамку или стрелку void drawTargetHud(); // рисует рамку или стрелку
int pickTargetId() const; // ???????? ???? (????: ????????? ????? ????????? ?????) int pickTargetId() const; // ???????? ???? (????: ????????? ????? ????????? ?????)
void resetPlayerState();
void clearTextRendererCache(); void clearTextRendererCache();
void updateSparkEmitters(float deltaMs);
void prepareSparkEmittersForDraw();
void drawShipSparkEmitters();
// Crosshair HUD // Crosshair HUD
struct CrosshairConfig { struct CrosshairConfig {
bool enabled = true; bool enabled = true;

View File

@ -8,6 +8,7 @@
#include "Environment.h" #include "Environment.h"
#include <stdexcept> #include <stdexcept>
#include "utils/Utils.h" #include "utils/Utils.h"
#include "GameConstants.h"
namespace ZL { namespace ZL {
@ -25,12 +26,12 @@ namespace ZL {
sparkQuad.data = VertexDataStruct(); sparkQuad.data = VertexDataStruct();
} }
SparkEmitter::SparkEmitter(const SparkEmitter& copyFrom) SparkEmitter::SparkEmitter(const SparkEmitter& copyFrom)
: particles(copyFrom.particles), emissionPoints(copyFrom.emissionPoints), : particles(copyFrom.particles), emissionPoints(copyFrom.emissionPoints),
lastEmissionTime(copyFrom.lastEmissionTime), emissionRate(copyFrom.emissionRate), lastEmissionTime(copyFrom.lastEmissionTime), emissionRate(copyFrom.emissionRate),
isActive(copyFrom.isActive), drawPositions(copyFrom.drawPositions), isActive(copyFrom.isActive), drawPositions(copyFrom.drawPositions),
drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(copyFrom.drawDataDirty), drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(true),
sparkQuad(copyFrom.sparkQuad), texture(copyFrom.texture), texture(copyFrom.texture),
maxParticles(copyFrom.maxParticles), particleSize(copyFrom.particleSize), maxParticles(copyFrom.maxParticles), particleSize(copyFrom.particleSize),
biasX(copyFrom.biasX), speedRange(copyFrom.speedRange), biasX(copyFrom.biasX), speedRange(copyFrom.speedRange),
zSpeedRange(copyFrom.zSpeedRange), zSpeedRange(copyFrom.zSpeedRange),
@ -39,6 +40,8 @@ namespace ZL {
shaderProgramName(copyFrom.shaderProgramName), shaderProgramName(copyFrom.shaderProgramName),
configured(copyFrom.configured), useWorldSpace(copyFrom.useWorldSpace) configured(copyFrom.configured), useWorldSpace(copyFrom.useWorldSpace)
{ {
// Each copy gets its own GPU buffers; only copy CPU-side data
sparkQuad.data = copyFrom.sparkQuad.data;
} }
@ -167,6 +170,18 @@ namespace ZL {
drawDataDirty = false; drawDataDirty = false;
} }
void SparkEmitter::prepareForDraw(bool withRotation) {
if (!configured) return;
prepareDrawData(withRotation);
if (!drawPositions.empty()) {
sparkQuad.data.PositionData = drawPositions;
sparkQuad.data.TexCoordData = drawTexCoords;
sparkQuad.RefreshVBO();
}
}
void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight) void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight)
{ {
draw(renderer, zoom, screenWidth, screenHeight, true); draw(renderer, zoom, screenWidth, screenHeight, true);
@ -185,46 +200,24 @@ namespace ZL {
throw std::runtime_error("Failed to load spark emitter config file 2!"); throw std::runtime_error("Failed to load spark emitter config file 2!");
} }
prepareDrawData(withRotation);
if (drawPositions.empty()) { if (drawPositions.empty()) {
return; return;
} }
sparkQuad.data.PositionData = drawPositions;
sparkQuad.data.TexCoordData = drawTexCoords;
sparkQuad.RefreshVBO();
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(shaderProgramName); renderer.shaderManager.PushShader(shaderProgramName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName); renderer.SetMatrix();
renderer.EnableVertexAttribArray(vTexCoordName);
//float aspectRatio = static_cast<float>(screenWidth) / static_cast<float>(screenHeight);
//renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, aspectRatio, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
glBindTexture(GL_TEXTURE_2D, texture->getTexID()); glBindTexture(GL_TEXTURE_2D, texture->getTexID());
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendFunc(GL_SRC_ALPHA, GL_ONE);
renderer.PushMatrix(); //renderer.PushMatrix();
//renderer.LoadIdentity();
//renderer.TranslateMatrix({ 0, 0, -1.0f * zoom });
renderer.DrawVertexRenderStruct(sparkQuad); renderer.DrawVertexRenderStruct(sparkQuad);
//renderer.PopMatrix();
renderer.PopMatrix();
//renderer.PopProjectionMatrix();
glDisable(GL_BLEND); glDisable(GL_BLEND);
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
} }
@ -237,9 +230,19 @@ namespace ZL {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>( auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
currentTime - lastEmissionTime).count(); currentTime - lastEmissionTime).count();
if (isActive && elapsed >= emissionRate) { if (isActive && elapsed >= static_cast<long long>(emissionRate)) {
emit(); int emitCount = static_cast<int>(elapsed / emissionRate);
lastEmissionTime = currentTime; float elapsedF = static_cast<float>(elapsed);
for (int e = 0; e < emitCount; ++e) {
// e=0 — самая старая эмиссия, e=emitCount-1 — самая свежая
float ageMs = static_cast<float>(emitCount - 1 - e) * emissionRate;
// lerpT=0 → текущая позиция (новейшая), lerpT=1 → предыдущая (самая старая)
float lerpT = (elapsedF > 0.0f) ? min(ageMs / elapsedF, 1.0f) : 0.0f;
emit(ageMs, lerpT);
}
lastEmissionTime += std::chrono::milliseconds(
static_cast<long long>(emitCount * emissionRate));
drawDataDirty = true; drawDataDirty = true;
} }
@ -278,7 +281,7 @@ namespace ZL {
} }
} }
void SparkEmitter::emit() { void SparkEmitter::emit(float ageMs, float lerpT) {
if (!configured) { if (!configured) {
throw std::runtime_error("Failed to load spark emitter config file 4!"); throw std::runtime_error("Failed to load spark emitter config file 4!");
} }
@ -286,7 +289,30 @@ namespace ZL {
if (emissionPoints.empty()) return; if (emissionPoints.empty()) return;
bool emitted = false; bool emitted = false;
for (int i = 0; i < emissionPoints.size(); ++i) { auto applyAge = [](SparkParticle& particle, float age) {
if (age <= 0.0f) return;
particle.position(0) += particle.velocity(0) * age / 1000.0f;
particle.position(1) += particle.velocity(1) * age / 1000.0f;
particle.position(2) += particle.velocity(2) * age / 1000.0f;
particle.lifeTime = age;
if (particle.lifeTime >= particle.maxLifeTime) {
particle.active = false;
} else {
float lifeRatio = particle.lifeTime / particle.maxLifeTime;
particle.scale = 1.0f - lifeRatio * 0.8f;
}
};
// Вычисляем стартовую позицию с интерполяцией между prev и current
// lerpT=0 → текущая позиция (новейшая эмиссия), lerpT=1 → предыдущая (самая старая)
bool canInterp = (prevEmissionPoints.size() == emissionPoints.size());
for (int i = 0; i < (int)emissionPoints.size(); ++i) {
Vector3f birthPos = emissionPoints[i];
if (canInterp && lerpT > 0.0f) {
birthPos = emissionPoints[i] * (1.0f - lerpT) + prevEmissionPoints[i] * lerpT;
}
bool particleFound = false; bool particleFound = false;
for (auto& particle : particles) { for (auto& particle : particles) {
@ -294,8 +320,9 @@ namespace ZL {
initParticle(particle, i); initParticle(particle, i);
particle.active = true; particle.active = true;
particle.lifeTime = 0; particle.lifeTime = 0;
particle.position = emissionPoints[i]; particle.position = birthPos;
particle.emitterIndex = i; particle.emitterIndex = i;
applyAge(particle, ageMs);
particleFound = true; particleFound = true;
emitted = true; emitted = true;
break; break;
@ -304,11 +331,11 @@ namespace ZL {
if (!particleFound && !particles.empty()) { if (!particleFound && !particles.empty()) {
size_t oldestIndex = 0; size_t oldestIndex = 0;
float maxLifeTime = 0; float maxLifeRatio = 0;
for (size_t j = 0; j < particles.size(); ++j) { for (size_t j = 0; j < particles.size(); ++j) {
if (particles[j].lifeTime > maxLifeTime) { if (particles[j].lifeTime > maxLifeRatio) {
maxLifeTime = particles[j].lifeTime; maxLifeRatio = particles[j].lifeTime;
oldestIndex = j; oldestIndex = j;
} }
} }
@ -316,8 +343,9 @@ namespace ZL {
initParticle(particles[oldestIndex], i); initParticle(particles[oldestIndex], i);
particles[oldestIndex].active = true; particles[oldestIndex].active = true;
particles[oldestIndex].lifeTime = 0; particles[oldestIndex].lifeTime = 0;
particles[oldestIndex].position = emissionPoints[i]; particles[oldestIndex].position = birthPos;
particles[oldestIndex].emitterIndex = i; particles[oldestIndex].emitterIndex = i;
applyAge(particles[oldestIndex], ageMs);
emitted = true; emitted = true;
} }
} }
@ -328,6 +356,14 @@ namespace ZL {
} }
void SparkEmitter::setEmissionPoints(const std::vector<Vector3f>& positions) { void SparkEmitter::setEmissionPoints(const std::vector<Vector3f>& positions) {
prevEmissionPoints = emissionPoints;
emissionPoints = positions;
drawDataDirty = true;
}
void SparkEmitter::resetEmissionPoints(const std::vector<Vector3f>& positions)
{
prevEmissionPoints.clear();
emissionPoints = positions; emissionPoints = positions;
drawDataDirty = true; drawDataDirty = true;
} }
@ -495,8 +531,9 @@ namespace ZL {
std::cout << "Total emission points: " << emissionPoints.size() << std::endl; std::cout << "Total emission points: " << emissionPoints.size() << std::endl;
} }
else { else {
std::cerr << "Emission points parsed but empty" << std::endl; setEmissionPoints({});
throw std::runtime_error("Failed to load spark emitter config file 10!"); std::cout << "Emission points parsed but empty" << std::endl;
//throw std::runtime_error("Failed to load spark emitter config file 10!");
} }
} }
else { else {
@ -583,25 +620,6 @@ namespace ZL {
throw std::runtime_error("Failed to load spark emitter config file 18!"); throw std::runtime_error("Failed to load spark emitter config file 18!");
} }
std::cout << "Working with shaders 2" << std::endl;
/*
if (j.contains("vertexShader") && j.contains("fragmentShader")
&& j["vertexShader"].is_string() && j["fragmentShader"].is_string()) {
std::string v = j["vertexShader"].get<std::string>();
std::string f = j["fragmentShader"].get<std::string>();
std::cout << "Loading shaders - vertex: " << v << ", fragment: " << f << std::endl;
try {
renderer.shaderManager.AddShaderFromFiles(shaderProgramName, v, f, zipFile.c_str());
std::cout << "Shaders loaded successfully" << std::endl;
}
catch (const std::exception& e) {
std::cerr << "Shader load error: " << e.what() << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 19!");
}
}*/
drawDataDirty = true; drawDataDirty = true;
configured = true; configured = true;
std::cout << "SparkEmitter configuration loaded successfully!" << std::endl; std::cout << "SparkEmitter configuration loaded successfully!" << std::endl;

View File

@ -26,6 +26,7 @@ namespace ZL {
private: private:
std::vector<SparkParticle> particles; std::vector<SparkParticle> particles;
std::vector<Vector3f> emissionPoints; std::vector<Vector3f> emissionPoints;
std::vector<Vector3f> prevEmissionPoints;
std::chrono::steady_clock::time_point lastEmissionTime; std::chrono::steady_clock::time_point lastEmissionTime;
float emissionRate; float emissionRate;
bool isActive; bool isActive;
@ -62,6 +63,7 @@ namespace ZL {
float rate = 100.0f); float rate = 100.0f);
void setEmissionPoints(const std::vector<Vector3f>& positions); void setEmissionPoints(const std::vector<Vector3f>& positions);
void resetEmissionPoints(const std::vector<Vector3f>& positions);
void setTexture(std::shared_ptr<Texture> tex); void setTexture(std::shared_ptr<Texture> tex);
void setEmissionRate(float rate) { emissionRate = rate; } void setEmissionRate(float rate) { emissionRate = rate; }
void setShaderProgramName(const std::string& name) { shaderProgramName = name; } void setShaderProgramName(const std::string& name) { shaderProgramName = name; }
@ -73,7 +75,10 @@ namespace ZL {
bool loadFromJsonFile(const std::string& path, Renderer& renderer, const std::string& zipFile = ""); bool loadFromJsonFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
void update(float deltaTimeMs); void update(float deltaTimeMs);
void emit(); void emit(float ageMs = 0.0f, float lerpT = 0.0f);
// Вызывать ДО draw() в начале кадра: готовит данные и загружает в VBO.
void prepareForDraw(bool withRotation = true);
void draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight); void draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight);
void draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight, bool withRotation); void draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight, bool withRotation);

View File

@ -4,6 +4,7 @@
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <algorithm> #include <algorithm>
#include "GameConstants.h"
namespace ZL { namespace ZL {
@ -61,23 +62,13 @@ namespace ZL {
} }
if (!(*tex)) return; if (!(*tex)) return;
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.PushMatrix(); renderer.PushMatrix();
renderer.TranslateMatrix({ animOffsetX, animOffsetY, 0.0f }); renderer.TranslateMatrix({ animOffsetX, animOffsetY, 0.0f });
renderer.ScaleMatrix({ animScaleX, animScaleY, 1.0f }); renderer.ScaleMatrix({ animScaleX, animScaleY, 1.0f });
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
glBindTexture(GL_TEXTURE_2D, (*tex)->getTexID()); glBindTexture(GL_TEXTURE_2D, (*tex)->getTexID());
renderer.DrawVertexRenderStruct(mesh); renderer.DrawVertexRenderStruct(mesh);
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.PopMatrix(); renderer.PopMatrix();
} }
@ -148,14 +139,7 @@ namespace ZL {
} }
void UiSlider::draw(Renderer& renderer) const { void UiSlider::draw(Renderer& renderer) const {
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
if (texTrack) { if (texTrack) {
glBindTexture(GL_TEXTURE_2D, texTrack->getTexID()); glBindTexture(GL_TEXTURE_2D, texTrack->getTexID());
renderer.DrawVertexRenderStruct(trackMesh); renderer.DrawVertexRenderStruct(trackMesh);
@ -164,9 +148,44 @@ namespace ZL {
glBindTexture(GL_TEXTURE_2D, texKnob->getTexID()); glBindTexture(GL_TEXTURE_2D, texKnob->getTexID());
renderer.DrawVertexRenderStruct(knobMesh); renderer.DrawVertexRenderStruct(knobMesh);
} }
}
renderer.DisableVertexAttribArray(vPositionName); void UiStaticImage::buildMesh() {
renderer.DisableVertexAttribArray(vTexCoordName); mesh.data.PositionData.clear();
mesh.data.TexCoordData.clear();
float x0 = rect.x;
float y0 = rect.y;
float x1 = rect.x + rect.w;
float y1 = rect.y + rect.h;
mesh.data.PositionData.push_back({ x0, y0, 0 });
mesh.data.TexCoordData.push_back({ 0, 0 });
mesh.data.PositionData.push_back({ x0, y1, 0 });
mesh.data.TexCoordData.push_back({ 0, 1 });
mesh.data.PositionData.push_back({ x1, y1, 0 });
mesh.data.TexCoordData.push_back({ 1, 1 });
mesh.data.PositionData.push_back({ x0, y0, 0 });
mesh.data.TexCoordData.push_back({ 0, 0 });
mesh.data.PositionData.push_back({ x1, y1, 0 });
mesh.data.TexCoordData.push_back({ 1, 1 });
mesh.data.PositionData.push_back({ x1, y0, 0 });
mesh.data.TexCoordData.push_back({ 1, 0 });
mesh.RefreshVBO();
}
void UiStaticImage::draw(Renderer& renderer) const {
if (!texture) return;
renderer.RenderUniform1i(textureUniformName, 0);
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
renderer.DrawVertexRenderStruct(mesh);
} }
void UiTextField::draw(Renderer& renderer) const { void UiTextField::draw(Renderer& renderer) const {
@ -249,6 +268,7 @@ namespace ZL {
if (j.contains("horizontal_gravity")) { if (j.contains("horizontal_gravity")) {
std::string hg = j["horizontal_gravity"].get<std::string>(); std::string hg = j["horizontal_gravity"].get<std::string>();
if (hg == "right") node->layoutSettings.hGravity = HorizontalGravity::Right; if (hg == "right") node->layoutSettings.hGravity = HorizontalGravity::Right;
else if (hg == "center") node->layoutSettings.hGravity = HorizontalGravity::Center;
else node->layoutSettings.hGravity = HorizontalGravity::Left; else node->layoutSettings.hGravity = HorizontalGravity::Left;
} }
@ -256,6 +276,7 @@ namespace ZL {
if (j.contains("vertical_gravity")) { if (j.contains("vertical_gravity")) {
std::string vg = j["vertical_gravity"].get<std::string>(); std::string vg = j["vertical_gravity"].get<std::string>();
if (vg == "bottom") node->layoutSettings.vGravity = VerticalGravity::Bottom; if (vg == "bottom") node->layoutSettings.vGravity = VerticalGravity::Bottom;
else if (vg == "center") node->layoutSettings.vGravity = VerticalGravity::Center;
else node->layoutSettings.vGravity = VerticalGravity::Top; else node->layoutSettings.vGravity = VerticalGravity::Top;
} }
@ -403,6 +424,32 @@ namespace ZL {
} }
} }
if (typeStr == "StaticImage") {
auto img = std::make_shared<UiStaticImage>();
img->name = node->name;
img->rect = initialRect;
std::string texPath;
if (j.contains("texture") && j["texture"].is_string()) {
texPath = j["texture"].get<std::string>();
}
else if (j.contains("textures") && j["textures"].is_object() && j["textures"].contains("normal")) {
texPath = j["textures"]["normal"].get<std::string>();
}
if (!texPath.empty()) {
try {
auto data = CreateTextureDataFromPng(texPath.c_str(), zipFile.c_str());
img->texture = std::make_shared<Texture>(data);
}
catch (const std::exception& e) {
std::cerr << "UiManager: failed load texture for StaticImage '" << img->name << "' : " << e.what() << std::endl;
}
}
node->staticImage = img;
}
if (typeStr == "TextView") { if (typeStr == "TextView") {
auto tv = std::make_shared<UiTextView>(); auto tv = std::make_shared<UiTextView>();
@ -492,6 +539,7 @@ namespace ZL {
sliders.clear(); sliders.clear();
textViews.clear(); textViews.clear();
textFields.clear(); textFields.clear();
staticImages.clear();
collectButtonsAndSliders(root); collectButtonsAndSliders(root);
nodeActiveAnims.clear(); nodeActiveAnims.clear();
@ -503,6 +551,9 @@ namespace ZL {
s->buildTrackMesh(); s->buildTrackMesh();
s->buildKnobMesh(); s->buildKnobMesh();
} }
for (auto& img : staticImages) {
img->buildMesh();
}
} }
void UiManager::loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) { void UiManager::loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
@ -618,9 +669,15 @@ namespace ZL {
if (child->layoutSettings.hGravity == HorizontalGravity::Right) { if (child->layoutSettings.hGravity == HorizontalGravity::Right) {
fLX = currentW - childW - child->localX; fLX = currentW - childW - child->localX;
} }
else if (child->layoutSettings.hGravity == HorizontalGravity::Center) {
fLX = (currentW - childW) / 2.0f + child->localX;
}
if (child->layoutSettings.vGravity == VerticalGravity::Top) { if (child->layoutSettings.vGravity == VerticalGravity::Top) {
fLY = currentH - childH - child->localY; fLY = currentH - childH - child->localY;
} }
else if (child->layoutSettings.vGravity == VerticalGravity::Center) {
fLY = (currentH - childH) / 2.0f + child->localY;
}
// Передаем рассчитанные fLX, fLY в рекурсию // Передаем рассчитанные fLX, fLY в рекурсию
layoutNode(child, node->screenRect.x, node->screenRect.y, currentW, currentH, fLX, fLY); layoutNode(child, node->screenRect.x, node->screenRect.y, currentW, currentH, fLX, fLY);
@ -661,6 +718,12 @@ namespace ZL {
node->textField->rect = node->screenRect; node->textField->rect = node->screenRect;
// Аналогично для курсора и фонового меша // Аналогично для курсора и фонового меша
} }
// 5. Обновляем статическое изображение
if (node->staticImage) {
node->staticImage->rect = node->screenRect;
node->staticImage->buildMesh();
}
} }
void UiManager::updateAllLayouts() { void UiManager::updateAllLayouts() {
@ -690,6 +753,9 @@ namespace ZL {
if (node->textField) { if (node->textField) {
textFields.push_back(node->textField); textFields.push_back(node->textField);
} }
if (node->staticImage) {
staticImages.push_back(node->staticImage);
}
for (auto& c : node->children) collectButtonsAndSliders(c); for (auto& c : node->children) collectButtonsAndSliders(c);
} }
@ -703,6 +769,16 @@ namespace ZL {
return true; return true;
} }
bool UiManager::setButtonPressCallback(const std::string& name, std::function<void(const std::string&)> cb) {
auto b = findButton(name);
if (!b) {
std::cerr << "UiManager: setButtonPressCallback failed, button not found: " << name << std::endl;
return false;
}
b->onPress = std::move(cb);
return true;
}
bool UiManager::addSlider(const std::string& name, const UiRect& rect, Renderer& renderer, const std::string& zipFile, bool UiManager::addSlider(const std::string& name, const UiRect& rect, Renderer& renderer, const std::string& zipFile,
const std::string& trackPath, const std::string& knobPath, float initialValue, bool vertical) { const std::string& trackPath, const std::string& knobPath, float initialValue, bool vertical) {
@ -788,8 +864,9 @@ namespace ZL {
prev.buttons = buttons; prev.buttons = buttons;
prev.sliders = sliders; prev.sliders = sliders;
prev.textFields = textFields; prev.textFields = textFields;
prev.pressedButton = pressedButton; prev.staticImages = staticImages;
prev.pressedSlider = pressedSlider; prev.pressedButtons = pressedButtons;
prev.pressedSliders = pressedSliders;
prev.focusedTextField = focusedTextField; prev.focusedTextField = focusedTextField;
prev.path = ""; prev.path = "";
@ -838,8 +915,9 @@ namespace ZL {
buttons = s.buttons; buttons = s.buttons;
sliders = s.sliders; sliders = s.sliders;
textFields = s.textFields; textFields = s.textFields;
pressedButton = s.pressedButton; staticImages = s.staticImages;
pressedSlider = s.pressedSlider; pressedButtons = s.pressedButtons;
pressedSliders = s.pressedSliders;
focusedTextField = s.focusedTextField; focusedTextField = s.focusedTextField;
animCallbacks = s.animCallbacks; animCallbacks = s.animCallbacks;
@ -872,6 +950,9 @@ namespace ZL {
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
for (const auto& img : staticImages) {
img->draw(renderer);
}
for (const auto& b : buttons) { for (const auto& b : buttons) {
b->draw(renderer); b->draw(renderer);
} }
@ -1051,21 +1132,25 @@ namespace ZL {
} }
} }
void UiManager::onMouseMove(int x, int y) { void UiManager::onTouchMove(int64_t fingerId, int x, int y) {
for (auto& b : buttons) { // Hover state updates only make sense for mouse (single pointer)
if (b->state != ButtonState::Disabled) if (fingerId == MOUSE_FINGER_ID) {
{ for (auto& b : buttons) {
if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) { if (b->state != ButtonState::Disabled)
if (b->state != ButtonState::Pressed) b->state = ButtonState::Hover; {
} if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) {
else { if (b->state != ButtonState::Pressed) b->state = ButtonState::Hover;
if (b->state != ButtonState::Pressed) b->state = ButtonState::Normal; }
else {
if (b->state != ButtonState::Pressed) b->state = ButtonState::Normal;
}
} }
} }
} }
if (pressedSlider) { auto it = pressedSliders.find(fingerId);
auto s = pressedSlider; if (it != pressedSliders.end()) {
auto s = it->second;
float t; float t;
if (s->vertical) { if (s->vertical) {
t = (y - s->rect.y) / s->rect.h; t = (y - s->rect.y) / s->rect.h;
@ -1082,20 +1167,22 @@ namespace ZL {
} }
void UiManager::onMouseDown(int x, int y) { void UiManager::onTouchDown(int64_t fingerId, int x, int y) {
for (auto& b : buttons) { for (auto& b : buttons) {
if (b->state != ButtonState::Disabled) if (b->state != ButtonState::Disabled)
{ {
if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) { if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) {
b->state = ButtonState::Pressed; b->state = ButtonState::Pressed;
pressedButton = b; pressedButtons[fingerId] = b;
if (b->onPress) b->onPress(b->name);
break; // a single finger can only press one button
} }
} }
} }
for (auto& s : sliders) { for (auto& s : sliders) {
if (s->rect.contains((float)x, (float)y)) { if (s->rect.contains((float)x, (float)y)) {
pressedSlider = s; pressedSliders[fingerId] = s;
float t; float t;
if (s->vertical) { if (s->vertical) {
t = (y - s->rect.y) / s->rect.h; t = (y - s->rect.y) / s->rect.h;
@ -1123,29 +1210,32 @@ namespace ZL {
} }
} }
void UiManager::onMouseUp(int x, int y) { void UiManager::onTouchUp(int64_t fingerId, int x, int y) {
std::vector<std::shared_ptr<UiButton>> clicked; std::vector<std::shared_ptr<UiButton>> clicked;
for (auto& b : buttons) { auto btnIt = pressedButtons.find(fingerId);
if (!b) continue; if (btnIt != pressedButtons.end()) {
bool contains = b->rect.contains((float)x, (float)y); auto b = btnIt->second;
if (b) {
if (b->state == ButtonState::Pressed) { bool contains = b->rect.contains((float)x, (float)y);
if (contains && pressedButton == b) { if (b->state == ButtonState::Pressed) {
clicked.push_back(b); if (contains) {
clicked.push_back(b);
}
// On mouse: leave Hover if still over button. On touch: always Normal.
b->state = (contains && fingerId == MOUSE_FINGER_ID) ? ButtonState::Hover : ButtonState::Normal;
} }
b->state = contains ? ButtonState::Hover : ButtonState::Normal;
} }
pressedButtons.erase(btnIt);
} }
pressedSliders.erase(fingerId);
for (auto& b : clicked) { for (auto& b : clicked) {
if (b->onClick) { if (b->onClick) {
b->onClick(b->name); b->onClick(b->name);
} }
} }
pressedButton.reset();
if (pressedSlider) pressedSlider.reset();
} }
void UiManager::onKeyPress(unsigned char key) { void UiManager::onKeyPress(unsigned char key) {
@ -1261,6 +1351,13 @@ namespace ZL {
return true; return true;
} }
std::shared_ptr<UiStaticImage> UiManager::findStaticImage(const std::string& name) {
for (auto& img : staticImages) {
if (img->name == name) return img;
}
return nullptr;
}
std::shared_ptr<UiTextView> UiManager::findTextView(const std::string& name) { std::shared_ptr<UiTextView> UiManager::findTextView(const std::string& name) {
for (auto& tv : textViews) { for (auto& tv : textViews) {
if (tv->name == name) return tv; if (tv->name == name) return tv;

View File

@ -10,6 +10,7 @@
#include <memory> #include <memory>
#include <functional> #include <functional>
#include <map> #include <map>
#include <cstdint>
namespace ZL { namespace ZL {
@ -60,11 +61,13 @@ namespace ZL {
enum class HorizontalGravity { enum class HorizontalGravity {
Left, Left,
Center,
Right Right
}; };
enum class VerticalGravity { enum class VerticalGravity {
Bottom, // Обычно в OpenGL Y растет вверх, так что низ - это 0 Bottom, // Обычно в OpenGL Y растет вверх, так что низ - это 0
Center,
Top Top
}; };
@ -91,6 +94,7 @@ namespace ZL {
VertexRenderStruct mesh; VertexRenderStruct mesh;
std::function<void(const std::string&)> onClick; std::function<void(const std::string&)> onClick;
std::function<void(const std::string&)> onPress; // fires on touch/mouse down
// animation runtime // animation runtime
float animOffsetX = 0.0f; float animOffsetX = 0.0f;
@ -159,6 +163,17 @@ namespace ZL {
void draw(Renderer& renderer) const; void draw(Renderer& renderer) const;
}; };
struct UiStaticImage {
std::string name;
UiRect rect;
std::shared_ptr<Texture> texture;
VertexRenderStruct mesh;
void buildMesh();
void draw(Renderer& renderer) const;
};
struct UiNode { struct UiNode {
std::string name; std::string name;
LayoutType layoutType = LayoutType::Frame; LayoutType layoutType = LayoutType::Frame;
@ -185,6 +200,7 @@ namespace ZL {
std::shared_ptr<UiSlider> slider; std::shared_ptr<UiSlider> slider;
std::shared_ptr<UiTextView> textView; std::shared_ptr<UiTextView> textView;
std::shared_ptr<UiTextField> textField; std::shared_ptr<UiTextField> textField;
std::shared_ptr<UiStaticImage> staticImage;
// Анимации // Анимации
struct AnimStep { struct AnimStep {
@ -210,19 +226,35 @@ namespace ZL {
public: public:
UiManager() = default; UiManager() = default;
// Sentinel finger ID used for mouse events on desktop/web
static constexpr int64_t MOUSE_FINGER_ID = -1LL;
void replaceRoot(std::shared_ptr<UiNode> newRoot); void replaceRoot(std::shared_ptr<UiNode> newRoot);
void loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = ""); void loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
void draw(Renderer& renderer); void draw(Renderer& renderer);
void onMouseMove(int x, int y); // Multi-touch methods (used directly for touch events with per-finger IDs)
void onMouseDown(int x, int y); void onTouchDown(int64_t fingerId, int x, int y);
void onMouseUp(int x, int y); void onTouchUp(int64_t fingerId, int x, int y);
void onTouchMove(int64_t fingerId, int x, int y);
// Mouse convenience wrappers (delegate to touch with MOUSE_FINGER_ID)
void onMouseMove(int x, int y) { onTouchMove(MOUSE_FINGER_ID, x, y); }
void onMouseDown(int x, int y) { onTouchDown(MOUSE_FINGER_ID, x, y); }
void onMouseUp(int x, int y) { onTouchUp(MOUSE_FINGER_ID, x, y); }
void onKeyPress(unsigned char key); void onKeyPress(unsigned char key);
void onKeyBackspace(); void onKeyBackspace();
// Returns true if any finger is currently interacting with UI
bool isUiInteraction() const { bool isUiInteraction() const {
return pressedButton != nullptr || pressedSlider != nullptr || focusedTextField != nullptr; return !pressedButtons.empty() || !pressedSliders.empty() || focusedTextField != nullptr;
}
// Returns true if this specific finger is currently interacting with UI
bool isUiInteractionForFinger(int64_t fingerId) const {
return pressedButtons.count(fingerId) > 0 || pressedSliders.count(fingerId) > 0 || focusedTextField != nullptr;
} }
void stopAllAnimations() { void stopAllAnimations() {
@ -241,6 +273,7 @@ namespace ZL {
std::shared_ptr<UiButton> findButton(const std::string& name); std::shared_ptr<UiButton> findButton(const std::string& name);
bool setButtonCallback(const std::string& name, std::function<void(const std::string&)> cb); bool setButtonCallback(const std::string& name, std::function<void(const std::string&)> cb);
bool setButtonPressCallback(const std::string& name, std::function<void(const std::string&)> cb);
bool addSlider(const std::string& name, const UiRect& rect, Renderer& renderer, const std::string& zipFile, bool addSlider(const std::string& name, const UiRect& rect, Renderer& renderer, const std::string& zipFile,
const std::string& trackPath, const std::string& knobPath, float initialValue = 0.0f, bool vertical = true); const std::string& trackPath, const std::string& knobPath, float initialValue = 0.0f, bool vertical = true);
@ -256,6 +289,8 @@ namespace ZL {
bool setTextFieldCallback(const std::string& name, std::function<void(const std::string&, const std::string&)> cb); bool setTextFieldCallback(const std::string& name, std::function<void(const std::string&, const std::string&)> cb);
std::string getTextFieldValue(const std::string& name); std::string getTextFieldValue(const std::string& name);
std::shared_ptr<UiStaticImage> findStaticImage(const std::string& name);
bool pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = ""); bool pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
bool pushMenuFromSavedRoot(std::shared_ptr<UiNode> newRoot); bool pushMenuFromSavedRoot(std::shared_ptr<UiNode> newRoot);
bool popMenu(); bool popMenu();
@ -301,12 +336,14 @@ namespace ZL {
std::vector<std::shared_ptr<UiSlider>> sliders; std::vector<std::shared_ptr<UiSlider>> sliders;
std::vector<std::shared_ptr<UiTextView>> textViews; std::vector<std::shared_ptr<UiTextView>> textViews;
std::vector<std::shared_ptr<UiTextField>> textFields; std::vector<std::shared_ptr<UiTextField>> textFields;
std::vector<std::shared_ptr<UiStaticImage>> staticImages;
std::map<std::shared_ptr<UiNode>, std::vector<ActiveAnim>> nodeActiveAnims; std::map<std::shared_ptr<UiNode>, std::vector<ActiveAnim>> nodeActiveAnims;
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks; // key: (nodeName, animName) std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks; // key: (nodeName, animName)
std::shared_ptr<UiButton> pressedButton; // Per-finger tracking for multi-touch support
std::shared_ptr<UiSlider> pressedSlider; std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
std::shared_ptr<UiTextField> focusedTextField; std::shared_ptr<UiTextField> focusedTextField;
struct MenuState { struct MenuState {
@ -314,8 +351,9 @@ namespace ZL {
std::vector<std::shared_ptr<UiButton>> buttons; std::vector<std::shared_ptr<UiButton>> buttons;
std::vector<std::shared_ptr<UiSlider>> sliders; std::vector<std::shared_ptr<UiSlider>> sliders;
std::vector<std::shared_ptr<UiTextField>> textFields; std::vector<std::shared_ptr<UiTextField>> textFields;
std::shared_ptr<UiButton> pressedButton; std::vector<std::shared_ptr<UiStaticImage>> staticImages;
std::shared_ptr<UiSlider> pressedSlider; std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
std::shared_ptr<UiTextField> focusedTextField; std::shared_ptr<UiTextField> focusedTextField;
std::string path; std::string path;
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks; std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks;

View File

@ -68,10 +68,6 @@ static void applyResize(int logicalW, int logicalH) {
SDL_SetWindowSize(ZL::Environment::window, physicalW, physicalH); SDL_SetWindowSize(ZL::Environment::window, physicalW, physicalH);
} }
// Обновляем ваши внутренние переменные окружения
ZL::Environment::width = physicalW;
ZL::Environment::height = physicalH;
// Пушим событие, чтобы движок пересчитал матрицы проекции // Пушим событие, чтобы движок пересчитал матрицы проекции
SDL_Event e = {}; SDL_Event e = {};
e.type = SDL_WINDOWEVENT; e.type = SDL_WINDOWEVENT;
@ -131,7 +127,11 @@ int main(int argc, char* argv[]) {
// канваса и отправит SDL_WINDOWEVENT_RESIZED для настройки проекции. // канваса и отправит SDL_WINDOWEVENT_RESIZED для настройки проекции.
applyResize(canvasW, canvasH); applyResize(canvasW, canvasH);
// Prevent mouse clicks from generating fake SDL_FINGERDOWN events (desktop browser)
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0"); SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
// Prevent touch events from generating fake SDL_MOUSEBUTTONDOWN events (mobile browser),
// since we now handle SDL_FINGERDOWN directly for multi-touch support.
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
emscripten_set_main_loop(MainLoop, 0, 1); emscripten_set_main_loop(MainLoop, 0, 1);
@ -240,68 +240,6 @@ int main(int argc, char *argv[]) {
ZL::Environment::width = CONST_WIDTH; ZL::Environment::width = CONST_WIDTH;
ZL::Environment::height = CONST_HEIGHT; ZL::Environment::height = CONST_HEIGHT;
/*#ifdef EMSCRIPTEN
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
return 1;
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_Window* win = SDL_CreateWindow("Space Ship Game",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
CONST_WIDTH, CONST_HEIGHT,
SDL_WINDOW_OPENGL);
if (!win) {
std::cerr << "SDL_CreateWindow failed: " << SDL_GetError() << std::endl;
return 1;
}
SDL_GLContext glContext = SDL_GL_CreateContext(win);
if (!glContext) {
std::cerr << "SDL_GL_CreateContext failed: " << SDL_GetError() << std::endl;
return 1;
}
// Привязка контекста к окну — важно!
SDL_GL_MakeCurrent(win, glContext);
ZL::Environment::window = win;
g_game = new ZL::Game();
g_game->setup();
// Re-create the game object when the WebGL context is lost and restored
// (this happens e.g. when the user toggles fullscreen in the browser).
emscripten_set_webglcontextlost_callback("#canvas", nullptr, EM_TRUE, onWebGLContextLost);
emscripten_set_webglcontextrestored_callback("#canvas", nullptr, EM_TRUE, onWebGLContextRestored);
// Keep Environment::width/height in sync when the canvas is resized.
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_FALSE, onWindowResized);
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_FALSE, onFullscreenChanged);
// 2. ИНИЦИАЛИЗАЦИЯ РАЗМЕРОВ:
// Получаем реальные размеры окна браузера на момент запуска
int canvasW = EM_ASM_INT({ return window.innerWidth; });
int canvasH = EM_ASM_INT({ return window.innerHeight; });
// Вызываем вашу функцию — она сама применит DPR, выставит физический размер
// канваса и отправит SDL_WINDOWEVENT_RESIZED для настройки проекции.
applyResize(canvasW, canvasH);
// 3. Создаем игру и вызываем setup (теперь проекция уже будет знать верный size)
g_game = new ZL::Game();
g_game->setup();
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
emscripten_set_main_loop(MainLoop, 0, 1);
#else*/
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
SDL_Log("SDL init failed: %s", SDL_GetError()); SDL_Log("SDL init failed: %s", SDL_GetError());
return 1; return 1;
@ -327,8 +265,6 @@ int main(int argc, char *argv[]) {
game.update(); game.update();
SDL_Delay(2); SDL_Delay(2);
} }
//#endif
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {

View File

@ -34,6 +34,7 @@ namespace ZL {
public: public:
virtual ~INetworkClient() = default; virtual ~INetworkClient() = default;
virtual void Connect(const std::string& host, uint16_t port) = 0; virtual void Connect(const std::string& host, uint16_t port) = 0;
virtual void Disconnect() {}
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; // ƒл¤ обработки вход¤щих пакетов

View File

@ -28,6 +28,15 @@ namespace ZL {
} }
} }
void WebSocketClient::Disconnect() {
if (!ws_ || !connected) return;
connected = false;
try {
boost::beast::get_lowest_layer(*ws_).cancel();
}
catch (...) {}
}
void WebSocketClient::startAsyncRead() { void WebSocketClient::startAsyncRead() {
ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) { ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) {
if (!ec) { if (!ec) {

View File

@ -54,6 +54,7 @@ namespace ZL {
{} {}
void Connect(const std::string& host, uint16_t port) override; void Connect(const std::string& host, uint16_t port) override;
void Disconnect() override;
void Poll() override; void Poll() override;

View File

@ -26,6 +26,15 @@ namespace ZL {
connected = false; connected = false;
} }
void WebSocketClientEmscripten::Disconnect() {
if (socket_ > 0) {
emscripten_websocket_close(socket_, 1000, "User disconnected");
emscripten_websocket_delete(socket_);
socket_ = 0;
}
connected = false;
}
void WebSocketClientEmscripten::flushOutgoingQueue() { void WebSocketClientEmscripten::flushOutgoingQueue() {
std::lock_guard<std::mutex> lock(outgoingMutex); std::lock_guard<std::mutex> lock(outgoingMutex);
if (!socket_) return; if (!socket_) return;

View File

@ -28,6 +28,7 @@ namespace ZL {
virtual ~WebSocketClientEmscripten() = default; virtual ~WebSocketClientEmscripten() = default;
void Connect(const std::string& host, uint16_t port) override; void Connect(const std::string& host, uint16_t port) override;
void Disconnect() override;
void Send(const std::string& message) override; void Send(const std::string& message) override;
void Poll() override; void Poll() override;

View File

@ -6,6 +6,7 @@
#include "StoneObject.h" #include "StoneObject.h"
#include "utils/TaskManager.h" #include "utils/TaskManager.h"
#include "TextModel.h" #include "TextModel.h"
#include "GameConstants.h"
namespace ZL { namespace ZL {
@ -185,16 +186,10 @@ namespace ZL {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
static const std::string defaultShaderName2 = "planetBake"; static const std::string planetBakeShaderName = "planetBake";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(planetBakeShaderName);
renderer.shaderManager.PushShader(defaultShaderName2);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
Triangle tr = planetData.getLodLevel().triangles[0]; Triangle tr = planetData.getLodLevel().triangles[0];
@ -260,8 +255,6 @@ namespace ZL {
glDisable(GL_CULL_FACE); // Не забываем выключить, чтобы не сломать остальной рендер glDisable(GL_CULL_FACE); // Не забываем выключить, чтобы не сломать остальной рендер
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
} }
@ -298,23 +291,12 @@ namespace ZL {
void PlanetObject::drawPlanet(Renderer& renderer) void PlanetObject::drawPlanet(Renderer& renderer)
{ {
static const std::string defaultShaderName = "planetLand"; static const std::string planetLandShaderName = "planetLand";
static const std::string vPositionName = "vPosition";
static const std::string vColorName = "vColor";
static const std::string vNormalName = "vNormal";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture"; static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName); renderer.shaderManager.PushShader(planetLandShaderName);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vColorName);
renderer.EnableVertexAttribArray(vNormalName);
renderer.EnableVertexAttribArray("vTangent");
renderer.EnableVertexAttribArray("vBinormal");
renderer.EnableVertexAttribArray(vTexCoordName);
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
auto zRange = planetData.calculateZRange(dist); auto zRange = planetData.calculateZRange(dist);
@ -335,10 +317,9 @@ namespace ZL {
const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix();
renderer.RenderUniform1i("Texture", 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1i("BakedTexture", 1); renderer.RenderUniform1i("BakedTexture", 1);
Triangle tr = planetData.getLodLevel().triangles[0]; // Берем базовый треугольник Triangle tr = planetData.getLodLevel().triangles[0]; // Берем базовый треугольник
Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании
@ -376,12 +357,6 @@ namespace ZL {
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vNormalName);
renderer.DisableVertexAttribArray("vTangent");
renderer.DisableVertexAttribArray("vBinormal");
renderer.DisableVertexAttribArray(vColorName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
@ -390,20 +365,10 @@ namespace ZL {
void PlanetObject::drawStones(Renderer& renderer) void PlanetObject::drawStones(Renderer& renderer)
{ {
static const std::string defaultShaderName2 = "planetStone"; static const std::string planetStoneShaderName = "planetStone";
static const std::string vPositionName = "vPosition";
static const std::string vColorName = "vColor";
static const std::string vNormalName = "vNormal";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName2); renderer.shaderManager.PushShader(planetStoneShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vColorName);
renderer.EnableVertexAttribArray(vNormalName);
renderer.EnableVertexAttribArray(vTexCoordName);
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
auto zRange = planetData.calculateZRange(dist); auto zRange = planetData.calculateZRange(dist);
@ -468,10 +433,6 @@ namespace ZL {
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vNormalName);
renderer.DisableVertexAttribArray(vColorName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
@ -480,16 +441,10 @@ namespace ZL {
void PlanetObject::drawAtmosphere(Renderer& renderer) { void PlanetObject::drawAtmosphere(Renderer& renderer) {
static const std::string defaultShaderName = "defaultAtmosphere"; static const std::string defaultShaderName = "defaultAtmosphere";
//static const std::string defaultShaderName = "defaultColor";
static const std::string vPositionName = "vPosition";
static const std::string vNormalName = "vNormal";
//glClear(GL_DEPTH_BUFFER_BIT); //glClear(GL_DEPTH_BUFFER_BIT);
glDepthMask(GL_FALSE); glDepthMask(GL_FALSE);
renderer.shaderManager.PushShader(defaultShaderName); renderer.shaderManager.PushShader(defaultShaderName);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vNormalName);
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
auto zRange = planetData.calculateZRange(dist); auto zRange = planetData.calculateZRange(dist);
float currentZNear = zRange.first; float currentZNear = zRange.first;
@ -571,9 +526,6 @@ namespace ZL {
glDepthMask(GL_TRUE); glDepthMask(GL_TRUE);
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vNormalName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
@ -581,20 +533,8 @@ namespace ZL {
void PlanetObject::drawCamp(Renderer& renderer) void PlanetObject::drawCamp(Renderer& renderer)
{ {
static const std::string defaultShaderName2 = "default"; renderer.shaderManager.PushShader(defaultShaderName);
static const std::string vPositionName = "vPosition";
static const std::string vColorName = "vColor";
static const std::string vNormalName = "vNormal";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName2);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vColorName);
renderer.EnableVertexAttribArray(vNormalName);
renderer.EnableVertexAttribArray(vTexCoordName);
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
auto zRange = planetData.calculateZRange(dist); auto zRange = planetData.calculateZRange(dist);
@ -655,10 +595,6 @@ namespace ZL {
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vNormalName);
renderer.DisableVertexAttribArray(vColorName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();

View File

@ -50,17 +50,14 @@ namespace ZL {
Matrix4f r; Matrix4f r;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
r.data()[0] = 2.f / width; r.data()[0] = 2.f / width;
r.data()[5] = 2.f / height; r.data()[5] = 2.f / height;
r.data()[10] = -1.f / depthRange; r.data()[10] = -1.f / depthRange;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
r.data()[1] = r.data()[2] = r.data()[3] = 0; r.data()[1] = r.data()[2] = r.data()[3] = 0;
r.data()[4] = r.data()[6] = r.data()[7] = 0; r.data()[4] = r.data()[6] = r.data()[7] = 0;
r.data()[8] = r.data()[9] = r.data()[11] = 0; r.data()[8] = r.data()[9] = r.data()[11] = 0;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>)
r.data()[12] = -(xmax + xmin) / width; r.data()[12] = -(xmax + xmin) / width;
r.data()[13] = -(ymax + ymin) / height; r.data()[13] = -(ymax + ymin) / height;
r.data()[14] = zNear / depthRange; r.data()[14] = zNear / depthRange;
@ -384,7 +381,7 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, positionVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, positionVBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, data.PositionData.size() * 12, &data.PositionData[0], GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, data.PositionData.size() * 12, &data.PositionData[0], GL_DYNAMIC_DRAW);
if (data.TexCoordData.size() > 0) if (data.TexCoordData.size() > 0)
{ {
@ -395,7 +392,7 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, texCoordVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, texCoordVBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, data.TexCoordData.size() * 8, &data.TexCoordData[0], GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, data.TexCoordData.size() * 8, &data.TexCoordData[0], GL_DYNAMIC_DRAW);
} }
if (data.NormalData.size() > 0) if (data.NormalData.size() > 0)
@ -407,7 +404,7 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, normalVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, normalVBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, data.NormalData.size() * 12, &data.NormalData[0], GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, data.NormalData.size() * 12, &data.NormalData[0], GL_DYNAMIC_DRAW);
} }
if (data.TangentData.size() > 0) if (data.TangentData.size() > 0)
@ -419,7 +416,7 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, tangentVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, tangentVBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, data.TangentData.size() * 12, &data.TangentData[0], GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, data.TangentData.size() * 12, &data.TangentData[0], GL_DYNAMIC_DRAW);
} }
if (data.BinormalData.size() > 0) if (data.BinormalData.size() > 0)
@ -431,7 +428,7 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, binormalVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, binormalVBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, data.BinormalData.size() * 12, &data.BinormalData[0], GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, data.BinormalData.size() * 12, &data.BinormalData[0], GL_DYNAMIC_DRAW);
} }
if (data.ColorData.size() > 0) if (data.ColorData.size() > 0)
@ -443,7 +440,7 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, colorVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, colorVBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, data.ColorData.size() * 12, &data.ColorData[0], GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, data.ColorData.size() * 12, &data.ColorData[0], GL_DYNAMIC_DRAW);
} }
} }
@ -620,6 +617,7 @@ namespace ZL {
{ {
throw std::runtime_error("Modelview matrix stack overflow!!!!"); throw std::runtime_error("Modelview matrix stack overflow!!!!");
} }
SetMatrix();
} }
void Renderer::LoadIdentity() void Renderer::LoadIdentity()
@ -703,19 +701,7 @@ namespace ZL {
Matrix4f m = Matrix4f::Identity(); Matrix4f m = Matrix4f::Identity();
m.block<3, 3>(0, 0) = m3; m.block<3, 3>(0, 0) = m3;
/*
m.m[0] = m3.data()[0];
m.m[1] = m3.data()[1];
m.m[2] = m3.data()[2];
m.m[4] = m3.data()[3];
m.m[5] = m3.data()[4];
m.m[6] = m3.data()[5];
m.m[8] = m3.data()[6];
m.m[9] = m3.data()[7];
m.m[10] = m3.data()[8];
*/
m = ModelviewMatrixStack.top() * m; m = ModelviewMatrixStack.top() * m;
if (ModelviewMatrixStack.size() == 0) if (ModelviewMatrixStack.size() == 0)
@ -733,17 +719,7 @@ namespace ZL {
void Renderer::RotateMatrix(const Matrix3f& m3) void Renderer::RotateMatrix(const Matrix3f& m3)
{ {
Matrix4f m = Matrix4f::Identity(); Matrix4f m = Matrix4f::Identity();
/*m.m[0] = m3.data()[0];
m.m[1] = m3.data()[1];
m.m[2] = m3.data()[2];
m.m[4] = m3.data()[3];
m.m[5] = m3.data()[4];
m.m[6] = m3.data()[5];
m.m[8] = m3.data()[6];
m.m[9] = m3.data()[7];
m.m[10] = m3.data()[8];*/
m.block<3, 3>(0, 0) = m3; m.block<3, 3>(0, 0) = m3;
m = ModelviewMatrixStack.top() * m; m = ModelviewMatrixStack.top() * m;
@ -782,22 +758,6 @@ namespace ZL {
SetMatrix(); SetMatrix();
} }
void Renderer::EnableVertexAttribArray(const std::string& attribName)
{
auto shader = shaderManager.GetCurrentShader();
if (shader->attribList.find(attribName) != shader->attribList.end())
glEnableVertexAttribArray(shader->attribList[attribName]);
}
void Renderer::DisableVertexAttribArray(const std::string& attribName)
{
auto shader = shaderManager.GetCurrentShader();
if (shader->attribList.find(attribName) != shader->attribList.end())
glDisableVertexAttribArray(shader->attribList[attribName]);
}
void Renderer::RenderUniformMatrix3fv(const std::string& uniformName, bool transpose, const float* value) void Renderer::RenderUniformMatrix3fv(const std::string& uniformName, bool transpose, const float* value)
{ {
auto shader = shaderManager.GetCurrentShader(); auto shader = shaderManager.GetCurrentShader();
@ -887,8 +847,25 @@ namespace ZL {
glVertexAttribPointer(shader->attribList[attribName], 3, GL_FLOAT, GL_FALSE, stride, pointer); glVertexAttribPointer(shader->attribList[attribName], 3, GL_FLOAT, GL_FALSE, stride, pointer);
} }
void Renderer::DisableVertexAttribArray(const std::string& attribName)
{
auto shader = shaderManager.GetCurrentShader();
auto it = shader->attribList.find(attribName);
if (it != shader->attribList.end())
glDisableVertexAttribArray(it->second);
}
void Renderer::DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct) void Renderer::DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct)
{ {
#ifndef EMSCRIPTEN
#ifndef __ANDROID__
if (VertexRenderStruct.vao) {
glBindVertexArray(VertexRenderStruct.vao->getBuffer());
shaderManager.EnableVertexAttribArrays();
}
#endif
#endif
static const std::string vNormal("vNormal"); static const std::string vNormal("vNormal");
static const std::string vTangent("vTangent"); static const std::string vTangent("vTangent");
static const std::string vBinormal("vBinormal"); static const std::string vBinormal("vBinormal");
@ -902,7 +879,6 @@ namespace ZL {
{ {
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.normalVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.normalVBO->getBuffer());
VertexAttribPointer3fv(vNormal, 0, NULL); VertexAttribPointer3fv(vNormal, 0, NULL);
EnableVertexAttribArray(vNormal);
} }
else else
{ {
@ -912,7 +888,6 @@ namespace ZL {
{ {
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.tangentVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.tangentVBO->getBuffer());
VertexAttribPointer3fv(vTangent, 0, NULL); VertexAttribPointer3fv(vTangent, 0, NULL);
EnableVertexAttribArray(vTangent);
} }
else else
{ {
@ -922,7 +897,6 @@ namespace ZL {
{ {
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.binormalVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.binormalVBO->getBuffer());
VertexAttribPointer3fv(vBinormal, 0, NULL); VertexAttribPointer3fv(vBinormal, 0, NULL);
EnableVertexAttribArray(vBinormal);
} }
else else
{ {
@ -932,7 +906,6 @@ namespace ZL {
{ {
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.colorVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.colorVBO->getBuffer());
VertexAttribPointer3fv(vColor, 0, NULL); VertexAttribPointer3fv(vColor, 0, NULL);
EnableVertexAttribArray(vColor);
} }
else else
{ {
@ -942,7 +915,6 @@ namespace ZL {
{ {
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.texCoordVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.texCoordVBO->getBuffer());
VertexAttribPointer2fv(vTexCoord, 0, NULL); VertexAttribPointer2fv(vTexCoord, 0, NULL);
EnableVertexAttribArray(vTexCoord);
} }
else else
{ {
@ -951,8 +923,6 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.positionVBO->getBuffer()); glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.positionVBO->getBuffer());
VertexAttribPointer3fv(vPosition, 0, NULL); VertexAttribPointer3fv(vPosition, 0, NULL);
EnableVertexAttribArray(vPosition);
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(VertexRenderStruct.data.PositionData.size())); glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(VertexRenderStruct.data.PositionData.size()));
} }

View File

@ -127,13 +127,6 @@ namespace ZL {
void SetMatrix(); void SetMatrix();
void EnableVertexAttribArray(const std::string& attribName);
void DisableVertexAttribArray(const std::string& attribName);
void RenderUniformMatrix3fv(const std::string& uniformName, bool transpose, const float* value); void RenderUniformMatrix3fv(const std::string& uniformName, bool transpose, const float* value);
void RenderUniformMatrix4fv(const std::string& uniformName, bool transpose, const float* value); void RenderUniformMatrix4fv(const std::string& uniformName, bool transpose, const float* value);
void RenderUniform1i(const std::string& uniformName, const int value); void RenderUniform1i(const std::string& uniformName, const int value);
@ -145,6 +138,8 @@ namespace ZL {
void VertexAttribPointer3fv(const std::string& attribName, int stride, const char* pointer); void VertexAttribPointer3fv(const std::string& attribName, int stride, const char* pointer);
void DisableVertexAttribArray(const std::string& attribName);
void DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct); void DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct);
}; };

View File

@ -167,7 +167,6 @@ namespace ZL {
fragmentShader = readTextFile(fragmentShaderFileName); fragmentShader = readTextFile(fragmentShaderFileName);
} }
///std::cout << "Shader: "<< vertexShader << std::endl;
shaderResourceMap[shaderName] = std::make_shared<ShaderResource>(vertexShader, shaderResourceMap[shaderName] = std::make_shared<ShaderResource>(vertexShader,
fragmentShader); fragmentShader);
} }
@ -181,9 +180,26 @@ namespace ZL {
throw std::runtime_error("Shader does not exist!"); throw std::runtime_error("Shader does not exist!");
} }
if (shaderStack.size() > 0)
{
auto& prevShaderAttribList = shaderResourceMap[shaderStack.top()]->attribList;
for (auto& attrib : prevShaderAttribList)
{
glDisableVertexAttribArray(attrib.second);
}
}
shaderStack.push(shaderName); shaderStack.push(shaderName);
glUseProgram(shaderResourceMap[shaderName]->getShaderProgram()); auto& shaderResource = shaderResourceMap[shaderName];
glUseProgram(shaderResource->getShaderProgram());
auto& shaderAttribList = shaderResource->attribList;
for (auto& attrib : shaderAttribList)
{
glEnableVertexAttribArray(attrib.second);
}
} }
@ -192,12 +208,39 @@ namespace ZL {
throw std::runtime_error("Shader stack underflow!"); throw std::runtime_error("Shader stack underflow!");
} }
auto& prevShaderAttribList = shaderResourceMap[shaderStack.top()]->attribList;
for (auto& attrib : prevShaderAttribList)
{
glDisableVertexAttribArray(attrib.second);
}
shaderStack.pop(); shaderStack.pop();
if (shaderStack.size() == 0) { if (shaderStack.size() == 0) {
glUseProgram(0); glUseProgram(0);
} else { } else {
glUseProgram(shaderResourceMap[shaderStack.top()]->getShaderProgram()); auto& shaderResource = shaderResourceMap[shaderStack.top()];
glUseProgram(shaderResource->getShaderProgram());
auto& shaderAttribList = shaderResource->attribList;
for (auto& attrib : shaderAttribList)
{
glEnableVertexAttribArray(attrib.second);
}
}
}
void ShaderManager::EnableVertexAttribArrays()
{
if (shaderStack.size() != 0) {
auto& shaderResource = shaderResourceMap[shaderStack.top()];
auto& shaderAttribList = shaderResource->attribList;
for (auto& attrib : shaderAttribList)
{
glEnableVertexAttribArray(attrib.second);
}
} }
} }

View File

@ -42,6 +42,7 @@ namespace ZL {
void PushShader(const std::string& shaderName); void PushShader(const std::string& shaderName);
void PopShader(); void PopShader();
void EnableVertexAttribArrays();
std::shared_ptr<ShaderResource> GetCurrentShader(); std::shared_ptr<ShaderResource> GetCurrentShader();
}; };

View File

@ -380,10 +380,6 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, atlasTexture->getTexID()); glBindTexture(GL_TEXTURE_2D, atlasTexture->getTexID());
r->EnableVertexAttribArray("vPosition");
r->EnableVertexAttribArray("vTexCoord");
//for (size_t i = 0; i < text.length(); ++i) { //for (size_t i = 0; i < text.length(); ++i) {
// auto it = glyphs.find(text[i]); // auto it = glyphs.find(text[i]);
// if (it == glyphs.end()) continue; // if (it == glyphs.end()) continue;
@ -401,9 +397,6 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
// glDrawArrays(GL_TRIANGLES, 0, 6); // glDrawArrays(GL_TRIANGLES, 0, 6);
//} //}
r->DrawVertexRenderStruct(cached.mesh); r->DrawVertexRenderStruct(cached.mesh);
r->DisableVertexAttribArray("vPosition");
r->DisableVertexAttribArray("vTexCoord");
r->shaderManager.PopShader(); r->shaderManager.PopShader();
// Сброс бинда текстуры не обязателен, но можно для чистоты // Сброс бинда текстуры не обязателен, но можно для чистоты