diff --git a/proj-web/CMakeLists.txt b/proj-web/CMakeLists.txt index 0f353b1..d7f6fc6 100644 --- a/proj-web/CMakeLists.txt +++ b/proj-web/CMakeLists.txt @@ -73,6 +73,8 @@ set(SOURCES ../src/MenuManager.cpp ../src/Space.h ../src/Space.cpp + ../src/GameConstants.h + ../src/GameConstants.cpp ) add_executable(space-game001 ${SOURCES}) diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 90e0f67..b49685a 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -67,6 +67,8 @@ add_executable(space-game001 ../src/MenuManager.cpp ../src/Space.h ../src/Space.cpp + ../src/GameConstants.h + ../src/GameConstants.cpp ) # Установка проекта по умолчанию для Visual Studio diff --git a/resources/button_minus_disabled.png b/resources/button_minus_disabled.png new file mode 100644 index 0000000..6a990e9 --- /dev/null +++ b/resources/button_minus_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9667686ef6f83297b38a9b06d893fce5f1128ed8610283c32989a6bd37e9327 +size 11294 diff --git a/resources/button_plus_disabled.png b/resources/button_plus_disabled.png new file mode 100644 index 0000000..e7cf3c7 --- /dev/null +++ b/resources/button_plus_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08e52cc78b6e2fbade9edd79ab7f394118fdcd6ca38ea7f2d68b25852e327acd +size 9563 diff --git a/resources/config/connecting.json b/resources/config/connecting.json new file mode 100644 index 0000000..b305d6e --- /dev/null +++ b/resources/config/connecting.json @@ -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" + } + ] + } +} \ No newline at end of file diff --git a/resources/config/connection_failed.json b/resources/config/connection_failed.json new file mode 100644 index 0000000..1ea3df9 --- /dev/null +++ b/resources/config/connection_failed.json @@ -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" + } + } + ] + } +} \ No newline at end of file diff --git a/resources/config/connection_lost.json b/resources/config/connection_lost.json new file mode 100644 index 0000000..fd84d8a --- /dev/null +++ b/resources/config/connection_lost.json @@ -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" + } + } + ] + } +} \ No newline at end of file diff --git a/resources/config/explosion_config.json b/resources/config/explosion_config.json index fc0a2bc..b2d8c19 100644 --- a/resources/config/explosion_config.json +++ b/resources/config/explosion_config.json @@ -5,7 +5,7 @@ "texture": "resources/spark_white.png", "speedRange": [10.0, 30.0], "zSpeedRange": [-1.0, 1.0], - "scaleRange": [0.5, 1.0], + "scaleRange": [5.0, 10.0], "lifeTimeRange": [200.0, 800.0], "emissionRate": 50.0, "maxParticles": 5, diff --git a/resources/config/main_menu.json b/resources/config/main_menu.json index 3d1c80b..c103fb2 100644 --- a/resources/config/main_menu.json +++ b/resources/config/main_menu.json @@ -11,37 +11,25 @@ "height": "match_parent", "children": [ { - "type": "Button", + "type": "StaticImage", "name": "titleBtn", - "width": 254, + "width": 434, "height": 35, - "textures": { - "normal": "resources/main_menu/title.png", - "hover": "resources/main_menu/title.png", - "pressed": "resources/main_menu/title.png" - } + "texture": "resources/main_menu/title.png" }, { - "type": "Button", + "type": "StaticImage", "name": "underlineBtn", "width": 168, "height": 44, - "textures": { - "normal": "resources/main_menu/line.png", - "hover": "resources/main_menu/line.png", - "pressed": "resources/main_menu/line.png" - } + "texture": "resources/main_menu/line.png" }, { - "type": "Button", + "type": "StaticImage", "name": "subtitleBtn", "width": 144, "height": 11, - "textures": { - "normal": "resources/main_menu/subtitle.png", - "hover": "resources/main_menu/subtitle.png", - "pressed": "resources/main_menu/subtitle.png" - } + "texture": "resources/main_menu/subtitle.png" }, { "type": "Button", @@ -66,15 +54,11 @@ } }, { - "type": "Button", + "type": "StaticImage", "name": "versionLabel", "width": 81, "height": 9, - "textures": { - "normal": "resources/main_menu/version.png", - "hover": "resources/main_menu/version.png", - "pressed": "resources/main_menu/version.png" - } + "texture": "resources/main_menu/version.png" } ] } diff --git a/resources/config/ship_selection_menu.json b/resources/config/ship_selection_menu.json index 1a305de..bba2052 100644 --- a/resources/config/ship_selection_menu.json +++ b/resources/config/ship_selection_menu.json @@ -10,6 +10,13 @@ "width": "match_parent", "height": "match_parent", "children": [ + { + "type": "StaticImage", + "name": "titleBtn", + "width": 266, + "height": 66, + "texture": "resources/select_your_ship.png" + }, { "type": "LinearLayout", "orientation": "horizontal", diff --git a/resources/config/spark_config.json b/resources/config/spark_config.json index d7ec09e..2a46f11 100644 --- a/resources/config/spark_config.json +++ b/resources/config/spark_config.json @@ -1,20 +1,14 @@ { - "emissionRate": 10.0, - "maxParticles": 200, + "emissionRate": 1.2, + "maxParticles": 400, "particleSize": 0.3, "biasX": 0.3, "emissionPoints": [ - { - "position": [-1.0, 1.4, -3.5] - }, - { - "position": [1.0, 1.4, -3.5] - } ], "speedRange": [0.5, 2.0], "zSpeedRange": [1.0, 3.0], "scaleRange": [0.8, 1.2], - "lifeTimeRange": [600.0, 1400.0], + "lifeTimeRange": [300.0, 500.0], "texture": "resources/spark.png", "shaderProgramName": "spark" } \ No newline at end of file diff --git a/resources/config/spark_config_cargo.json b/resources/config/spark_config_cargo.json index 1fbe1a0..c853da0 100644 --- a/resources/config/spark_config_cargo.json +++ b/resources/config/spark_config_cargo.json @@ -1,6 +1,6 @@ { - "emissionRate": 10.0, - "maxParticles": 200, + "emissionRate": 1.2, + "maxParticles": 400, "particleSize": 0.3, "biasX": 0.3, "emissionPoints": [ diff --git a/resources/config/ui.json b/resources/config/ui.json index 06e895a..65d8225 100644 --- a/resources/config/ui.json +++ b/resources/config/ui.json @@ -31,7 +31,7 @@ "vertical_gravity": "bottom", "textures": { "normal": "resources/fire.png", - "hover": "resources/fire2.png", + "hover": "resources/fire.png", "pressed": "resources/fire2.png", "disabled": "resources/fire_disabled.png" } @@ -47,7 +47,7 @@ "vertical_gravity": "bottom", "textures": { "normal": "resources/fire.png", - "hover": "resources/fire2.png", + "hover": "resources/fire.png", "pressed": "resources/fire2.png", "disabled": "resources/fire_disabled.png" } @@ -65,7 +65,8 @@ "textures": { "normal": "resources/button_minus.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": { "normal": "resources/button_plus.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" } } ] diff --git a/resources/connecting.png b/resources/connecting.png new file mode 100644 index 0000000..f88597c --- /dev/null +++ b/resources/connecting.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c7d62c0ae0956dfd50dbf6616b9162c2a985c3d36287e2bc8b696639ffe4a19 +size 87183 diff --git a/resources/connection_failed.png b/resources/connection_failed.png new file mode 100644 index 0000000..c1d108c --- /dev/null +++ b/resources/connection_failed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:529240c1211bd455015a6dbc581d4b624711183b60f736b45a7729cdb4c6277f +size 125781 diff --git a/resources/connection_lost.png b/resources/connection_lost.png new file mode 100644 index 0000000..52515b1 --- /dev/null +++ b/resources/connection_lost.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:801e00e096b469e1081cd805e83e682b858ded21ab0d3963bf7633fb22a76a4e +size 124627 diff --git a/resources/loading.png b/resources/loading.png index 76fa26a..a31d7fa 100644 --- a/resources/loading.png +++ b/resources/loading.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb69fc16814ff0db507965e95df225f1316917b802998e8517242b8ef534e3ea -size 5861 +oid sha256:f5c05b59ffb6170102aef205bafaf95fa88abb76ec549722d408600154c2b4f0 +size 13592 diff --git a/resources/main_menu/about.png b/resources/main_menu/about.png new file mode 100644 index 0000000..da06378 --- /dev/null +++ b/resources/main_menu/about.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97ce17108ceabc0c0c8f8853f910322d98ee21a3d85dff44409ccc00c370ebf2 +size 1027 diff --git a/resources/main_menu/about_hover.png b/resources/main_menu/about_hover.png new file mode 100644 index 0000000..dac27c9 --- /dev/null +++ b/resources/main_menu/about_hover.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0675bd5e392e2635cb896215e023a9785e765507449b716273c826e17edeff73 +size 17670 diff --git a/resources/main_menu/about_pressed.png b/resources/main_menu/about_pressed.png new file mode 100644 index 0000000..714d290 --- /dev/null +++ b/resources/main_menu/about_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a395d9bada999ce26f7379543c708e0bbe7b56c3fc7989deac8675c669d1b28 +size 1003 diff --git a/resources/main_menu/title.png b/resources/main_menu/title.png index 70058a0..c20c4d7 100644 --- a/resources/main_menu/title.png +++ b/resources/main_menu/title.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cfd73ee7e067e0298f461ea0a01f1e2fc4b093cf2d22c665654f030867463b7 -size 7191 +oid sha256:32a5288d6024ea21d8b9e53608ec182516e30845de638457a23498649cda9832 +size 12534 diff --git a/resources/select_your_ship.png b/resources/select_your_ship.png new file mode 100644 index 0000000..14f21d6 --- /dev/null +++ b/resources/select_your_ship.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c962f6f711cb6b4b243c3c62334282b7565ca643bca99f0efd7049ef6e012217 +size 17024 diff --git a/src/Environment.cpp b/src/Environment.cpp index 09ab631..99a13f5 100644 --- a/src/Environment.cpp +++ b/src/Environment.cpp @@ -13,8 +13,8 @@ namespace ZL { int Environment::windowHeaderHeight = 0; -int Environment::width = 0; -int Environment::height = 0; +int Environment::width = CONST_DEFAULT_WIDTH; +int Environment::height = CONST_DEFAULT_HEIGHT; float Environment::zoom = DEFAULT_ZOOM; @@ -49,10 +49,10 @@ void Environment::computeProjectionDimensions() if (width >= height) { // Landscape: fix height to 720, scale width to preserve aspect projectionHeight = refShortSide; - projectionWidth = refShortSide * aspect; + projectionWidth = refShortSide * aspect; } else { // Portrait: fix width to 720, scale height to preserve aspect - projectionWidth = refShortSide; + projectionWidth = refShortSide; projectionHeight = refShortSide / aspect; } } diff --git a/src/Environment.h b/src/Environment.h index 00fd608..8eac9ca 100644 --- a/src/Environment.h +++ b/src/Environment.h @@ -14,6 +14,8 @@ namespace ZL { class Environment { public: + static constexpr int CONST_DEFAULT_WIDTH = 1280; + static constexpr int CONST_DEFAULT_HEIGHT = 720; static int windowHeaderHeight; static int width; static int height; diff --git a/src/Game.cpp b/src/Game.cpp index 4aa8d80..b58236e 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -30,7 +30,7 @@ #include "network/LocalClient.h" #include "network/ClientState.h" - +#include "GameConstants.h" namespace ZL { @@ -41,6 +41,10 @@ namespace ZL const char* CONST_ZIP_FILE = ""; #endif + float x = 0; + float y = 0; + float z = 0; + #ifdef EMSCRIPTEN Game* Game::s_instance = nullptr; @@ -86,6 +90,8 @@ namespace ZL //glContext = SDL_GL_CreateContext(ZL::Environment::window); //glContext = in_glContext; + Environment::width = Environment::CONST_DEFAULT_WIDTH; + Environment::height = Environment::CONST_DEFAULT_HEIGHT; Environment::computeProjectionDimensions(); ZL::BindOpenGlFunctions(); @@ -104,7 +110,20 @@ namespace ZL loadingTexture = std::make_unique(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE)); #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(); #ifdef EMSCRIPTEN @@ -146,11 +165,17 @@ namespace ZL menuManager.setupMenu(); + menuManager.onMainMenuEntered = [this]() { + if (networkClient) { + networkClient->Disconnect(); + networkClient.reset(); + } + }; + menuManager.onSingleplayerPressed = [this](const std::string& nickname, int shipType) { Environment::shipState.nickname = nickname; Environment::shipState.shipType = shipType; - if (Environment::shipState.shipType == 1) { menuManager.uiManager.findButton("shootButton")->state = ButtonState::Disabled; @@ -170,15 +195,14 @@ namespace ZL networkClient = std::unique_ptr(localClient); networkClient->Connect("", 0); + space.resetPlayerState(); lastTickCount = 0; - spaceGameStarted = 1; }; menuManager.onMultiplayerPressed = [this](const std::string& nickname, int shipType) { Environment::shipState.nickname = nickname; Environment::shipState.shipType = shipType; - #ifdef EMSCRIPTEN networkClient = std::make_unique(); networkClient->Connect("localhost", 8081); @@ -186,7 +210,7 @@ namespace ZL networkClient = std::make_unique(taskManager.getIOContext()); networkClient->Connect("localhost", 8081); #endif - + if (networkClient) { std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType); networkClient->Send(joinMsg); @@ -195,12 +219,12 @@ namespace ZL space.boxCoordsArr.clear(); space.boxRenderArr.clear(); - //space.boxLabels.clear(); space.boxAlive.clear(); space.serverBoxesApplied = false; + space.resetPlayerState(); + connectingStartTicks = SDL_GetTicks(); lastTickCount = 0; - spaceGameStarted = 1; }; @@ -212,21 +236,10 @@ namespace ZL void Game::drawUI() { - static const std::string defaultShaderName = "default"; - static const std::string envShaderName = "env"; - static const std::string vPositionName = "vPosition"; - static const std::string vTexCoordName = "vTexCoord"; - static const std::string textureUniformName = "Texture"; - glClear(GL_DEPTH_BUFFER_BIT); renderer.shaderManager.PushShader(defaultShaderName); renderer.RenderUniform1i(textureUniformName, 0); - renderer.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vTexCoordName); - - renderer.DisableVertexAttribArray(vPositionName); - renderer.DisableVertexAttribArray(vTexCoordName); glEnable(GL_BLEND); menuManager.uiManager.draw(renderer); glDisable(GL_BLEND); @@ -242,17 +255,14 @@ namespace ZL } void Game::drawScene() { - //For low quality: glViewport(0, 0, Environment::width, Environment::height); - //For high quality: - //glViewport(0, 0, Environment::projectionWidth, Environment::projectionHeight); if (!loadingCompleted) { drawLoading(); } else { - if (spaceGameStarted) { + if (menuManager.shouldRenderSpace()) { space.drawScene(); } else @@ -266,24 +276,17 @@ namespace ZL 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); renderer.shaderManager.PushShader(defaultShaderName); renderer.RenderUniform1i(textureUniformName, 0); - renderer.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vTexCoordName); - //float width = Environment::projectionWidth; - //float height = Environment::projectionHeight; + float width = Environment::projectionWidth; + float height = Environment::projectionHeight; renderer.PushProjectionMatrix( - 0, 1, - 0, 1, + -width * 0.5f, width*0.5f, + -height * 0.5f, height * 0.5f, -10, 10); renderer.PushMatrix(); @@ -294,9 +297,6 @@ namespace ZL renderer.PopMatrix(); renderer.PopProjectionMatrix(); - - renderer.DisableVertexAttribArray(vPositionName); - renderer.DisableVertexAttribArray(vTexCoordName); renderer.shaderManager.PopShader(); CheckGlError(); } @@ -336,7 +336,7 @@ namespace ZL //throw std::runtime_error("Synchronization is lost"); } - if (spaceGameStarted) { + if (menuManager.shouldRenderSpace()) { space.processTickCount(newTickCount, delta); } menuManager.uiManager.update(static_cast(delta)); @@ -348,11 +348,13 @@ namespace ZL //SDL_GL_MakeCurrent(ZL::Environment::window, glContext); 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); + //processTickCount(); drawScene(); processTickCount(); + //std::this_thread::sleep_for(std::chrono::milliseconds(50)); SDL_GL_SwapWindow(ZL::Environment::window); } @@ -386,51 +388,55 @@ namespace ZL if (event.type == SDL_FINGERDOWN) { int mx = static_cast(event.tfinger.x * Environment::projectionWidth); int my = static_cast(event.tfinger.y * Environment::projectionHeight); - handleDown(mx, my); + handleDown(static_cast(event.tfinger.fingerId), mx, my); } else if (event.type == SDL_FINGERUP) { int mx = static_cast(event.tfinger.x * Environment::projectionWidth); int my = static_cast(event.tfinger.y * Environment::projectionHeight); - handleUp(mx, my); + handleUp(static_cast(event.tfinger.fingerId), mx, my); } else if (event.type == SDL_FINGERMOTION) { int mx = static_cast(event.tfinger.x * Environment::projectionWidth); int my = static_cast(event.tfinger.y * Environment::projectionHeight); - handleMotion(mx, my); + handleMotion(static_cast(event.tfinger.fingerId), mx, my); } #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(event.tfinger.x * Environment::projectionWidth); + int my = static_cast(event.tfinger.y * Environment::projectionHeight); + handleDown(static_cast(event.tfinger.fingerId), mx, my); + } + else if (event.type == SDL_FINGERUP) { + int mx = static_cast(event.tfinger.x * Environment::projectionWidth); + int my = static_cast(event.tfinger.y * Environment::projectionHeight); + handleUp(static_cast(event.tfinger.fingerId), mx, my); + } + else if (event.type == SDL_FINGERMOTION) { + int mx = static_cast(event.tfinger.x * Environment::projectionWidth); + int my = static_cast(event.tfinger.y * Environment::projectionHeight); + handleMotion(static_cast(event.tfinger.fingerId), mx, my); + } +#endif if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) { // Преобразуем экранные пиксели в проекционные единицы int mx = static_cast((float)event.button.x / Environment::width * Environment::projectionWidth); int my = static_cast((float)event.button.y / Environment::height * Environment::projectionHeight); - if (event.type == SDL_MOUSEBUTTONDOWN) handleDown(mx, my); - else handleUp(mx, my); + if (event.type == SDL_MOUSEBUTTONDOWN) handleDown(ZL::UiManager::MOUSE_FINGER_ID, 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) { int mx = static_cast((float)event.motion.x / Environment::width * Environment::projectionWidth); int my = static_cast((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) { static const float zoomstep = 2.0f; if (event.wheel.y > 0) { @@ -459,8 +465,8 @@ namespace ZL } if (event.type == SDL_KEYUP) { - if (event.key.keysym.sym == SDLK_a) { - //Environment::shipState.position = { 9466.15820, 1046.00159, 18531.2090 }; + if (event.key.keysym.sym == SDLK_r) { + std::cout << "Camera position: x=" << x << " y=" << y << " z=" << z << std::endl; } } #endif @@ -475,6 +481,22 @@ namespace ZL } //#endif 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 auto* wsBase = dynamic_cast(networkClient.get()); if (wsBase) { @@ -503,67 +525,58 @@ namespace ZL } mainThreadHandler.processMainThreadTasks(); - if (spaceGameStarted) { + if (menuManager.shouldRenderSpace()) { space.update(); } } - void Game::handleDown(int mx, int my) + void Game::handleDown(int64_t fingerId, int mx, int my) { int uiX = mx; int uiY = Environment::projectionHeight - my; - menuManager.uiManager.onMouseDown(uiX, uiY); + menuManager.uiManager.onTouchDown(fingerId, uiX, uiY); - bool uiHandled = false; - - for (const auto& button : menuManager.uiManager.findButton("") ? std::vector>{} : std::vector>{}) { - (void)button; - } - - auto pressedSlider = [&]() -> std::shared_ptr { - for (const auto& slider : menuManager.uiManager.findSlider("") ? std::vector>{} : std::vector>{}) { - (void)slider; - } - return nullptr; - }(); - - if (!menuManager.uiManager.isUiInteraction()) { - if (spaceGameStarted) { + if (!menuManager.uiManager.isUiInteractionForFinger(fingerId)) { + if (menuManager.shouldRenderSpace()) { 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 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 (spaceGameStarted) { + if (!wasUiInteraction) { + if (menuManager.shouldRenderSpace()) { 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 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 (spaceGameStarted) { + if (!wasUiInteraction) { + if (menuManager.shouldRenderSpace()) { space.handleMotion(mx, my); } } - } diff --git a/src/Game.h b/src/Game.h index 208fa1e..7fd6d36 100644 --- a/src/Game.h +++ b/src/Game.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "MenuManager.h" #include "Space.h" @@ -50,9 +51,9 @@ namespace ZL { void drawUI(); void drawUnderMainMenu(); void drawLoading(); - void handleDown(int mx, int my); - void handleUp(int mx, int my); - void handleMotion(int mx, int my); + void handleDown(int64_t fingerId, int mx, int my); + void handleUp(int64_t fingerId, int mx, int my); + void handleMotion(int64_t fingerId, int mx, int my); #ifdef EMSCRIPTEN static Game* s_instance; @@ -65,14 +66,14 @@ namespace ZL { int64_t newTickCount; 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_MAX_TIME_INTERVAL = 1000; MenuManager menuManager; Space space; - - int spaceGameStarted = 0; }; diff --git a/src/GameConstants.cpp b/src/GameConstants.cpp new file mode 100644 index 0000000..1b617aa --- /dev/null +++ b/src/GameConstants.cpp @@ -0,0 +1,9 @@ +#include "GameConstants.h" + +namespace ZL +{ + const std::string defaultShaderName = "default"; + const std::string envShaderName = "env"; + const std::string textureUniformName = "Texture"; +} + diff --git a/src/GameConstants.h b/src/GameConstants.h new file mode 100644 index 0000000..2d5ae0b --- /dev/null +++ b/src/GameConstants.h @@ -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; +} + diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index fcd47f5..6b8c2f4 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -1,5 +1,5 @@ -#include "MenuManager.h" - +#include "MenuManager.h" +#include namespace ZL { @@ -10,294 +10,246 @@ namespace ZL { 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); - - settingsSavedRoot = loadUiFromFile("resources/config/settings.json", renderer, CONST_ZIP_FILE); + bool MenuManager::shouldRenderSpace() const + { + return state == GameState::Gameplay + || state == GameState::GameOver + || state == GameState::ConnectionLost; + } - multiplayerSavedRoot = loadUiFromFile("resources/config/multiplayer_menu.json", renderer, CONST_ZIP_FILE); - - 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 loadGameplayUI; - loadGameplayUI = [this]() { - uiManager.replaceRoot(uiSavedRoot); + // ── State: MainMenu ────────────────────────────────────────────────────── - auto velocityTv = uiManager.findTextView("velocityText"); - if (velocityTv) { - velocityTv->rect.x = 10.0f; - velocityTv->rect.y = static_cast(Environment::height) - velocityTv->rect.h - 10.0f; - } - else { - std::cerr << "Failed to find velocityText in UI" << std::endl; - } + void MenuManager::enterMainMenu() + { + state = GameState::MainMenu; + uiManager.replaceRoot(mainMenuRoot); - uiManager.startAnimationOnNode("backgroundNode", "bgScroll"); - 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; - } - }); + if (onMainMenuEntered) onMainMenuEntered(); - uiManager.setAnimationCallback("exitButton", "bgScroll", [this]() { - std::cerr << "Exit button bgScroll animation finished" << std::endl; - 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("singleButton", [this](const std::string&) { + enterShipSelectionSingle(); }); - uiManager.setButtonCallback("multiplayerButton", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) { - std::cerr << "Multiplayer 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 (onMultiplayerPressed) onMultiplayerPressed(nick, shipType); - }); + uiManager.setButtonCallback("multiplayerButton", [this](const std::string&) { + enterShipSelectionMulti(); + }); + } - 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 (onMultiplayerPressed) onMultiplayerPressed(nick, shipType); - }); + // ── State: ShipSelectionSingle ─────────────────────────────────────────── - uiManager.setButtonCallback("backButton", [this](const std::string& btnName) { - uiManager.popMenu(); - }); - } - else { - std::cerr << "Failed to push ship selection menu\n"; - } + void MenuManager::enterShipSelectionSingle() + { + state = GameState::ShipSelectionSingle; + uiManager.replaceRoot(shipSelectionRoot); + + 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) { - std::cerr << "Multiplayer button pressed → opening multiplayer menu\n"; + uiManager.setButtonCallback("cargoshipButton", [this](const std::string&) { + std::string nick = uiManager.getTextFieldValue("nicknameInput"); + if (nick.empty()) nick = "Player"; + enterGameplay(); + if (onSingleplayerPressed) onSingleplayerPressed(nick, 1); + }); - uiManager.startAnimationOnNode("playButton", "buttonsExit"); - uiManager.startAnimationOnNode("settingsButton", "buttonsExit"); - uiManager.startAnimationOnNode("multiplayerButton", "buttonsExit"); - uiManager.startAnimationOnNode("exitButton", "buttonsExit"); + uiManager.setButtonCallback("backButton", [this](const std::string&) { + enterMainMenu(); + }); + } - if (uiManager.pushMenuFromSavedRoot(multiplayerSavedRoot)) { + // ── State: ShipSelectionMulti ───────────────────────────────────────────── - uiManager.setButtonCallback("connectButton", [this](const std::string& buttonName) { - std::string serverAddress = uiManager.getTextFieldValue("serverInputField"); + void MenuManager::enterShipSelectionMulti() + { + state = GameState::ShipSelectionMulti; + uiManager.replaceRoot(shipSelectionRoot); - if (serverAddress.empty()) { - uiManager.setText("statusText", "Please enter server address"); - return; - } + uiManager.setButtonCallback("spaceshipButton", [this](const std::string&) { + std::string nick = uiManager.getTextFieldValue("nicknameInput"); + if (nick.empty()) nick = "Player"; + pendingMultiNick = nick; + pendingMultiShipType = 0; + enterConnecting(); + if (onMultiplayerPressed) onMultiplayerPressed(nick, 0); + }); - uiManager.setText("statusText", "Connecting to " + serverAddress + "..."); - std::cerr << "Connecting to server: " << serverAddress << std::endl; + uiManager.setButtonCallback("cargoshipButton", [this](const std::string&) { + 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) { - uiManager.popMenu(); - }); + // ── State: Connecting ──────────────────────────────────────────────────── - uiManager.setTextFieldCallback("serverInputField", - [this](const std::string& fieldName, const std::string& newText) { - std::cout << "Server input field changed to: " << newText << std::endl; - }); + void MenuManager::enterConnecting() + { + 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(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 { - std::cerr << "Failed to load multiplayer menu\n"; + else + { + uiManager.findButton("plusButton")->state = ButtonState::Normal; } - std::cerr << "Single button pressed: " << name << " -> open ship selection UI\n"; - if (!shipSelectionRoot) { - std::cerr << "Failed to load ship selection UI\n"; - return; + if (onVelocityChanged) onVelocityChanged(newVel); + }); + uiManager.setButtonPressCallback("minusButton", [this](const std::string&) { + 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)) { - 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 + { + uiManager.findButton("minusButton")->state = ButtonState::Normal; } - else { - std::cerr << "Failed to push ship selection menu\n"; + if (onVelocityChanged) onVelocityChanged(newVel); + }); + uiManager.setSliderCallback("velocitySlider", [this](const std::string&, float value) { + int newVel = static_cast(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) { - if (!uiGameOverShown) { - if (uiManager.pushMenuFromSavedRoot(gameOverSavedRoot)) { - 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"; - } + if (state == GameState::Gameplay) { + enterGameOver(score); } } -} + + void MenuManager::showConnectionLost() + { + if (state == GameState::Gameplay) { + enterConnectionLost(); + } + } + +} // namespace ZL \ No newline at end of file diff --git a/src/MenuManager.h b/src/MenuManager.h index 88757be..083492b 100644 --- a/src/MenuManager.h +++ b/src/MenuManager.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/Renderer.h" #include "Environment.h" #include "render/TextureManager.h" @@ -7,37 +7,71 @@ namespace ZL { 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: Renderer& renderer; - std::shared_ptr uiSavedRoot; - std::shared_ptr gameOverSavedRoot; - std::shared_ptr settingsSavedRoot; - std::shared_ptr multiplayerSavedRoot; + + // Pre-loaded UI roots (loaded once in setupMenu) + std::shared_ptr mainMenuRoot; + std::shared_ptr shipSelectionRoot; + std::shared_ptr connectingRoot; + std::shared_ptr connectionFailedRoot; + std::shared_ptr gameplayRoot; + std::shared_ptr gameOverRoot; + std::shared_ptr 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: - bool uiGameOverShown = false; - bool g_exitBgAnimating = false; - UiManager uiManager; MenuManager(Renderer& iRenderer); void setupMenu(); - //void showGameOver(); - void showGameOver(int score); + // Returns true for states where Space should render and run (Gameplay, GameOver, ConnectionLost) + 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 onMainMenuEntered; std::function onRestartPressed; std::function onVelocityChanged; std::function onFirePressed; - std::function onSingleplayerPressed; std::function onMultiplayerPressed; - }; -}; +} // namespace ZL \ No newline at end of file diff --git a/src/Space.cpp b/src/Space.cpp index b079041..3869c84 100644 --- a/src/Space.cpp +++ b/src/Space.cpp @@ -25,12 +25,16 @@ #include "network/LocalClient.h" #endif +#include "GameConstants.h" + namespace ZL { extern const char* CONST_ZIP_FILE; extern float x; + extern float y; + extern float z; Eigen::Quaternionf generateRandomQuaternion(std::mt19937& gen) { @@ -252,20 +256,32 @@ namespace ZL Space::~Space() { } + void Space::resetPlayerState() + { + shipAlive = true; + gameOver = false; + showExplosion = false; + explosionEmitter.setEmissionPoints(std::vector()); + 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() { menuManager.onRestartPressed = [this]() { - this->shipAlive = true; - this->gameOver = false; - this->showExplosion = false; - this->explosionEmitter.setEmissionPoints(std::vector()); - 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; + resetPlayerState(); if (networkClient) { try { @@ -276,7 +292,6 @@ namespace ZL std::cerr << "Client: Failed to send RESPAWN\n"; } } - this->playerScore = 0; 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 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 explosionCfgLoaded = explosionEmitter.loadFromJsonFile("resources/config/explosion_config.json", renderer, CONST_ZIP_FILE); explosionEmitter.setEmissionPoints(std::vector()); @@ -413,17 +431,13 @@ namespace ZL void Space::drawCubemap(float skyPercent) { - static const std::string defaultShaderName = "default"; - static const std::string envShaderName = "env_sky"; - static const std::string vPositionName = "vPosition"; - static const std::string vTexCoordName = "vTexCoord"; - static const std::string textureUniformName = "Texture"; + static const std::string envSkyShaderName = "env_sky"; static const std::string skyPercentUniformName = "skyPercent"; - renderer.shaderManager.PushShader(envShaderName); + renderer.shaderManager.PushShader(envSkyShaderName); renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1f(skyPercentUniformName, skyPercent); - renderer.EnableVertexAttribArray(vPositionName); + renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); @@ -471,24 +485,14 @@ namespace ZL renderer.PopMatrix(); renderer.PopProjectionMatrix(); - renderer.DisableVertexAttribArray(vPositionName); - renderer.shaderManager.PopShader(); CheckGlError(); } 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.RenderUniform1i(textureUniformName, 0); - renderer.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vTexCoordName); renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), @@ -509,15 +513,17 @@ namespace ZL glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); renderer.DrawVertexRenderStruct(spaceship); } + + + drawShipSparkEmitters(); } + renderer.PopMatrix(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - renderer.shaderManager.PushShader("default"); + renderer.shaderManager.PushShader(defaultShaderName); renderer.RenderUniform1i(textureUniformName, 0); - renderer.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vTexCoordName); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); renderer.PushMatrix(); @@ -527,62 +533,29 @@ namespace ZL renderer.TranslateMatrix(-Environment::shipState.position); for (const auto& p : projectiles) { if (p && p->isActive()) { - //p->draw(renderer); p->projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); } } renderer.PopMatrix(); glDisable(GL_BLEND); - renderer.DisableVertexAttribArray(vPositionName); - renderer.DisableVertexAttribArray(vTexCoordName); 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) { explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height, false); } - //glBindTexture(GL_TEXTURE_2D, basePlatformTexture->getTexID()); - //renderer.DrawVertexRenderStruct(basePlatform); - glDisable(GL_BLEND); renderer.PopMatrix(); renderer.PopProjectionMatrix(); - renderer.DisableVertexAttribArray(vPositionName); - renderer.DisableVertexAttribArray(vTexCoordName); - renderer.shaderManager.PopShader(); CheckGlError(); } 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.RenderUniform1i(textureUniformName, 0); - renderer.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vTexCoordName); - renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); @@ -601,31 +574,23 @@ namespace ZL renderer.RotateMatrix(boxCoordsArr[i].m); glBindTexture(GL_TEXTURE_2D, boxTexture->getTexID()); - //glBindTexture(GL_TEXTURE_2D, rockTexture->getTexID()); renderer.DrawVertexRenderStruct(boxRenderArr[i]); renderer.PopMatrix(); } renderer.PopProjectionMatrix(); - renderer.DisableVertexAttribArray(vPositionName); - renderer.DisableVertexAttribArray(vTexCoordName); - renderer.shaderManager.PopShader(); CheckGlError(); } void 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); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); glViewport(0, 0, Environment::width, Environment::height); + prepareSparkEmittersForDraw(); + CheckGlError(); float skyPercent = 0.0; @@ -663,17 +628,9 @@ namespace ZL } 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.RenderUniform1i(textureUniformName, 0); - renderer.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vTexCoordName); - renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); @@ -714,7 +671,6 @@ namespace ZL renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); - //renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset renderer.RotateMatrix(Environment::inverseShipMatrix); renderer.TranslateMatrix(-Environment::shipState.position); @@ -736,8 +692,6 @@ namespace ZL } renderer.PopProjectionMatrix(); - renderer.DisableVertexAttribArray(vPositionName); - renderer.DisableVertexAttribArray(vTexCoordName); renderer.shaderManager.PopShader(); CheckGlError(); @@ -991,17 +945,11 @@ namespace ZL renderer.PushMatrix(); renderer.LoadIdentity(); - renderer.EnableVertexAttribArray("vPosition"); - renderer.EnableVertexAttribArray("vColor"); - Eigen::Vector4f uColor(crosshairCfg.color.x(), crosshairCfg.color.y(), crosshairCfg.color.z(), crosshairCfg.alpha); renderer.RenderUniform4fv("uColor", uColor.data()); renderer.DrawVertexRenderStruct(crosshairMesh); - renderer.DisableVertexAttribArray("vPosition"); - renderer.DisableVertexAttribArray("vColor"); - renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); @@ -1330,7 +1278,6 @@ namespace ZL renderer.PushMatrix(); renderer.LoadIdentity(); - renderer.EnableVertexAttribArray("vPosition"); renderer.RenderUniform4fv("uColor", enemyColor.data()); // рамки @@ -1353,7 +1300,6 @@ namespace ZL drawLeadRing2D(lx, ly); } - renderer.DisableVertexAttribArray("vPosition"); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); @@ -1439,7 +1385,6 @@ namespace ZL renderer.PushMatrix(); renderer.LoadIdentity(); - renderer.EnableVertexAttribArray("vPosition"); renderer.RenderUniform4fv("uColor", enemyColor.data()); // стрелка @@ -1457,7 +1402,6 @@ namespace ZL drawLeadRing2D(lx, ly); } - renderer.DisableVertexAttribArray("vPosition"); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); @@ -1476,23 +1420,93 @@ namespace ZL targetWasVisible = false; } + void Space::updateSparkEmitters(float deltaMs) + { + // Local ship + SparkEmitter* sparkEmitterPtr; + if (Environment::shipState.shipType == 1) { + sparkEmitterPtr = &sparkEmitterCargo; + static std::vector 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 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 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) { auto now_ms = newTickCount; - SparkEmitter* sparkEmitterPtr; - - if (Environment::shipState.shipType == 1) { - sparkEmitterPtr = &sparkEmitterCargo; - } - else - { - sparkEmitterPtr = &sparkEmitter; - - } - sparkEmitterPtr->update(static_cast(delta)); - planetObject.update(static_cast(delta)); - if (firePressed) { firePressed = false; @@ -1580,7 +1594,7 @@ namespace ZL std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent(); networkClient->Send(msg); - std::cout << "Sending: " << msg << std::endl; + //std::cout << "Sending: " << msg << std::endl; } long long leftoverDelta = delta; @@ -1624,6 +1638,8 @@ namespace ZL remotePlayerStates[id] = playerState; } + updateSparkEmitters(static_cast(delta)); + for (auto& p : projectiles) { if (p && p->isActive()) { p->update(static_cast(delta), renderer); @@ -1634,42 +1650,11 @@ namespace ZL for (const auto& p : projectiles) { if (p && p->isActive()) { Vector3f worldPos = p->getPosition(); - //Vector3f rel = worldPos - Environment::shipState.position; - //Vector3f camPos = Environment::inverseShipMatrix * rel; - p->projectileEmitter.setEmissionPoints({ worldPos }); - p->projectileEmitter.emit(); + p->projectileEmitter.resetEmissionPoints({ worldPos }); p->projectileEmitter.update(static_cast(delta)); } } - /* - if (!projCameraPoints.empty()) { - projectileEmitter.setEmissionPoints(projCameraPoints); - projectileEmitter.emit(); - } - else { - projectileEmitter.setEmissionPoints(std::vector()); - }*/ - if (Environment::shipState.velocity > 0.1f) { - - sparkEmitterPtr->setIsActive(true); - - std::vector 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(delta)); - //projectileEmitter.update(static_cast(delta)); explosionEmitter.update(static_cast(delta)); if (showExplosion) { @@ -1690,6 +1675,7 @@ namespace ZL shipAlive = false; gameOver = true; + Environment::shipState.selectedVelocity = 0; Environment::shipState.velocity = 0.0f; showExplosion = true; @@ -1780,6 +1766,8 @@ namespace ZL } } + planetObject.update(static_cast(delta)); + // update velocity text if (shipAlive && !gameOver) { @@ -1831,7 +1819,7 @@ namespace ZL gameOver = true; Environment::shipState.velocity = 0.0f; std::cout << "Client: Lost connection to server\n"; - menuManager.showGameOver(this->playerScore); + menuManager.showConnectionLost(); } auto pending = networkClient->getPendingProjectiles(); @@ -1927,6 +1915,7 @@ namespace ZL for (int pid : disconnects) { remotePlayerStates.erase(pid); deadRemotePlayers.erase(pid); + remoteShipSparkEmitters.erase(pid); if (trackedTargetId == pid) { trackedTargetId = -1; targetAcquireAnim = 0.f; diff --git a/src/Space.h b/src/Space.h index 54f54d5..05395a3 100644 --- a/src/Space.h +++ b/src/Space.h @@ -66,6 +66,7 @@ namespace ZL { std::unique_ptr textRenderer; std::unordered_map remotePlayerStates; + std::unordered_map remoteShipSparkEmitters; float newShipVelocity = 0; @@ -98,7 +99,7 @@ namespace ZL { float projectileCooldownMs = 500.0f; int64_t lastProjectileFireTime = 0; int maxProjectiles = 500; - std::vector shipLocalEmissionPoints; + //std::vector shipLocalEmissionPoints; bool shipAlive = true; @@ -141,8 +142,13 @@ namespace ZL { void drawTargetHud(); // рисует рамку или стрелку int pickTargetId() const; // ???????? ???? (????: ????????? ????? ????????? ?????) + void resetPlayerState(); void clearTextRendererCache(); + void updateSparkEmitters(float deltaMs); + void prepareSparkEmittersForDraw(); + void drawShipSparkEmitters(); + // Crosshair HUD struct CrosshairConfig { bool enabled = true; diff --git a/src/SparkEmitter.cpp b/src/SparkEmitter.cpp index 748afd5..4baa1dc 100644 --- a/src/SparkEmitter.cpp +++ b/src/SparkEmitter.cpp @@ -8,6 +8,7 @@ #include "Environment.h" #include #include "utils/Utils.h" +#include "GameConstants.h" namespace ZL { @@ -25,12 +26,12 @@ namespace ZL { sparkQuad.data = VertexDataStruct(); } - SparkEmitter::SparkEmitter(const SparkEmitter& copyFrom) + SparkEmitter::SparkEmitter(const SparkEmitter& copyFrom) : particles(copyFrom.particles), emissionPoints(copyFrom.emissionPoints), lastEmissionTime(copyFrom.lastEmissionTime), emissionRate(copyFrom.emissionRate), isActive(copyFrom.isActive), drawPositions(copyFrom.drawPositions), - drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(copyFrom.drawDataDirty), - sparkQuad(copyFrom.sparkQuad), texture(copyFrom.texture), + drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(true), + texture(copyFrom.texture), maxParticles(copyFrom.maxParticles), particleSize(copyFrom.particleSize), biasX(copyFrom.biasX), speedRange(copyFrom.speedRange), zSpeedRange(copyFrom.zSpeedRange), @@ -39,6 +40,8 @@ namespace ZL { shaderProgramName(copyFrom.shaderProgramName), 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; } + 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) { draw(renderer, zoom, screenWidth, screenHeight, true); @@ -185,46 +200,24 @@ namespace ZL { throw std::runtime_error("Failed to load spark emitter config file 2!"); } - prepareDrawData(withRotation); - if (drawPositions.empty()) { 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.RenderUniform1i(textureUniformName, 0); - renderer.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vTexCoordName); - - //float aspectRatio = static_cast(screenWidth) / static_cast(screenHeight); - //renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, aspectRatio, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); + renderer.SetMatrix(); glBindTexture(GL_TEXTURE_2D, texture->getTexID()); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); - renderer.PushMatrix(); - //renderer.LoadIdentity(); - //renderer.TranslateMatrix({ 0, 0, -1.0f * zoom }); - + //renderer.PushMatrix(); renderer.DrawVertexRenderStruct(sparkQuad); - - renderer.PopMatrix(); - //renderer.PopProjectionMatrix(); + //renderer.PopMatrix(); glDisable(GL_BLEND); - - renderer.DisableVertexAttribArray(vPositionName); - renderer.DisableVertexAttribArray(vTexCoordName); renderer.shaderManager.PopShader(); } @@ -237,9 +230,19 @@ namespace ZL { auto elapsed = std::chrono::duration_cast( currentTime - lastEmissionTime).count(); - if (isActive && elapsed >= emissionRate) { - emit(); - lastEmissionTime = currentTime; + if (isActive && elapsed >= static_cast(emissionRate)) { + int emitCount = static_cast(elapsed / emissionRate); + float elapsedF = static_cast(elapsed); + for (int e = 0; e < emitCount; ++e) { + // e=0 — самая старая эмиссия, e=emitCount-1 — самая свежая + float ageMs = static_cast(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(emitCount * emissionRate)); + drawDataDirty = true; } @@ -278,7 +281,7 @@ namespace ZL { } } - void SparkEmitter::emit() { + void SparkEmitter::emit(float ageMs, float lerpT) { if (!configured) { throw std::runtime_error("Failed to load spark emitter config file 4!"); } @@ -286,7 +289,30 @@ namespace ZL { if (emissionPoints.empty()) return; 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; for (auto& particle : particles) { @@ -294,8 +320,9 @@ namespace ZL { initParticle(particle, i); particle.active = true; particle.lifeTime = 0; - particle.position = emissionPoints[i]; + particle.position = birthPos; particle.emitterIndex = i; + applyAge(particle, ageMs); particleFound = true; emitted = true; break; @@ -304,11 +331,11 @@ namespace ZL { if (!particleFound && !particles.empty()) { size_t oldestIndex = 0; - float maxLifeTime = 0; + float maxLifeRatio = 0; for (size_t j = 0; j < particles.size(); ++j) { - if (particles[j].lifeTime > maxLifeTime) { - maxLifeTime = particles[j].lifeTime; + if (particles[j].lifeTime > maxLifeRatio) { + maxLifeRatio = particles[j].lifeTime; oldestIndex = j; } } @@ -316,8 +343,9 @@ namespace ZL { initParticle(particles[oldestIndex], i); particles[oldestIndex].active = true; particles[oldestIndex].lifeTime = 0; - particles[oldestIndex].position = emissionPoints[i]; + particles[oldestIndex].position = birthPos; particles[oldestIndex].emitterIndex = i; + applyAge(particles[oldestIndex], ageMs); emitted = true; } } @@ -328,6 +356,14 @@ namespace ZL { } void SparkEmitter::setEmissionPoints(const std::vector& positions) { + prevEmissionPoints = emissionPoints; + emissionPoints = positions; + drawDataDirty = true; + } + + void SparkEmitter::resetEmissionPoints(const std::vector& positions) + { + prevEmissionPoints.clear(); emissionPoints = positions; drawDataDirty = true; } @@ -495,8 +531,9 @@ namespace ZL { std::cout << "Total emission points: " << emissionPoints.size() << std::endl; } else { - std::cerr << "Emission points parsed but empty" << std::endl; - throw std::runtime_error("Failed to load spark emitter config file 10!"); + setEmissionPoints({}); + std::cout << "Emission points parsed but empty" << std::endl; + //throw std::runtime_error("Failed to load spark emitter config file 10!"); } } else { @@ -583,25 +620,6 @@ namespace ZL { 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 f = j["fragmentShader"].get(); - 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; configured = true; std::cout << "SparkEmitter configuration loaded successfully!" << std::endl; diff --git a/src/SparkEmitter.h b/src/SparkEmitter.h index 744c32f..b41ee4b 100644 --- a/src/SparkEmitter.h +++ b/src/SparkEmitter.h @@ -26,6 +26,7 @@ namespace ZL { private: std::vector particles; std::vector emissionPoints; + std::vector prevEmissionPoints; std::chrono::steady_clock::time_point lastEmissionTime; float emissionRate; bool isActive; @@ -62,6 +63,7 @@ namespace ZL { float rate = 100.0f); void setEmissionPoints(const std::vector& positions); + void resetEmissionPoints(const std::vector& positions); void setTexture(std::shared_ptr tex); void setEmissionRate(float rate) { emissionRate = rate; } 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 = ""); 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, bool withRotation); diff --git a/src/UiManager.cpp b/src/UiManager.cpp index 8a7bcf9..b04a861 100644 --- a/src/UiManager.cpp +++ b/src/UiManager.cpp @@ -4,6 +4,7 @@ #include #include #include +#include "GameConstants.h" namespace ZL { @@ -61,23 +62,13 @@ namespace ZL { } if (!(*tex)) return; - static const std::string vPositionName = "vPosition"; - static const std::string vTexCoordName = "vTexCoord"; - static const std::string textureUniformName = "Texture"; - renderer.PushMatrix(); renderer.TranslateMatrix({ animOffsetX, animOffsetY, 0.0f }); renderer.ScaleMatrix({ animScaleX, animScaleY, 1.0f }); renderer.RenderUniform1i(textureUniformName, 0); - renderer.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vTexCoordName); - glBindTexture(GL_TEXTURE_2D, (*tex)->getTexID()); renderer.DrawVertexRenderStruct(mesh); - - renderer.DisableVertexAttribArray(vPositionName); - renderer.DisableVertexAttribArray(vTexCoordName); renderer.PopMatrix(); } @@ -148,14 +139,7 @@ namespace ZL { } 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.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vTexCoordName); - if (texTrack) { glBindTexture(GL_TEXTURE_2D, texTrack->getTexID()); renderer.DrawVertexRenderStruct(trackMesh); @@ -164,9 +148,44 @@ namespace ZL { glBindTexture(GL_TEXTURE_2D, texKnob->getTexID()); renderer.DrawVertexRenderStruct(knobMesh); } + } - renderer.DisableVertexAttribArray(vPositionName); - renderer.DisableVertexAttribArray(vTexCoordName); + void UiStaticImage::buildMesh() { + 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 { @@ -249,6 +268,7 @@ namespace ZL { if (j.contains("horizontal_gravity")) { std::string hg = j["horizontal_gravity"].get(); if (hg == "right") node->layoutSettings.hGravity = HorizontalGravity::Right; + else if (hg == "center") node->layoutSettings.hGravity = HorizontalGravity::Center; else node->layoutSettings.hGravity = HorizontalGravity::Left; } @@ -256,6 +276,7 @@ namespace ZL { if (j.contains("vertical_gravity")) { std::string vg = j["vertical_gravity"].get(); if (vg == "bottom") node->layoutSettings.vGravity = VerticalGravity::Bottom; + else if (vg == "center") node->layoutSettings.vGravity = VerticalGravity::Center; else node->layoutSettings.vGravity = VerticalGravity::Top; } @@ -403,6 +424,32 @@ namespace ZL { } } + if (typeStr == "StaticImage") { + auto img = std::make_shared(); + img->name = node->name; + img->rect = initialRect; + + std::string texPath; + if (j.contains("texture") && j["texture"].is_string()) { + texPath = j["texture"].get(); + } + else if (j.contains("textures") && j["textures"].is_object() && j["textures"].contains("normal")) { + texPath = j["textures"]["normal"].get(); + } + + if (!texPath.empty()) { + try { + auto data = CreateTextureDataFromPng(texPath.c_str(), zipFile.c_str()); + img->texture = std::make_shared(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") { auto tv = std::make_shared(); @@ -492,6 +539,7 @@ namespace ZL { sliders.clear(); textViews.clear(); textFields.clear(); + staticImages.clear(); collectButtonsAndSliders(root); nodeActiveAnims.clear(); @@ -503,6 +551,9 @@ namespace ZL { s->buildTrackMesh(); s->buildKnobMesh(); } + for (auto& img : staticImages) { + img->buildMesh(); + } } 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) { 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) { fLY = currentH - childH - child->localY; } + else if (child->layoutSettings.vGravity == VerticalGravity::Center) { + fLY = (currentH - childH) / 2.0f + child->localY; + } // Передаем рассчитанные 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; // Аналогично для курсора и фонового меша } + + // 5. Обновляем статическое изображение + if (node->staticImage) { + node->staticImage->rect = node->screenRect; + node->staticImage->buildMesh(); + } } void UiManager::updateAllLayouts() { @@ -690,6 +753,9 @@ namespace ZL { if (node->textField) { textFields.push_back(node->textField); } + if (node->staticImage) { + staticImages.push_back(node->staticImage); + } for (auto& c : node->children) collectButtonsAndSliders(c); } @@ -703,6 +769,16 @@ namespace ZL { return true; } + bool UiManager::setButtonPressCallback(const std::string& name, std::function 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, const std::string& trackPath, const std::string& knobPath, float initialValue, bool vertical) { @@ -788,8 +864,9 @@ namespace ZL { prev.buttons = buttons; prev.sliders = sliders; prev.textFields = textFields; - prev.pressedButton = pressedButton; - prev.pressedSlider = pressedSlider; + prev.staticImages = staticImages; + prev.pressedButtons = pressedButtons; + prev.pressedSliders = pressedSliders; prev.focusedTextField = focusedTextField; prev.path = ""; @@ -838,8 +915,9 @@ namespace ZL { buttons = s.buttons; sliders = s.sliders; textFields = s.textFields; - pressedButton = s.pressedButton; - pressedSlider = s.pressedSlider; + staticImages = s.staticImages; + pressedButtons = s.pressedButtons; + pressedSliders = s.pressedSliders; focusedTextField = s.focusedTextField; animCallbacks = s.animCallbacks; @@ -872,6 +950,9 @@ namespace ZL { renderer.PushMatrix(); renderer.LoadIdentity(); + for (const auto& img : staticImages) { + img->draw(renderer); + } for (const auto& b : buttons) { b->draw(renderer); } @@ -1051,21 +1132,25 @@ namespace ZL { } } - void UiManager::onMouseMove(int x, int y) { - for (auto& b : buttons) { - if (b->state != ButtonState::Disabled) - { - if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) { - if (b->state != ButtonState::Pressed) b->state = ButtonState::Hover; - } - else { - if (b->state != ButtonState::Pressed) b->state = ButtonState::Normal; + void UiManager::onTouchMove(int64_t fingerId, int x, int y) { + // Hover state updates only make sense for mouse (single pointer) + if (fingerId == MOUSE_FINGER_ID) { + for (auto& b : buttons) { + if (b->state != ButtonState::Disabled) + { + if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) { + if (b->state != ButtonState::Pressed) b->state = ButtonState::Hover; + } + else { + if (b->state != ButtonState::Pressed) b->state = ButtonState::Normal; + } } } } - if (pressedSlider) { - auto s = pressedSlider; + auto it = pressedSliders.find(fingerId); + if (it != pressedSliders.end()) { + auto s = it->second; float t; if (s->vertical) { 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) { if (b->state != ButtonState::Disabled) { if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) { 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) { if (s->rect.contains((float)x, (float)y)) { - pressedSlider = s; + pressedSliders[fingerId] = s; float t; if (s->vertical) { 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> clicked; - for (auto& b : buttons) { - if (!b) continue; - bool contains = b->rect.contains((float)x, (float)y); - - if (b->state == ButtonState::Pressed) { - if (contains && pressedButton == b) { - clicked.push_back(b); + auto btnIt = pressedButtons.find(fingerId); + if (btnIt != pressedButtons.end()) { + auto b = btnIt->second; + if (b) { + bool contains = b->rect.contains((float)x, (float)y); + if (b->state == ButtonState::Pressed) { + 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) { if (b->onClick) { b->onClick(b->name); } } - - pressedButton.reset(); - if (pressedSlider) pressedSlider.reset(); } void UiManager::onKeyPress(unsigned char key) { @@ -1261,6 +1351,13 @@ namespace ZL { return true; } + std::shared_ptr UiManager::findStaticImage(const std::string& name) { + for (auto& img : staticImages) { + if (img->name == name) return img; + } + return nullptr; + } + std::shared_ptr UiManager::findTextView(const std::string& name) { for (auto& tv : textViews) { if (tv->name == name) return tv; diff --git a/src/UiManager.h b/src/UiManager.h index a03fa85..e8f8c69 100644 --- a/src/UiManager.h +++ b/src/UiManager.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace ZL { @@ -60,11 +61,13 @@ namespace ZL { enum class HorizontalGravity { Left, + Center, Right }; enum class VerticalGravity { Bottom, // Обычно в OpenGL Y растет вверх, так что низ - это 0 + Center, Top }; @@ -91,6 +94,7 @@ namespace ZL { VertexRenderStruct mesh; std::function onClick; + std::function onPress; // fires on touch/mouse down // animation runtime float animOffsetX = 0.0f; @@ -159,6 +163,17 @@ namespace ZL { void draw(Renderer& renderer) const; }; + struct UiStaticImage { + std::string name; + UiRect rect; + std::shared_ptr texture; + + VertexRenderStruct mesh; + + void buildMesh(); + void draw(Renderer& renderer) const; + }; + struct UiNode { std::string name; LayoutType layoutType = LayoutType::Frame; @@ -185,6 +200,7 @@ namespace ZL { std::shared_ptr slider; std::shared_ptr textView; std::shared_ptr textField; + std::shared_ptr staticImage; // Анимации struct AnimStep { @@ -210,19 +226,35 @@ namespace ZL { public: 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 newRoot); void loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = ""); void draw(Renderer& renderer); - void onMouseMove(int x, int y); - void onMouseDown(int x, int y); - void onMouseUp(int x, int y); + // Multi-touch methods (used directly for touch events with per-finger IDs) + void onTouchDown(int64_t fingerId, 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 onKeyBackspace(); + // Returns true if any finger is currently interacting with UI 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() { @@ -241,6 +273,7 @@ namespace ZL { std::shared_ptr findButton(const std::string& name); bool setButtonCallback(const std::string& name, std::function cb); + bool setButtonPressCallback(const std::string& name, std::function cb); 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); @@ -256,6 +289,8 @@ namespace ZL { bool setTextFieldCallback(const std::string& name, std::function cb); std::string getTextFieldValue(const std::string& name); + std::shared_ptr findStaticImage(const std::string& name); + bool pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = ""); bool pushMenuFromSavedRoot(std::shared_ptr newRoot); bool popMenu(); @@ -301,12 +336,14 @@ namespace ZL { std::vector> sliders; std::vector> textViews; std::vector> textFields; + std::vector> staticImages; std::map, std::vector> nodeActiveAnims; std::map, std::function> animCallbacks; // key: (nodeName, animName) - std::shared_ptr pressedButton; - std::shared_ptr pressedSlider; + // Per-finger tracking for multi-touch support + std::map> pressedButtons; + std::map> pressedSliders; std::shared_ptr focusedTextField; struct MenuState { @@ -314,8 +351,9 @@ namespace ZL { std::vector> buttons; std::vector> sliders; std::vector> textFields; - std::shared_ptr pressedButton; - std::shared_ptr pressedSlider; + std::vector> staticImages; + std::map> pressedButtons; + std::map> pressedSliders; std::shared_ptr focusedTextField; std::string path; std::map, std::function> animCallbacks; diff --git a/src/main.cpp b/src/main.cpp index 6dda7cb..1258e80 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,10 +68,6 @@ static void applyResize(int logicalW, int logicalH) { SDL_SetWindowSize(ZL::Environment::window, physicalW, physicalH); } - // Обновляем ваши внутренние переменные окружения - ZL::Environment::width = physicalW; - ZL::Environment::height = physicalH; - // Пушим событие, чтобы движок пересчитал матрицы проекции SDL_Event e = {}; e.type = SDL_WINDOWEVENT; @@ -131,7 +127,11 @@ int main(int argc, char* argv[]) { // канваса и отправит SDL_WINDOWEVENT_RESIZED для настройки проекции. applyResize(canvasW, canvasH); + // Prevent mouse clicks from generating fake SDL_FINGERDOWN events (desktop browser) 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); @@ -240,68 +240,6 @@ int main(int argc, char *argv[]) { ZL::Environment::width = CONST_WIDTH; 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) { SDL_Log("SDL init failed: %s", SDL_GetError()); return 1; @@ -327,8 +265,6 @@ int main(int argc, char *argv[]) { game.update(); SDL_Delay(2); } -//#endif - } catch (const std::exception& e) { diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h index 191445d..a5dd5c9 100644 --- a/src/network/NetworkInterface.h +++ b/src/network/NetworkInterface.h @@ -34,6 +34,7 @@ namespace ZL { public: virtual ~INetworkClient() = default; virtual void Connect(const std::string& host, uint16_t port) = 0; + virtual void Disconnect() {} virtual void Send(const std::string& message) = 0; virtual bool IsConnected() const = 0; virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index d19463a..69301c7 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -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() { ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) { if (!ec) { diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h index bd01ba2..956aeae 100644 --- a/src/network/WebSocketClient.h +++ b/src/network/WebSocketClient.h @@ -54,6 +54,7 @@ namespace ZL { {} void Connect(const std::string& host, uint16_t port) override; + void Disconnect() override; void Poll() override; diff --git a/src/network/WebSocketClientEmscripten.cpp b/src/network/WebSocketClientEmscripten.cpp index e879a89..1cb8e96 100644 --- a/src/network/WebSocketClientEmscripten.cpp +++ b/src/network/WebSocketClientEmscripten.cpp @@ -26,6 +26,15 @@ namespace ZL { 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() { std::lock_guard lock(outgoingMutex); if (!socket_) return; diff --git a/src/network/WebSocketClientEmscripten.h b/src/network/WebSocketClientEmscripten.h index a6467ba..5e347e6 100644 --- a/src/network/WebSocketClientEmscripten.h +++ b/src/network/WebSocketClientEmscripten.h @@ -28,6 +28,7 @@ namespace ZL { virtual ~WebSocketClientEmscripten() = default; void Connect(const std::string& host, uint16_t port) override; + void Disconnect() override; void Send(const std::string& message) override; void Poll() override; diff --git a/src/planet/PlanetObject.cpp b/src/planet/PlanetObject.cpp index 02503a2..ad567e4 100644 --- a/src/planet/PlanetObject.cpp +++ b/src/planet/PlanetObject.cpp @@ -6,6 +6,7 @@ #include "StoneObject.h" #include "utils/TaskManager.h" #include "TextModel.h" +#include "GameConstants.h" namespace ZL { @@ -185,16 +186,10 @@ namespace ZL { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - static const std::string defaultShaderName2 = "planetBake"; - static const std::string vPositionName = "vPosition"; - static const std::string vTexCoordName = "vTexCoord"; - static const std::string textureUniformName = "Texture"; + static const std::string planetBakeShaderName = "planetBake"; - - renderer.shaderManager.PushShader(defaultShaderName2); + renderer.shaderManager.PushShader(planetBakeShaderName); renderer.RenderUniform1i(textureUniformName, 0); - renderer.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vTexCoordName); Triangle tr = planetData.getLodLevel().triangles[0]; @@ -260,8 +255,6 @@ namespace ZL { glDisable(GL_CULL_FACE); // Не забываем выключить, чтобы не сломать остальной рендер renderer.PopMatrix(); renderer.PopProjectionMatrix(); - renderer.DisableVertexAttribArray(vTexCoordName); - renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } @@ -298,23 +291,12 @@ namespace ZL { 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"; - 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); auto zRange = planetData.calculateZRange(dist); @@ -335,10 +317,9 @@ namespace ZL { const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); - renderer.RenderUniform1i("Texture", 0); + renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i("BakedTexture", 1); - Triangle tr = planetData.getLodLevel().triangles[0]; // Берем базовый треугольник Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании @@ -376,12 +357,6 @@ namespace ZL { renderer.PopMatrix(); renderer.PopProjectionMatrix(); - renderer.DisableVertexAttribArray(vTexCoordName); - renderer.DisableVertexAttribArray(vNormalName); - renderer.DisableVertexAttribArray("vTangent"); - renderer.DisableVertexAttribArray("vBinormal"); - renderer.DisableVertexAttribArray(vColorName); - renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); @@ -390,20 +365,10 @@ namespace ZL { void PlanetObject::drawStones(Renderer& renderer) { - static const std::string defaultShaderName2 = "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"; + static const std::string planetStoneShaderName = "planetStone"; - renderer.shaderManager.PushShader(defaultShaderName2); + renderer.shaderManager.PushShader(planetStoneShaderName); renderer.RenderUniform1i(textureUniformName, 0); - renderer.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vColorName); - renderer.EnableVertexAttribArray(vNormalName); - renderer.EnableVertexAttribArray(vTexCoordName); - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); auto zRange = planetData.calculateZRange(dist); @@ -468,10 +433,6 @@ namespace ZL { renderer.PopMatrix(); renderer.PopProjectionMatrix(); - renderer.DisableVertexAttribArray(vTexCoordName); - renderer.DisableVertexAttribArray(vNormalName); - renderer.DisableVertexAttribArray(vColorName); - renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); @@ -480,16 +441,10 @@ namespace ZL { void PlanetObject::drawAtmosphere(Renderer& renderer) { 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); glDepthMask(GL_FALSE); renderer.shaderManager.PushShader(defaultShaderName); - renderer.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vNormalName); - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); auto zRange = planetData.calculateZRange(dist); float currentZNear = zRange.first; @@ -571,9 +526,6 @@ namespace ZL { glDepthMask(GL_TRUE); renderer.PopMatrix(); renderer.PopProjectionMatrix(); - - renderer.DisableVertexAttribArray(vNormalName); - renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); @@ -581,20 +533,8 @@ namespace ZL { void PlanetObject::drawCamp(Renderer& renderer) { - static const std::string defaultShaderName2 = "default"; - 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(defaultShaderName); renderer.RenderUniform1i(textureUniformName, 0); - renderer.EnableVertexAttribArray(vPositionName); - renderer.EnableVertexAttribArray(vColorName); - renderer.EnableVertexAttribArray(vNormalName); - renderer.EnableVertexAttribArray(vTexCoordName); - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); auto zRange = planetData.calculateZRange(dist); @@ -655,10 +595,6 @@ namespace ZL { renderer.PopMatrix(); renderer.PopProjectionMatrix(); - renderer.DisableVertexAttribArray(vTexCoordName); - renderer.DisableVertexAttribArray(vNormalName); - renderer.DisableVertexAttribArray(vColorName); - renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 748b7f3..f50aec0 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -50,17 +50,14 @@ namespace ZL { Matrix4f r; - // ��������������� r.data()[0] = 2.f / width; r.data()[5] = 2.f / height; r.data()[10] = -1.f / depthRange; - // ��������� �������������� ����������� r.data()[1] = r.data()[2] = r.data()[3] = 0; r.data()[4] = r.data()[6] = r.data()[7] = 0; r.data()[8] = r.data()[9] = r.data()[11] = 0; - // ���������� (��������) r.data()[12] = -(xmax + xmin) / width; r.data()[13] = -(ymax + ymin) / height; r.data()[14] = zNear / depthRange; @@ -384,7 +381,7 @@ namespace ZL { 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) { @@ -395,7 +392,7 @@ namespace ZL { 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) @@ -407,7 +404,7 @@ namespace ZL { 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) @@ -419,7 +416,7 @@ namespace ZL { 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) @@ -431,7 +428,7 @@ namespace ZL { 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) @@ -443,7 +440,7 @@ namespace ZL { 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!!!!"); } + SetMatrix(); } void Renderer::LoadIdentity() @@ -703,19 +701,7 @@ namespace ZL { Matrix4f m = Matrix4f::Identity(); 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; if (ModelviewMatrixStack.size() == 0) @@ -733,17 +719,7 @@ namespace ZL { void Renderer::RotateMatrix(const Matrix3f& m3) { 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 = ModelviewMatrixStack.top() * m; @@ -782,22 +758,6 @@ namespace ZL { 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) { auto shader = shaderManager.GetCurrentShader(); @@ -887,8 +847,25 @@ namespace ZL { 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) { +#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 vTangent("vTangent"); static const std::string vBinormal("vBinormal"); @@ -902,7 +879,6 @@ namespace ZL { { glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.normalVBO->getBuffer()); VertexAttribPointer3fv(vNormal, 0, NULL); - EnableVertexAttribArray(vNormal); } else { @@ -912,7 +888,6 @@ namespace ZL { { glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.tangentVBO->getBuffer()); VertexAttribPointer3fv(vTangent, 0, NULL); - EnableVertexAttribArray(vTangent); } else { @@ -922,7 +897,6 @@ namespace ZL { { glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.binormalVBO->getBuffer()); VertexAttribPointer3fv(vBinormal, 0, NULL); - EnableVertexAttribArray(vBinormal); } else { @@ -932,7 +906,6 @@ namespace ZL { { glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.colorVBO->getBuffer()); VertexAttribPointer3fv(vColor, 0, NULL); - EnableVertexAttribArray(vColor); } else { @@ -942,7 +915,6 @@ namespace ZL { { glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.texCoordVBO->getBuffer()); VertexAttribPointer2fv(vTexCoord, 0, NULL); - EnableVertexAttribArray(vTexCoord); } else { @@ -951,8 +923,6 @@ namespace ZL { glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.positionVBO->getBuffer()); VertexAttribPointer3fv(vPosition, 0, NULL); - EnableVertexAttribArray(vPosition); - glDrawArrays(GL_TRIANGLES, 0, static_cast(VertexRenderStruct.data.PositionData.size())); } diff --git a/src/render/Renderer.h b/src/render/Renderer.h index 656752a..e203b34 100644 --- a/src/render/Renderer.h +++ b/src/render/Renderer.h @@ -127,13 +127,6 @@ namespace ZL { 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 RenderUniformMatrix4fv(const std::string& uniformName, bool transpose, const float* 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 DisableVertexAttribArray(const std::string& attribName); + void DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct); }; diff --git a/src/render/ShaderManager.cpp b/src/render/ShaderManager.cpp index 45c8140..db21934 100644 --- a/src/render/ShaderManager.cpp +++ b/src/render/ShaderManager.cpp @@ -167,7 +167,6 @@ namespace ZL { fragmentShader = readTextFile(fragmentShaderFileName); } - ///std::cout << "Shader: "<< vertexShader << std::endl; shaderResourceMap[shaderName] = std::make_shared(vertexShader, fragmentShader); } @@ -181,9 +180,26 @@ namespace ZL { 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); - 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!"); } + auto& prevShaderAttribList = shaderResourceMap[shaderStack.top()]->attribList; + for (auto& attrib : prevShaderAttribList) + { + glDisableVertexAttribArray(attrib.second); + } + shaderStack.pop(); if (shaderStack.size() == 0) { glUseProgram(0); } 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); + } } } diff --git a/src/render/ShaderManager.h b/src/render/ShaderManager.h index 2d7130f..c27f8c9 100644 --- a/src/render/ShaderManager.h +++ b/src/render/ShaderManager.h @@ -42,6 +42,7 @@ namespace ZL { void PushShader(const std::string& shaderName); void PopShader(); + void EnableVertexAttribArrays(); std::shared_ptr GetCurrentShader(); }; diff --git a/src/render/TextRenderer.cpp b/src/render/TextRenderer.cpp index 73e2db1..6e1c0ea 100644 --- a/src/render/TextRenderer.cpp +++ b/src/render/TextRenderer.cpp @@ -380,10 +380,6 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, atlasTexture->getTexID()); - - r->EnableVertexAttribArray("vPosition"); - r->EnableVertexAttribArray("vTexCoord"); - //for (size_t i = 0; i < text.length(); ++i) { // auto it = glyphs.find(text[i]); // 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); //} r->DrawVertexRenderStruct(cached.mesh); - - r->DisableVertexAttribArray("vPosition"); - r->DisableVertexAttribArray("vTexCoord"); r->shaderManager.PopShader(); // Сброс бинда текстуры не обязателен, но можно для чистоты