Compare commits

...

16 Commits

Author SHA1 Message Date
Vladislav Khorev
59b5bea540 Fixing particles 2026-03-08 19:07:02 +03:00
Vladislav Khorev
9da3cc4401 Fixing bug in particle emitter 2026-03-08 00:22:13 +03:00
Vladislav Khorev
4a542fd6c8 Added multi-touch, added on press down 2026-03-07 22:41:25 +03:00
Vladislav Khorev
a06cb77a66 Fixing minor stuff, refactoring 2026-03-07 20:51:34 +03:00
Vladislav Khorev
3c2c06ca40 Working on refactoring 2026-03-07 20:03:16 +03:00
Vladislav Khorev
928600acd4 Refactoring 2026-03-07 19:41:15 +03:00
Vladislav Khorev
7ccda081b0 Fixing bugs 2026-03-06 23:15:56 +03:00
Vladislav Khorev
41f370f2ee Minor fixing 2026-03-06 22:34:03 +03:00
Vladislav Khorev
4af20c65e6 Make menu to state machine 2026-03-06 21:52:00 +03:00
Vladislav Khorev
ac551122d9 Added force disconnect from server 2026-03-06 20:05:55 +03:00
Vladislav Khorev
8528b4dc90 Clean up server 2026-03-06 19:08:55 +03:00
Vladislav Khorev
c35e093cc5 Working on encoding 2026-03-06 17:51:46 +03:00
Vladislav Khorev
a8c76dee3c Working on fixing minor bugs 2026-03-05 23:58:35 +03:00
Vladislav Khorev
e84a094ad9 Merge branch 'spark' 2026-03-05 19:54:24 +03:00
Vladislav Khorev
b0b7391939 Fixed low quality 2026-03-05 19:53:27 +03:00
Vlad
3e2632c5b5 added textures 2026-03-05 20:14:02 +06:00
83 changed files with 1861 additions and 1426 deletions

View File

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

View File

@ -1,9 +1,14 @@
# how to build # how to build
If emsdk is not installed, you need to clone it from here: https://github.com/emscripten-core/emsdk
Activate the environment: and install:
``` ```
C:\Work\Projects\emsdk\emsdk.bat install latest C:\Work\Projects\emsdk\emsdk.bat install latest
```
Then activate the environment:
```
C:\Work\Projects\emsdk\emsdk.bat activate latest C:\Work\Projects\emsdk\emsdk.bat activate latest
C:\Work\Projects\emsdk\emsdk_env.bat C:\Work\Projects\emsdk\emsdk_env.bat
``` ```

View File

@ -17,23 +17,10 @@
width: 100vw; height: 100vh; width: 100vw; height: 100vh;
border: none; border: none;
} }
/* Кнопка Fullscreen */
#fs-button {
position: absolute;
top: 10px; right: 10px;
padding: 10px;
z-index: 10;
background: rgba(255,255,255,0.3);
color: white; border: 1px solid white;
cursor: pointer;
font-family: sans-serif;
border-radius: 5px;
}
#status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } #status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
</style> </style>
</head> </head>
<body> <body>
<button id="fs-button">Fullscreen</button>
<div id="status">Downloading...</div> <div id="status">Downloading...</div>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas> <canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
@ -49,17 +36,6 @@
} }
}; };
// Кнопка Fullscreen
document.getElementById('fs-button').addEventListener('click', function() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(e => {
console.error(`Error attempting to enable full-screen mode: ${e.message}`);
});
} else {
document.exitFullscreen();
}
});
// Обработка ориентации // Обработка ориентации
window.addEventListener("orientationchange", function() { window.addEventListener("orientationchange", function() {
// Chrome на Android обновляет innerWidth/Height не мгновенно. // Chrome на Android обновляет innerWidth/Height не мгновенно.

View File

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

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

@ -65,8 +65,8 @@
"height": 56, "height": 56,
"textures": { "textures": {
"normal": "resources/game_over/Filledbuttons.png", "normal": "resources/game_over/Filledbuttons.png",
"hover": "resources/game_over/Filledbuttons.png", "hover": "resources/game_over/Variant5.png",
"pressed": "resources/game_over/Filledbuttons.png" "pressed": "resources/game_over/Variant6.png"
} }
}, },
{ {

View File

@ -11,26 +11,18 @@
"height": "match_parent", "height": "match_parent",
"children": [ "children": [
{ {
"type": "Button", "type": "StaticImage",
"name": "titleBtn", "name": "titleBtn",
"width": 254, "width": 254,
"height": 35, "height": 35,
"textures": { "texture": "resources/main_menu/title.png"
"normal": "resources/main_menu/title.png",
"hover": "resources/main_menu/title.png",
"pressed": "resources/main_menu/title.png"
}
}, },
{ {
"type": "Button", "type": "StaticImage",
"name": "underlineBtn", "name": "underlineBtn",
"width": 168, "width": 168,
"height": 44, "height": 44,
"textures": { "texture": "resources/main_menu/line.png"
"normal": "resources/main_menu/line.png",
"hover": "resources/main_menu/line.png",
"pressed": "resources/main_menu/line.png"
}
}, },
{ {
"type": "Button", "type": "Button",
@ -50,8 +42,8 @@
"height": 56, "height": 56,
"textures": { "textures": {
"normal": "resources/main_menu/single.png", "normal": "resources/main_menu/single.png",
"hover": "resources/main_menu/single.png", "hover": "resources/main_menu/Variant5.png",
"pressed": "resources/main_menu/single.png" "pressed": "resources/main_menu/Variant6.png"
} }
}, },
{ {
@ -61,20 +53,16 @@
"height": 56, "height": 56,
"textures": { "textures": {
"normal": "resources/main_menu/multi.png", "normal": "resources/main_menu/multi.png",
"hover": "resources/main_menu/multi.png", "hover": "resources/main_menu/Variant7.png",
"pressed": "resources/main_menu/multi.png" "pressed": "resources/main_menu/Variant8.png"
} }
}, },
{ {
"type": "Button", "type": "StaticImage",
"name": "versionLabel", "name": "versionLabel",
"width": 81, "width": 81,
"height": 9, "height": 9,
"textures": { "texture": "resources/main_menu/version.png"
"normal": "resources/main_menu/version.png",
"hover": "resources/main_menu/version.png",
"pressed": "resources/main_menu/version.png"
}
} }
] ]
} }

View File

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

View File

@ -0,0 +1,20 @@
{
"emissionRate": 0.4,
"maxParticles": 400,
"particleSize": 0.3,
"biasX": 0.3,
"emissionPoints": [
{
"position": [0.0, 2.8, -3.5]
},
{
"position": [0.0, 1.5, -3.5]
}
],
"speedRange": [0.5, 2.0],
"zSpeedRange": [1.0, 3.0],
"scaleRange": [0.8, 1.2],
"lifeTimeRange": [600.0, 1400.0],
"texture": "resources/spark.png",
"shaderProgramName": "spark"
}

View File

@ -2,14 +2,14 @@
"emissionPoints": [ "emissionPoints": [
{ "position": [0.0, 0.0, 0.0] } { "position": [0.0, 0.0, 0.0] }
], ],
"texture": "resources/spark_white.png", "texture": "resources/spark2.png",
"speedRange": [10.0, 30.0], "speedRange": [5.0, 10.0],
"zSpeedRange": [-1.0, 1.0], "zSpeedRange": [-1.0, 1.0],
"scaleRange": [0.5, 1.0], "scaleRange": [0.5, 2.0],
"lifeTimeRange": [200.0, 800.0], "lifeTimeRange": [200.0, 800.0],
"emissionRate": 50.0, "emissionRate": 30.0,
"maxParticles": 10, "maxParticles": 150,
"particleSize": 0.5, "particleSize": 1.0,
"biasX": 0.1, "biasX": 0.1,
"shaderProgramName": "default" "shaderProgramName": "default"
} }

View File

@ -6,6 +6,20 @@
"width": "match_parent", "width": "match_parent",
"height": "match_parent", "height": "match_parent",
"children": [ "children": [
{
"type": "TextView",
"name": "velocityText",
"x": 10,
"y": 10,
"width": 200,
"height": 40,
"horizontal_gravity": "left",
"vertical_gravity": "top",
"text": "Velocity: 0",
"fontSize": 24,
"color": [1.0, 1.0, 1.0, 1.0],
"centered": false
},
{ {
"type": "Button", "type": "Button",
"name": "shootButton", "name": "shootButton",
@ -16,9 +30,10 @@
"horizontal_gravity": "right", "horizontal_gravity": "right",
"vertical_gravity": "bottom", "vertical_gravity": "bottom",
"textures": { "textures": {
"normal": "resources/shoot_normal.png", "normal": "resources/fire.png",
"hover": "resources/shoot_hover.png", "hover": "resources/fire.png",
"pressed": "resources/shoot_pressed.png" "pressed": "resources/fire2.png",
"disabled": "resources/fire_disabled.png"
} }
}, },
{ {
@ -31,25 +46,42 @@
"horizontal_gravity": "left", "horizontal_gravity": "left",
"vertical_gravity": "bottom", "vertical_gravity": "bottom",
"textures": { "textures": {
"normal": "resources/shoot_normal.png", "normal": "resources/fire.png",
"hover": "resources/shoot_hover.png", "hover": "resources/fire.png",
"pressed": "resources/shoot_pressed.png" "pressed": "resources/fire2.png",
"disabled": "resources/fire_disabled.png"
} }
}, },
{ {
"type": "Slider", "type": "Button",
"name": "velocitySlider", "name": "minusButton",
"x": 10, "x": -20,
"y": 200, "y": 110,
"width": 80, "width": 150,
"height": 300, "height": 150,
"value": 0.0, "border" : 20,
"orientation": "vertical",
"horizontal_gravity": "right", "horizontal_gravity": "right",
"vertical_gravity": "bottom", "vertical_gravity": "bottom",
"textures": { "textures": {
"track": "resources/velocitySliderTexture.png", "normal": "resources/button_minus.png",
"knob": "resources/velocitySliderButton.png" "hover": "resources/button_minus_pressed.png",
"pressed": "resources/button_minus_pressed.png"
}
},
{
"type": "Button",
"name": "plusButton",
"x": -20,
"y": 220,
"width": 150,
"height": 150,
"border" : 20,
"horizontal_gravity": "right",
"vertical_gravity": "bottom",
"textures": {
"normal": "resources/button_plus.png",
"hover": "resources/button_plus_pressed.png",
"pressed": "resources/button_plus_pressed.png"
} }
} }
] ]

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

View File

@ -26,6 +26,7 @@ add_subdirectory("${BOOST_SRC_DIR}/libs/predef" boost-predef-build EXCLUDE_FROM_
# Исполняемый файл сервера # Исполняемый файл сервера
add_executable(Server add_executable(Server
server.h
server.cpp server.cpp
../src/network/ClientState.h ../src/network/ClientState.h
../src/network/ClientState.cpp ../src/network/ClientState.cpp

View File

@ -1,44 +1,11 @@
#include <boost/beast/core.hpp> #include "server.h"
#include <boost/beast/websocket.hpp> #include <boost/beast/websocket.hpp>
#include <boost/asio/ip/tcp.hpp> #include <boost/asio/ip/tcp.hpp>
#include <iostream> #include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <mutex>
#include <map>
#include <queue>
#include <sstream> #include <sstream>
#include <Eigen/Dense>
#define _USE_MATH_DEFINES
#include <math.h>
#include "../src/network/ClientState.h"
#include <random> #include <random>
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <unordered_set>
namespace beast = boost::beast;
namespace http = beast::http;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = net::ip::tcp;
static constexpr float kWorldZOffset = 45000.0f;
static const Eigen::Vector3f kWorldOffset(0.0f, 0.0f, kWorldZOffset);
static constexpr float kShipRadius = 15.0f;
static constexpr float kSpawnShipMargin = 25.0f;
static constexpr float kSpawnBoxMargin = 15.0f;
static constexpr float kSpawnZJitter = 60.0f;
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId);
struct DeathInfo {
int targetId = -1;
uint64_t serverTime = 0;
Eigen::Vector3f position = Eigen::Vector3f::Zero();
int killerId = -1;
};
std::vector<std::string> split(const std::string& s, char delimiter) { std::vector<std::string> split(const std::string& s, char delimiter) {
std::vector<std::string> tokens; std::vector<std::string> tokens;
@ -50,75 +17,30 @@ std::vector<std::string> split(const std::string& s, char delimiter) {
return tokens; return tokens;
} }
struct ServerBox { Session::Session(Server& server, tcp::socket&& socket, int id)
Eigen::Vector3f position; : server_(server)
Eigen::Matrix3f rotation; , ws_(std::move(socket))
float collisionRadius = 2.0f; , id_(id)
bool destroyed = false; , lastReceivedTime_(std::chrono::system_clock::now()) {
};
struct Projectile {
int shooterId = -1;
uint64_t spawnMs = 0;
Eigen::Vector3f pos;
Eigen::Vector3f vel;
float lifeMs = PROJECTILE_LIFE;
};
struct BoxDestroyedInfo {
int boxIndex = -1;
uint64_t serverTime = 0;
Eigen::Vector3f position = Eigen::Vector3f::Zero();
int destroyedBy = -1;
};
std::vector<BoxDestroyedInfo> g_boxDestructions;
std::mutex g_boxDestructions_mutex;
std::vector<ServerBox> g_serverBoxes;
std::mutex g_boxes_mutex;
std::vector<std::shared_ptr<class Session>> g_sessions;
std::mutex g_sessions_mutex;
std::vector<Projectile> g_projectiles;
std::mutex g_projectiles_mutex;
std::unordered_set<int> g_dead_players;
std::mutex g_dead_mutex;
class Session;
void broadcastToAll(const std::string& message);
class Session : public std::enable_shared_from_this<Session> {
websocket::stream<beast::tcp_stream> ws_;
beast::flat_buffer buffer_;
int id_;
bool is_writing_ = false;
std::queue<std::shared_ptr<std::string>> writeQueue_;
std::mutex writeMutex_;
public:
ClientStateInterval timedClientStates;
bool joined_ = false;
bool hasReservedSpawn_ = false;
Eigen::Vector3f reservedSpawn_ = Eigen::Vector3f(0.0f, 0.0f, kWorldZOffset);
std::string nickname = "Player";
int shipType = 0;
explicit Session(tcp::socket&& socket, int id)
: ws_(std::move(socket)), id_(id) {
} }
int get_id() const { return id_; } bool Session::is_timed_out(std::chrono::system_clock::time_point now) const {
if (!joined_) return false;
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastReceivedTime_).count();
return elapsed > PLAYER_TIMEOUT_MS;
}
bool hasSpawnReserved() const { return hasReservedSpawn_; } void Session::force_disconnect() {
const Eigen::Vector3f& reservedSpawn() const { return reservedSpawn_; } ws_.async_close(websocket::close_code::normal,
[self = shared_from_this()](beast::error_code) {});
}
bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const { int Session::get_id() const { return id_; }
bool Session::hasSpawnReserved() const { return hasReservedSpawn_; }
const Eigen::Vector3f& Session::reservedSpawn() const { return reservedSpawn_; }
bool Session::fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const {
if (timedClientStates.canFetchClientStateAtTime(targetTime)) { if (timedClientStates.canFetchClientStateAtTime(targetTime)) {
outState = timedClientStates.fetchClientStateAtTime(targetTime); outState = timedClientStates.fetchClientStateAtTime(targetTime);
return true; return true;
@ -126,7 +48,7 @@ public:
return false; return false;
} }
void send_message(const std::string& msg) { void Session::send_message(const std::string& msg) {
auto ss = std::make_shared<std::string>(msg); auto ss = std::make_shared<std::string>(msg);
{ {
std::lock_guard<std::mutex> lock(writeMutex_); std::lock_guard<std::mutex> lock(writeMutex_);
@ -135,10 +57,10 @@ public:
doWrite(); doWrite();
} }
void run() { void Session::run() {
{ {
std::lock_guard<std::mutex> lock(g_sessions_mutex); std::lock_guard<std::mutex> lock(server_.g_sessions_mutex);
g_sessions.push_back(shared_from_this()); server_.g_sessions.push_back(shared_from_this());
} }
ws_.async_accept([self = shared_from_this()](beast::error_code ec) { ws_.async_accept([self = shared_from_this()](beast::error_code ec) {
@ -148,7 +70,7 @@ public:
}); });
} }
bool IsMessageValid(const std::string& fullMessage) { bool Session::IsMessageValid(const std::string& fullMessage) {
#ifdef ENABLE_NETWORK_CHECKSUM #ifdef ENABLE_NETWORK_CHECKSUM
size_t hashPos = fullMessage.find("#hash:"); size_t hashPos = fullMessage.find("#hash:");
if (hashPos == std::string::npos) { if (hashPos == std::string::npos) {
@ -170,16 +92,14 @@ public:
#endif #endif
} }
private: void Session::sendBoxesToClient() {
std::lock_guard<std::mutex> lock(server_.g_boxes_mutex);
void sendBoxesToClient() {
std::lock_guard<std::mutex> lock(g_boxes_mutex);
std::string boxMsg = "BOXES:"; std::string boxMsg = "BOXES:";
bool first = true; bool first = true;
for (size_t i = 0; i < g_serverBoxes.size(); ++i) { for (size_t i = 0; i < server_.g_serverBoxes.size(); ++i) {
const auto& box = g_serverBoxes[i]; const auto& box = server_.g_serverBoxes[i];
if (box.destroyed) continue; if (box.destroyed) continue;
@ -199,14 +119,11 @@ private:
"0"; "0";
} }
// Если все коробки уничтожены — отправится просто "BOXES:" (это нормально)
send_message(boxMsg); send_message(boxMsg);
} }
public: void Session::init()
void init()
{ {
sendBoxesToClient(); sendBoxesToClient();
@ -224,23 +141,17 @@ public:
}); });
} }
ClientState get_latest_state(std::chrono::system_clock::time_point now) { ClientState Session::get_latest_state(std::chrono::system_clock::time_point now) {
if (timedClientStates.timedStates.empty()) { if (timedClientStates.timedStates.empty()) {
return {}; return {};
} }
// 1. Берем самое последнее известное состояние
ClientState latest = timedClientStates.timedStates.back(); ClientState latest = timedClientStates.timedStates.back();
// 3. Применяем компенсацию лага (экстраполяцию).
// Функция внутри использует simulate_physics, чтобы переместить объект
// из точки lastUpdateServerTime в точку now.
latest.apply_lag_compensation(now); latest.apply_lag_compensation(now);
return latest; return latest;
} }
void doWrite() { void Session::doWrite() {
std::lock_guard<std::mutex> lock(writeMutex_); std::lock_guard<std::mutex> lock(writeMutex_);
if (is_writing_ || writeQueue_.empty()) { if (is_writing_ || writeQueue_.empty()) {
return; return;
@ -263,17 +174,20 @@ public:
self->doWrite(); self->doWrite();
}); });
} }
private:
void do_read() { void Session::do_read() {
ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) { ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
if (ec) { if (ec) {
std::lock_guard<std::mutex> lock(g_sessions_mutex); if (self->joined_) {
g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(), self->server_.broadcastToAllExceptId("PLAYER_LEFT:" + std::to_string(self->id_), self->id_);
std::cout << "Client " << self->id_ << " disconnected, broadcasting PLAYER_LEFT\n";
}
std::lock_guard<std::mutex> lock(self->server_.g_sessions_mutex);
self->server_.g_sessions.erase(std::remove_if(self->server_.g_sessions.begin(), self->server_.g_sessions.end(),
[self](const std::shared_ptr<Session>& session) { [self](const std::shared_ptr<Session>& session) {
return session.get() == self.get(); return session.get() == self.get();
}), g_sessions.end()); }), self->server_.g_sessions.end());
std::cout << "Client " << self->id_ << " disconnected\n"; std::cout << "Client " << self->id_ << " removed from session list\n";
return; return;
} }
@ -285,11 +199,12 @@ private:
}); });
} }
void process_message(const std::string& msg) { void Session::process_message(const std::string& msg) {
if (!IsMessageValid(msg)) { if (!IsMessageValid(msg)) {
std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl; std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl;
return; return;
} }
lastReceivedTime_ = std::chrono::system_clock::now();
std::string cleanMessage = msg.substr(0, msg.find("#hash:")); std::string cleanMessage = msg.substr(0, msg.find("#hash:"));
std::cout << "Received from player " << id_ << ": " << cleanMessage << std::endl; std::cout << "Received from player " << id_ << ": " << cleanMessage << std::endl;
@ -316,7 +231,7 @@ private:
uint64_t now_ms = static_cast<uint64_t>( uint64_t now_ms = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count()); std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
Eigen::Vector3f spawnPos = PickSafeSpawnPos(id_); Eigen::Vector3f spawnPos = server_.PickSafeSpawnPos(id_);
this->hasReservedSpawn_ = true; this->hasReservedSpawn_ = true;
this->reservedSpawn_ = spawnPos; this->reservedSpawn_ = spawnPos;
@ -342,27 +257,16 @@ private:
std::string eventMsg = std::string eventMsg =
"EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent(); "EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent();
{ server_.broadcastToAllExceptId(eventMsg, id_);
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() == id_) continue;
session->send_message(eventMsg);
}
}
std::cout << "Server: Player " << id_ << " joined as [" << nick << "] shipType=" << sType << std::endl; std::cout << "Server: Player " << id_ << " joined as [" << nick << "] shipType=" << sType << std::endl;
std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType); std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType);
server_.broadcastToAllExceptId(info, id_);
{ {
std::lock_guard<std::mutex> lock(g_sessions_mutex); std::lock_guard<std::mutex> lock(server_.g_sessions_mutex);
for (auto& session : g_sessions) { for (auto& session : server_.g_sessions) {
if (session->get_id() == this->id_) continue;
session->send_message(info);
}
}
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() == this->id_) continue; if (session->get_id() == this->id_) continue;
std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType); std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType);
@ -376,8 +280,8 @@ private:
return; return;
} }
{ {
std::lock_guard<std::mutex> gd(g_dead_mutex); std::lock_guard<std::mutex> gd(server_.g_dead_mutex);
if (g_dead_players.find(id_) != g_dead_players.end()) { if (server_.g_dead_players.find(id_) != server_.g_dead_players.end()) {
std::cout << "Server: Ignoring UPD from dead player " << id_ << std::endl; std::cout << "Server: Ignoring UPD from dead player " << id_ << std::endl;
return; return;
} }
@ -399,8 +303,8 @@ private:
} }
else if (type == "RESPAWN") { else if (type == "RESPAWN") {
{ {
std::lock_guard<std::mutex> gd(g_dead_mutex); std::lock_guard<std::mutex> gd(server_.g_dead_mutex);
g_dead_players.erase(id_); server_.g_dead_players.erase(id_);
} }
{ {
@ -410,7 +314,7 @@ private:
ClientState st; ClientState st;
st.id = id_; st.id = id_;
Eigen::Vector3f spawnPos = PickSafeSpawnPos(id_); Eigen::Vector3f spawnPos = server_.PickSafeSpawnPos(id_);
st.position = spawnPos; st.position = spawnPos;
this->hasReservedSpawn_ = true; this->hasReservedSpawn_ = true;
@ -431,13 +335,13 @@ private:
"SPAWN:" + std::to_string(id_) + ":" + std::to_string(now_ms) + ":" + st.formPingMessageContent() "SPAWN:" + std::to_string(id_) + ":" + std::to_string(now_ms) + ":" + st.formPingMessageContent()
); );
std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_); std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_);
broadcastToAll(respawnMsg); server_.broadcastToAll(respawnMsg);
std::string playerInfo = "PLAYERINFO:" + std::to_string(id_) + ":" + st.nickname + ":" + std::to_string(st.shipType); std::string playerInfo = "PLAYERINFO:" + std::to_string(id_) + ":" + st.nickname + ":" + std::to_string(st.shipType);
broadcastToAll(playerInfo); server_.broadcastToAll(playerInfo);
std::string eventMsg = "EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent(); std::string eventMsg = "EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent();
broadcastToAll(eventMsg); server_.broadcastToAll(eventMsg);
std::cout << "Server: Player " << id_ << " respawned, broadcasted RESPAWN_ACK, PLAYERINFO and initial UPD\n"; std::cout << "Server: Player " << id_ << " respawned, broadcasted RESPAWN_ACK, PLAYERINFO and initial UPD\n";
} }
@ -454,12 +358,6 @@ private:
); );
float velocity = std::stof(parts[9]); float velocity = std::stof(parts[9]);
int shotCount = 2;
if (parts.size() >= 11) {
try { shotCount = std::stoi(parts[10]); }
catch (...) { shotCount = 2; }
}
std::string broadcast = "PROJECTILE:" + std::string broadcast = "PROJECTILE:" +
std::to_string(id_) + ":" + std::to_string(id_) + ":" +
std::to_string(clientTime) + ":" + std::to_string(clientTime) + ":" +
@ -472,50 +370,14 @@ private:
std::to_string(dir.z()) + ":" + std::to_string(dir.z()) + ":" +
std::to_string(velocity); std::to_string(velocity);
{
std::lock_guard<std::mutex> lock(g_sessions_mutex); server_.broadcastToAllExceptId(broadcast, id_);
for (auto& session : g_sessions) {
if (session->get_id() != id_) { server_.createProjectile(id_, pos, dir, velocity);
session->send_message(broadcast);
}
} }
} }
{ Eigen::Vector3f Server::PickSafeSpawnPos(int forPlayerId)
const std::vector<Eigen::Vector3f> localOffsets = {
Eigen::Vector3f(-1.5f, 0.9f - 6.f, 5.0f),
Eigen::Vector3f(1.5f, 0.9f - 6.f, 5.0f)
};
uint64_t now_ms = std::chrono::duration_cast<std::chrono::milliseconds>((
std::chrono::system_clock::now().time_since_epoch())).count();
std::lock_guard<std::mutex> pl(g_projectiles_mutex);
for (int i = 0; i < std::min(shotCount, (int)localOffsets.size()); ++i) {
Projectile pr;
pr.shooterId = id_;
pr.spawnMs = now_ms;
Eigen::Vector3f shotPos = pos + dir.toRotationMatrix() * localOffsets[i];
pr.pos = shotPos;
Eigen::Vector3f localForward(0.0f, 0.0f, -1.0f);
Eigen::Vector3f worldForward = dir.toRotationMatrix() * localForward;
float len = worldForward.norm();
if (len > 1e-6f) worldForward /= len;
pr.vel = worldForward * velocity;
pr.lifeMs = 15000.0f;
g_projectiles.push_back(pr);
std::cout << "Server: Created projectile from player " << id_
<< " at pos (" << shotPos.x() << ", " << shotPos.y() << ", " << shotPos.z()
<< ") vel (" << pr.vel.x() << ", " << pr.vel.y() << ", " << pr.vel.z() << ")" << std::endl;
}
}
}
}
};
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId)
{ {
static thread_local std::mt19937 rng{ std::random_device{}() }; static thread_local std::mt19937 rng{ std::random_device{}() };
@ -580,14 +442,54 @@ Eigen::Vector3f PickSafeSpawnPos(int forPlayerId)
return Eigen::Vector3f(600.0f + a * 100.0f, -600.0f + b * 100.0f, kWorldZOffset); return Eigen::Vector3f(600.0f + a * 100.0f, -600.0f + b * 100.0f, kWorldZOffset);
} }
void broadcastToAll(const std::string& message) { void Server::broadcastToAll(const std::string& message) {
std::lock_guard<std::mutex> lock(g_sessions_mutex); std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (const auto& session : g_sessions) { for (const auto& session : g_sessions) {
session->send_message(message); session->send_message(message);
} }
} }
void update_world(net::steady_timer& timer, net::io_context& ioc) { void Server::broadcastToAllExceptId(const std::string& message, int id)
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() == id) continue;
session->send_message(message);
}
}
void Server::createProjectile(int id, Eigen::Vector3f pos, Eigen::Quaternionf dir, float velocity)
{
const std::vector<Eigen::Vector3f> localOffsets = {
Eigen::Vector3f(-1.5f, 0.9f - 6.f, 5.0f),
Eigen::Vector3f(1.5f, 0.9f - 6.f, 5.0f)
};
uint64_t now_ms = std::chrono::duration_cast<std::chrono::milliseconds>((
std::chrono::system_clock::now().time_since_epoch())).count();
std::lock_guard<std::mutex> pl(g_projectiles_mutex);
for (int i = 0; i < localOffsets.size(); ++i) {
Projectile pr;
pr.shooterId = id;
pr.spawnMs = now_ms;
Eigen::Vector3f shotPos = pos + dir.toRotationMatrix() * localOffsets[i];
pr.pos = shotPos;
Eigen::Vector3f localForward(0.0f, 0.0f, -1.0f);
Eigen::Vector3f worldForward = dir.toRotationMatrix() * localForward;
float len = worldForward.norm();
if (len > 1e-6f) worldForward /= len;
pr.vel = worldForward * velocity;
pr.lifeMs = 15000.0f;
g_projectiles.push_back(pr);
std::cout << "Server: Created projectile from player " << id
<< " at pos (" << shotPos.x() << ", " << shotPos.y() << ", " << shotPos.z()
<< ") vel (" << pr.vel.x() << ", " << pr.vel.y() << ", " << pr.vel.z() << ")" << std::endl;
}
}
void Server::update_world() {
static auto last_snapshot_time = std::chrono::system_clock::now(); static auto last_snapshot_time = std::chrono::system_clock::now();
@ -595,26 +497,24 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) {
uint64_t now_ms = static_cast<uint64_t>( uint64_t now_ms = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count()); std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
// --- Snapshot every 500ms --- // --- Detect and force-disconnect timed-out players ---
/*if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_snapshot_time).count() >= 500) { {
last_snapshot_time = now; std::vector<std::shared_ptr<Session>> timedOut;
{
std::string snapshot_msg = "SNAPSHOT:" + std::to_string(now_ms);
std::lock_guard<std::mutex> lock(g_sessions_mutex); std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) { for (auto& session : g_sessions) {
ClientState st = session->get_latest_state(now); if (session->is_timed_out(now)) {
snapshot_msg += "|" + std::to_string(session->get_id()) + ":" + st.formPingMessageContent(); timedOut.push_back(session);
}
}
}
for (auto& session : timedOut) {
std::cout << "Server: Player " << session->get_id()
<< " timed out after " << PLAYER_TIMEOUT_MS << "ms, forcing disconnect\n";
session->force_disconnect();
} }
for (auto& session : g_sessions) {
session->send_message(snapshot_msg);
} }
}*/
// --- Tick: broadcast each player's latest state to all others (20Hz) ---
// Send the raw last-known state with its original timestamp, NOT an extrapolated one.
// Extrapolating here causes snap-back: if a player stops rotating, the server would
// keep sending over-rotated positions until the new state arrives, then B snaps back.
{ {
std::lock_guard<std::mutex> lock(g_sessions_mutex); std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& sender : g_sessions) { for (auto& sender : g_sessions) {
@ -834,13 +734,13 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) {
// --- Schedule next tick in 50ms --- // --- Schedule next tick in 50ms ---
timer.expires_after(std::chrono::milliseconds(50)); timer.expires_after(std::chrono::milliseconds(50));
timer.async_wait([&timer, &ioc](const boost::system::error_code& ec) { timer.async_wait([this](const boost::system::error_code& ec) {
if (ec) return; if (ec) return;
update_world(timer, ioc); update_world();
}); });
} }
std::vector<ServerBox> generateServerBoxes(int count) { std::vector<ServerBox> Server::generateServerBoxes(int count) {
std::vector<ServerBox> boxes; std::vector<ServerBox> boxes;
std::random_device rd; std::random_device rd;
std::mt19937 gen(rd()); std::mt19937 gen(rd());
@ -889,34 +789,42 @@ std::vector<ServerBox> generateServerBoxes(int count) {
return boxes; return boxes;
} }
int main() { Server::Server(tcp::acceptor& acceptor, net::io_context& ioc)
try { : acceptor_(acceptor)
, ioc_(ioc)
, timer(ioc_)
{
}
void Server::init()
{ {
std::lock_guard<std::mutex> lock(g_boxes_mutex); std::lock_guard<std::mutex> lock(g_boxes_mutex);
g_serverBoxes = generateServerBoxes(50); g_serverBoxes = generateServerBoxes(50);
//g_serverBoxes = generateServerBoxes(1);
std::cout << "Generated " << g_serverBoxes.size() << " boxes on server\n"; std::cout << "Generated " << g_serverBoxes.size() << " boxes on server\n";
} }
void Server::accept()
{
acceptor_.async_accept([&](beast::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<Session>(*this, std::move(socket), next_id++)->run();
}
accept();
});
}
int main() {
try {
net::io_context ioc; net::io_context ioc;
tcp::acceptor acceptor{ ioc, {tcp::v4(), 8081} }; tcp::acceptor acceptor{ ioc, {tcp::v4(), 8081} };
int next_id = 1000; Server server(acceptor, ioc);
server.accept();
std::cout << "Server started on port 8081...\n"; std::cout << "Server started on port 8081...\n";
auto do_accept = [&](auto& self_fn) -> void { server.update_world();
acceptor.async_accept([&, self_fn](beast::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<Session>(std::move(socket), next_id++)->run();
}
self_fn(self_fn);
});
};
net::steady_timer timer(ioc);
update_world(timer, ioc);
do_accept(do_accept);
ioc.run(); ioc.run();
} }
catch (std::exception const& e) { catch (std::exception const& e) {

140
server/server.h Normal file
View File

@ -0,0 +1,140 @@
#pragma once
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <string>
#include <memory>
#include <vector>
#include <mutex>
#include <queue>
#include <unordered_set>
#include <Eigen/Dense>
#include "../src/network/ClientState.h"
#define _USE_MATH_DEFINES
#include <math.h>
namespace beast = boost::beast;
namespace http = beast::http;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = net::ip::tcp;
static constexpr float kWorldZOffset = 45000.0f;
static const Eigen::Vector3f kWorldOffset(0.0f, 0.0f, kWorldZOffset);
static constexpr float kShipRadius = 15.0f;
static constexpr float kSpawnShipMargin = 25.0f;
static constexpr float kSpawnBoxMargin = 15.0f;
static constexpr float kSpawnZJitter = 60.0f;
std::vector<std::string> split(const std::string& s, char delimiter);
struct DeathInfo {
int targetId = -1;
uint64_t serverTime = 0;
Eigen::Vector3f position = Eigen::Vector3f::Zero();
int killerId = -1;
};
struct ServerBox {
Eigen::Vector3f position;
Eigen::Matrix3f rotation;
float collisionRadius = 2.0f;
bool destroyed = false;
};
struct Projectile {
int shooterId = -1;
uint64_t spawnMs = 0;
Eigen::Vector3f pos;
Eigen::Vector3f vel;
float lifeMs = PROJECTILE_LIFE;
};
struct BoxDestroyedInfo {
int boxIndex = -1;
uint64_t serverTime = 0;
Eigen::Vector3f position = Eigen::Vector3f::Zero();
int destroyedBy = -1;
};
class Server;
class Session : public std::enable_shared_from_this<Session> {
Server& server_;
websocket::stream<beast::tcp_stream> ws_;
beast::flat_buffer buffer_;
int id_;
bool is_writing_ = false;
std::queue<std::shared_ptr<std::string>> writeQueue_;
std::mutex writeMutex_;
public:
ClientStateInterval timedClientStates;
bool joined_ = false;
std::chrono::system_clock::time_point lastReceivedTime_;
bool hasReservedSpawn_ = false;
Eigen::Vector3f reservedSpawn_ = Eigen::Vector3f(0.0f, 0.0f, kWorldZOffset);
std::string nickname = "Player";
int shipType = 0;
Session(Server& server, tcp::socket&& socket, int id);
int get_id() const;
bool hasSpawnReserved() const;
const Eigen::Vector3f& reservedSpawn() const;
bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const;
void send_message(const std::string& msg);
void run();
bool IsMessageValid(const std::string& fullMessage);
bool is_timed_out(std::chrono::system_clock::time_point now) const;
void force_disconnect();
private:
void sendBoxesToClient();
public:
void init();
ClientState get_latest_state(std::chrono::system_clock::time_point now);
void doWrite();
private:
void do_read();
void process_message(const std::string& msg);
};
class Server
{
public:
tcp::acceptor& acceptor_;
net::io_context& ioc_;
net::steady_timer timer;
std::vector<BoxDestroyedInfo> g_boxDestructions;
std::mutex g_boxDestructions_mutex;
std::vector<ServerBox> g_serverBoxes;
std::mutex g_boxes_mutex;
std::vector<std::shared_ptr<Session>> g_sessions;
std::mutex g_sessions_mutex;
std::vector<Projectile> g_projectiles;
std::mutex g_projectiles_mutex;
std::unordered_set<int> g_dead_players;
std::mutex g_dead_mutex;
int next_id = 1000;
std::vector<ServerBox> generateServerBoxes(int count);
public:
Server(tcp::acceptor& acceptor, net::io_context& ioc);
void broadcastToAll(const std::string& message);
void broadcastToAllExceptId(const std::string& message, int id);
void createProjectile(int id, Eigen::Vector3f pos, Eigen::Quaternionf dir, float velocity);
void update_world();
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId);
void init();
void accept();
};

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"

View File

@ -1,4 +1,4 @@
#ifdef AUDIO #ifdef AUDIO
#include "AudioPlayerAsync.h" #include "AudioPlayerAsync.h"

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#ifdef AUDIO #ifdef AUDIO

View File

@ -1,4 +1,4 @@
#include "BoneAnimatedModel.h" #include "BoneAnimatedModel.h"
#include <regex> #include <regex>
#include <string> #include <string>
#include <fstream> #include <fstream>

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"
#include <unordered_map> #include <unordered_map>

View File

@ -1,4 +1,4 @@
#include "Environment.h" #include "Environment.h"
#include "utils/Utils.h" #include "utils/Utils.h"
@ -13,8 +13,8 @@ namespace ZL {
int Environment::windowHeaderHeight = 0; int Environment::windowHeaderHeight = 0;
int Environment::width = 0; int Environment::width = CONST_DEFAULT_WIDTH;
int Environment::height = 0; int Environment::height = CONST_DEFAULT_HEIGHT;
float Environment::zoom = DEFAULT_ZOOM; float Environment::zoom = DEFAULT_ZOOM;

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#ifdef __linux__ #ifdef __linux__
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#endif #endif
@ -14,6 +14,8 @@ namespace ZL {
class Environment { class Environment {
public: public:
static constexpr int CONST_DEFAULT_WIDTH = 1280;
static constexpr int CONST_DEFAULT_HEIGHT = 720;
static int windowHeaderHeight; static int windowHeaderHeight;
static int width; static int width;
static int height; static int height;

View File

@ -30,7 +30,7 @@
#include "network/LocalClient.h" #include "network/LocalClient.h"
#include "network/ClientState.h" #include "network/ClientState.h"
#include "GameConstants.h"
namespace ZL namespace ZL
{ {
@ -86,6 +86,8 @@ namespace ZL
//glContext = SDL_GL_CreateContext(ZL::Environment::window); //glContext = SDL_GL_CreateContext(ZL::Environment::window);
//glContext = in_glContext; //glContext = in_glContext;
Environment::width = Environment::CONST_DEFAULT_WIDTH;
Environment::height = Environment::CONST_DEFAULT_HEIGHT;
Environment::computeProjectionDimensions(); Environment::computeProjectionDimensions();
ZL::BindOpenGlFunctions(); ZL::BindOpenGlFunctions();
@ -146,10 +148,28 @@ namespace ZL
menuManager.setupMenu(); menuManager.setupMenu();
menuManager.onMainMenuEntered = [this]() {
if (networkClient) {
networkClient->Disconnect();
networkClient.reset();
}
};
menuManager.onSingleplayerPressed = [this](const std::string& nickname, int shipType) { menuManager.onSingleplayerPressed = [this](const std::string& nickname, int shipType) {
Environment::shipState.nickname = nickname; Environment::shipState.nickname = nickname;
Environment::shipState.shipType = shipType; Environment::shipState.shipType = shipType;
if (Environment::shipState.shipType == 1)
{
menuManager.uiManager.findButton("shootButton")->state = ButtonState::Disabled;
menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Disabled;
}
else
{
menuManager.uiManager.findButton("shootButton")->state = ButtonState::Normal;
menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Normal;
}
auto localClient = new LocalClient; auto localClient = new LocalClient;
ClientState st = Environment::shipState; ClientState st = Environment::shipState;
st.id = localClient->GetClientId(); st.id = localClient->GetClientId();
@ -158,15 +178,14 @@ namespace ZL
networkClient = std::unique_ptr<INetworkClient>(localClient); networkClient = std::unique_ptr<INetworkClient>(localClient);
networkClient->Connect("", 0); networkClient->Connect("", 0);
space.resetPlayerState();
lastTickCount = 0; lastTickCount = 0;
spaceGameStarted = 1;
}; };
menuManager.onMultiplayerPressed = [this](const std::string& nickname, int shipType) { menuManager.onMultiplayerPressed = [this](const std::string& nickname, int shipType) {
Environment::shipState.nickname = nickname; Environment::shipState.nickname = nickname;
Environment::shipState.shipType = shipType; Environment::shipState.shipType = shipType;
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
networkClient = std::make_unique<WebSocketClientEmscripten>(); networkClient = std::make_unique<WebSocketClientEmscripten>();
networkClient->Connect("localhost", 8081); networkClient->Connect("localhost", 8081);
@ -183,12 +202,12 @@ namespace ZL
space.boxCoordsArr.clear(); space.boxCoordsArr.clear();
space.boxRenderArr.clear(); space.boxRenderArr.clear();
//space.boxLabels.clear();
space.boxAlive.clear(); space.boxAlive.clear();
space.serverBoxesApplied = false; space.serverBoxesApplied = false;
space.resetPlayerState();
connectingStartTicks = SDL_GetTicks();
lastTickCount = 0; lastTickCount = 0;
spaceGameStarted = 1;
}; };
@ -200,21 +219,10 @@ namespace ZL
void Game::drawUI() void Game::drawUI()
{ {
static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
glClear(GL_DEPTH_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT);
renderer.shaderManager.PushShader(defaultShaderName); renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
glEnable(GL_BLEND); glEnable(GL_BLEND);
menuManager.uiManager.draw(renderer); menuManager.uiManager.draw(renderer);
glDisable(GL_BLEND); glDisable(GL_BLEND);
@ -230,13 +238,14 @@ namespace ZL
} }
void Game::drawScene() { void Game::drawScene() {
glViewport(0, 0, Environment::width, Environment::height);
if (!loadingCompleted) { if (!loadingCompleted) {
drawLoading(); drawLoading();
} }
else else
{ {
if (spaceGameStarted) { if (menuManager.shouldRenderSpace()) {
space.drawScene(); space.drawScene();
} }
else else
@ -250,17 +259,10 @@ namespace ZL
void Game::drawLoading() void Game::drawLoading()
{ {
static const std::string defaultShaderName = "default";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
glClear(GL_DEPTH_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT);
renderer.shaderManager.PushShader(defaultShaderName); renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
//float width = Environment::projectionWidth; //float width = Environment::projectionWidth;
//float height = Environment::projectionHeight; //float height = Environment::projectionHeight;
@ -278,9 +280,6 @@ namespace ZL
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
} }
@ -320,7 +319,7 @@ namespace ZL
//throw std::runtime_error("Synchronization is lost"); //throw std::runtime_error("Synchronization is lost");
} }
if (spaceGameStarted) { if (menuManager.shouldRenderSpace()) {
space.processTickCount(newTickCount, delta); space.processTickCount(newTickCount, delta);
} }
menuManager.uiManager.update(static_cast<float>(delta)); menuManager.uiManager.update(static_cast<float>(delta));
@ -335,8 +334,10 @@ namespace ZL
glClearColor(0.0f, 0.0f, 1.0f, 1.0f); glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//processTickCount();
drawScene(); drawScene();
processTickCount(); processTickCount();
//std::this_thread::sleep_for(std::chrono::milliseconds(50));
SDL_GL_SwapWindow(ZL::Environment::window); SDL_GL_SwapWindow(ZL::Environment::window);
} }
@ -370,33 +371,53 @@ namespace ZL
if (event.type == SDL_FINGERDOWN) { if (event.type == SDL_FINGERDOWN) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth); int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight); int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleDown(mx, my); handleDown(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
} }
else if (event.type == SDL_FINGERUP) { else if (event.type == SDL_FINGERUP) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth); int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight); int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleUp(mx, my); handleUp(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
} }
else if (event.type == SDL_FINGERMOTION) { else if (event.type == SDL_FINGERMOTION) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth); int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight); int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleMotion(mx, my); handleMotion(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
} }
#else #else
// Emscripten on mobile browser: handle real touch events with per-finger IDs.
// SDL_HINT_TOUCH_MOUSE_EVENTS="0" is set in main.cpp so these don't
// also fire SDL_MOUSEBUTTONDOWN, preventing double-processing.
#ifdef EMSCRIPTEN
if (event.type == SDL_FINGERDOWN) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleDown(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
}
else if (event.type == SDL_FINGERUP) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleUp(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
}
else if (event.type == SDL_FINGERMOTION) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleMotion(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
}
#endif
if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) { if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
// Преобразуем экранные пиксели в проекционные единицы // Преобразуем экранные пиксели в проекционные единицы
int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth); int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth);
int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight); int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight);
if (event.type == SDL_MOUSEBUTTONDOWN) handleDown(mx, my); if (event.type == SDL_MOUSEBUTTONDOWN) handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
else handleUp(mx, my); else handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
//std::cout << "Mouse button " << (event.type == SDL_MOUSEBUTTONDOWN ? "down" : "up") << ": x=" << mx << " y=" << my << std::endl;
} }
else if (event.type == SDL_MOUSEMOTION) { else if (event.type == SDL_MOUSEMOTION) {
int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth); int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth);
int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight); int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
handleMotion(mx, my); handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
} }
/*if (event.type == SDL_MOUSEBUTTONDOWN) { /*if (event.type == SDL_MOUSEBUTTONDOWN) {
@ -459,6 +480,22 @@ namespace ZL
} }
//#endif //#endif
networkClient->Poll(); networkClient->Poll();
if (menuManager.getState() == GameState::Connecting) {
if (networkClient->IsConnected()) {
menuManager.notifyConnected();
// Enable/disable shoot buttons based on ship type
if (Environment::shipState.shipType == 1) {
if (auto b = menuManager.uiManager.findButton("shootButton")) b->state = ButtonState::Disabled;
if (auto b = menuManager.uiManager.findButton("shootButton2")) b->state = ButtonState::Disabled;
} else {
if (auto b = menuManager.uiManager.findButton("shootButton")) b->state = ButtonState::Normal;
if (auto b = menuManager.uiManager.findButton("shootButton2")) b->state = ButtonState::Normal;
}
} else if (SDL_GetTicks() - connectingStartTicks > CONNECTING_TIMEOUT_MS) {
menuManager.notifyConnectionFailed();
}
}
#ifdef NETWORK #ifdef NETWORK
auto* wsBase = dynamic_cast<ZL::WebSocketClientBase*>(networkClient.get()); auto* wsBase = dynamic_cast<ZL::WebSocketClientBase*>(networkClient.get());
if (wsBase) { if (wsBase) {
@ -487,67 +524,58 @@ namespace ZL
} }
mainThreadHandler.processMainThreadTasks(); mainThreadHandler.processMainThreadTasks();
if (spaceGameStarted) { if (menuManager.shouldRenderSpace()) {
space.update(); space.update();
} }
} }
void Game::handleDown(int mx, int my) void Game::handleDown(int64_t fingerId, int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::projectionHeight - my; int uiY = Environment::projectionHeight - my;
menuManager.uiManager.onMouseDown(uiX, uiY); menuManager.uiManager.onTouchDown(fingerId, uiX, uiY);
bool uiHandled = false; if (!menuManager.uiManager.isUiInteractionForFinger(fingerId)) {
if (menuManager.shouldRenderSpace()) {
for (const auto& button : menuManager.uiManager.findButton("") ? std::vector<std::shared_ptr<UiButton>>{} : std::vector<std::shared_ptr<UiButton>>{}) {
(void)button;
}
auto pressedSlider = [&]() -> std::shared_ptr<UiSlider> {
for (const auto& slider : menuManager.uiManager.findSlider("") ? std::vector<std::shared_ptr<UiSlider>>{} : std::vector<std::shared_ptr<UiSlider>>{}) {
(void)slider;
}
return nullptr;
}();
if (!menuManager.uiManager.isUiInteraction()) {
if (spaceGameStarted) {
space.handleDown(mx, my); space.handleDown(mx, my);
} }
} }
} }
void Game::handleUp(int mx, int my) void Game::handleUp(int64_t fingerId, int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::projectionHeight - my; int uiY = Environment::projectionHeight - my;
menuManager.uiManager.onMouseUp(uiX, uiY); // Check BEFORE onTouchUp erases the finger from the map.
// If this finger started on a UI element, don't notify space —
// otherwise space would think the ship-control finger was released.
bool wasUiInteraction = menuManager.uiManager.isUiInteractionForFinger(fingerId);
menuManager.uiManager.onTouchUp(fingerId, uiX, uiY);
if (!menuManager.uiManager.isUiInteraction()) { if (!wasUiInteraction) {
if (spaceGameStarted) { if (menuManager.shouldRenderSpace()) {
space.handleUp(mx, my); space.handleUp(mx, my);
} }
} }
} }
void Game::handleMotion(int mx, int my) void Game::handleMotion(int64_t fingerId, int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::projectionHeight - my; int uiY = Environment::projectionHeight - my;
menuManager.uiManager.onMouseMove(uiX, uiY); // Check before onTouchMove so the "started on UI" state is preserved
// regardless of what onTouchMove does internally.
bool wasUiInteraction = menuManager.uiManager.isUiInteractionForFinger(fingerId);
menuManager.uiManager.onTouchMove(fingerId, uiX, uiY);
if (!menuManager.uiManager.isUiInteraction()) { if (!wasUiInteraction) {
if (spaceGameStarted) { if (menuManager.shouldRenderSpace()) {
space.handleMotion(mx, my); space.handleMotion(mx, my);
} }
} }
} }

View File

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

9
src/GameConstants.cpp Normal file
View File

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

11
src/GameConstants.h Normal file
View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#include "Projectile.h" #include "Projectile.h"
namespace ZL { namespace ZL {

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"
#include "render/TextureManager.h" #include "render/TextureManager.h"

View File

@ -1,4 +1,4 @@
#include "Space.h" #include "Space.h"
#include "AnimatedModel.h" #include "AnimatedModel.h"
#include "BoneAnimatedModel.h" #include "BoneAnimatedModel.h"
#include "planet/PlanetData.h" #include "planet/PlanetData.h"
@ -25,6 +25,8 @@
#include "network/LocalClient.h" #include "network/LocalClient.h"
#endif #endif
#include "GameConstants.h"
namespace ZL namespace ZL
{ {
@ -252,20 +254,26 @@ namespace ZL
Space::~Space() { Space::~Space() {
} }
void Space::setup() { void Space::resetPlayerState()
{
shipAlive = true;
menuManager.onRestartPressed = [this]() { gameOver = false;
this->shipAlive = true; showExplosion = false;
this->gameOver = false; explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
this->showExplosion = false;
this->explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
Environment::shipState.position = Vector3f{ 0, 0, 45000.f }; Environment::shipState.position = Vector3f{ 0, 0, 45000.f };
Environment::shipState.velocity = 0.0f; Environment::shipState.velocity = 0.0f;
Environment::shipState.rotation = Eigen::Matrix3f::Identity(); Environment::shipState.rotation = Eigen::Matrix3f::Identity();
Environment::inverseShipMatrix = Eigen::Matrix3f::Identity(); Environment::inverseShipMatrix = Eigen::Matrix3f::Identity();
Environment::zoom = DEFAULT_ZOOM; Environment::zoom = DEFAULT_ZOOM;
Environment::tapDownHold = false; Environment::tapDownHold = false;
playerScore = 0;
}
void Space::setup() {
menuManager.onRestartPressed = [this]() {
resetPlayerState();
if (networkClient) { if (networkClient) {
try { try {
@ -276,12 +284,25 @@ namespace ZL
std::cerr << "Client: Failed to send RESPAWN\n"; std::cerr << "Client: Failed to send RESPAWN\n";
} }
} }
this->playerScore = 0;
std::cerr << "Game restarted\n"; std::cerr << "Game restarted\n";
}; };
menuManager.onVelocityChanged = [this](float newVelocity) { menuManager.onVelocityChanged = [this](float newVelocity) {
newShipVelocity = newVelocity; newShipVelocity = newVelocity;
if (Environment::shipState.shipType == 0)
{
if (newVelocity > 2)
{
this->menuManager.uiManager.findButton("shootButton")->state = ButtonState::Disabled;
this->menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Disabled;
}
else
{
this->menuManager.uiManager.findButton("shootButton")->state = ButtonState::Normal;
this->menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Normal;
}
}
}; };
menuManager.onFirePressed = [this]() { menuManager.onFirePressed = [this]() {
@ -289,10 +310,16 @@ namespace ZL
}; };
bool cfgLoaded = sparkEmitter.loadFromJsonFile("resources/config/spark_config.json", renderer, CONST_ZIP_FILE); bool cfgLoaded = sparkEmitter.loadFromJsonFile("resources/config/spark_config.json", renderer, CONST_ZIP_FILE);
bool cfgLoaded2 = sparkEmitterCargo.loadFromJsonFile("resources/config/spark_config_cargo.json", renderer, CONST_ZIP_FILE);
sparkEmitter.setIsActive(false);
sparkEmitterCargo.setIsActive(false);
bool projCfgLoaded = projectileEmitter.loadFromJsonFile("resources/config/spark_projectile_config.json", renderer, CONST_ZIP_FILE); bool projCfgLoaded = projectileEmitter.loadFromJsonFile("resources/config/spark_projectile_config.json", renderer, CONST_ZIP_FILE);
bool explosionCfgLoaded = explosionEmitter.loadFromJsonFile("resources/config/explosion_config.json", renderer, CONST_ZIP_FILE); bool explosionCfgLoaded = explosionEmitter.loadFromJsonFile("resources/config/explosion_config.json", renderer, CONST_ZIP_FILE);
explosionEmitter.setEmissionPoints(std::vector<Vector3f>()); explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
projectileEmitter.setEmissionPoints(std::vector<Vector3f>()); //projectileEmitter.setEmissionPoints({ Vector3f{0,0,45000} });
//projectileEmitter.setUseWorldSpace(true);
cubemapTexture = std::make_shared<Texture>( cubemapTexture = std::make_shared<Texture>(
std::array<TextureDataStruct, 6>{ std::array<TextureDataStruct, 6>{
@ -315,7 +342,9 @@ namespace ZL
spaceshipBase = LoadFromTextFile02("resources/spaceshipnew001.txt", CONST_ZIP_FILE); spaceshipBase = LoadFromTextFile02("resources/spaceshipnew001.txt", CONST_ZIP_FILE);
spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix()); spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());
spaceshipBase.Move(Vector3f{ 1.2, 0, -5 }); spaceshipBase.Move(Vector3f{ 1.2, 0, -5 });
spaceshipBase.Scale(0.4f);
spaceship.AssignFrom(spaceshipBase); spaceship.AssignFrom(spaceshipBase);
spaceship.RefreshVBO(); spaceship.RefreshVBO();
@ -394,17 +423,13 @@ namespace ZL
void Space::drawCubemap(float skyPercent) void Space::drawCubemap(float skyPercent)
{ {
static const std::string defaultShaderName = "default"; static const std::string envSkyShaderName = "env_sky";
static const std::string envShaderName = "env_sky";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
static const std::string skyPercentUniformName = "skyPercent"; static const std::string skyPercentUniformName = "skyPercent";
renderer.shaderManager.PushShader(envShaderName); renderer.shaderManager.PushShader(envSkyShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1f(skyPercentUniformName, skyPercent); renderer.RenderUniform1f(skyPercentUniformName, skyPercent);
renderer.EnableVertexAttribArray(vPositionName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
@ -452,24 +477,14 @@ namespace ZL
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
} }
void Space::drawShip() void Space::drawShip()
{ {
static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName); renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
@ -490,69 +505,59 @@ namespace ZL
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
renderer.DrawVertexRenderStruct(spaceship); renderer.DrawVertexRenderStruct(spaceship);
} }
renderer.PushMatrix();
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(- /*0.5 * */ Environment::shipState.position);
std::cout << "Ship pos draw: " << Environment::shipState.position.transpose() << "\n";
if (Environment::shipState.shipType == 1) {
sparkEmitterCargo.draw(renderer, Environment::zoom, Environment::width, Environment::height);
} }
else {
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
}
renderer.PopMatrix();
}
renderer.PopMatrix(); renderer.PopMatrix();
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
renderer.shaderManager.PushShader("default"); renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendFunc(GL_SRC_ALPHA, GL_ONE);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position);
for (const auto& p : projectiles) { for (const auto& p : projectiles) {
if (p && p->isActive()) { if (p && p->isActive()) {
p->draw(renderer);
p->projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); p->projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
} }
} }
renderer.PopMatrix();
glDisable(GL_BLEND); glDisable(GL_BLEND);
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
//projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
if (shipAlive) {
renderer.PushMatrix();
renderer.TranslateMatrix({ 0, 0, 16 });
renderer.TranslateMatrix({ 0, -6.f, 0 });
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
renderer.PopMatrix();
}
if (showExplosion) { if (showExplosion) {
explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height, false);
} }
//glBindTexture(GL_TEXTURE_2D, basePlatformTexture->getTexID());
//renderer.DrawVertexRenderStruct(basePlatform);
glDisable(GL_BLEND); glDisable(GL_BLEND);
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
} }
void Space::drawBoxes() void Space::drawBoxes()
{ {
static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName); renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
@ -571,31 +576,32 @@ namespace ZL
renderer.RotateMatrix(boxCoordsArr[i].m); renderer.RotateMatrix(boxCoordsArr[i].m);
glBindTexture(GL_TEXTURE_2D, boxTexture->getTexID()); glBindTexture(GL_TEXTURE_2D, boxTexture->getTexID());
//glBindTexture(GL_TEXTURE_2D, rockTexture->getTexID());
renderer.DrawVertexRenderStruct(boxRenderArr[i]); renderer.DrawVertexRenderStruct(boxRenderArr[i]);
renderer.PopMatrix(); renderer.PopMatrix();
} }
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
} }
void Space::drawScene() { void Space::drawScene() {
static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
glClearColor(0.0f, 1.0f, 0.0f, 1.0f); glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glViewport(0, 0, Environment::width, Environment::height); glViewport(0, 0, Environment::width, Environment::height);
// Готовим данные всех эмиттеров (CPU + VBO upload) до начала отрисовки,
// чтобы draw() делал только GPU-вызовы без пауз между кораблём и частицами.
sparkEmitter.prepareForDraw(true);
sparkEmitterCargo.prepareForDraw(true);
explosionEmitter.prepareForDraw(false);
for (const auto& p : projectiles) {
if (p && p->isActive()) {
p->projectileEmitter.prepareForDraw(true);
}
}
CheckGlError(); CheckGlError();
float skyPercent = 0.0; float skyPercent = 0.0;
@ -633,17 +639,9 @@ namespace ZL
} }
void Space::drawRemoteShips() { void Space::drawRemoteShips() {
static const std::string defaultShaderName = "default";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName); renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
@ -684,7 +682,6 @@ namespace ZL
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
//renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset
renderer.RotateMatrix(Environment::inverseShipMatrix); renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position); renderer.TranslateMatrix(-Environment::shipState.position);
@ -706,8 +703,6 @@ namespace ZL
} }
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
@ -961,17 +956,11 @@ namespace ZL
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.EnableVertexAttribArray("vPosition");
renderer.EnableVertexAttribArray("vColor");
Eigen::Vector4f uColor(crosshairCfg.color.x(), crosshairCfg.color.y(), crosshairCfg.color.z(), crosshairCfg.alpha); Eigen::Vector4f uColor(crosshairCfg.color.x(), crosshairCfg.color.y(), crosshairCfg.color.z(), crosshairCfg.alpha);
renderer.RenderUniform4fv("uColor", uColor.data()); renderer.RenderUniform4fv("uColor", uColor.data());
renderer.DrawVertexRenderStruct(crosshairMesh); renderer.DrawVertexRenderStruct(crosshairMesh);
renderer.DisableVertexAttribArray("vPosition");
renderer.DisableVertexAttribArray("vColor");
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
@ -1300,7 +1289,6 @@ namespace ZL
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.EnableVertexAttribArray("vPosition");
renderer.RenderUniform4fv("uColor", enemyColor.data()); renderer.RenderUniform4fv("uColor", enemyColor.data());
// рамки // рамки
@ -1323,7 +1311,6 @@ namespace ZL
drawLeadRing2D(lx, ly); drawLeadRing2D(lx, ly);
} }
renderer.DisableVertexAttribArray("vPosition");
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
@ -1409,7 +1396,6 @@ namespace ZL
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.EnableVertexAttribArray("vPosition");
renderer.RenderUniform4fv("uColor", enemyColor.data()); renderer.RenderUniform4fv("uColor", enemyColor.data());
// стрелка // стрелка
@ -1427,7 +1413,6 @@ namespace ZL
drawLeadRing2D(lx, ly); drawLeadRing2D(lx, ly);
} }
renderer.DisableVertexAttribArray("vPosition");
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
@ -1450,9 +1435,6 @@ namespace ZL
auto now_ms = newTickCount; auto now_ms = newTickCount;
sparkEmitter.update(static_cast<float>(delta));
planetObject.update(static_cast<float>(delta));
if (firePressed) if (firePressed)
{ {
firePressed = false; firePressed = false;
@ -1563,6 +1545,41 @@ namespace ZL
} }
//--------------
SparkEmitter* sparkEmitterPtr;
if (Environment::shipState.shipType == 1) {
sparkEmitterPtr = &sparkEmitterCargo;
static std::vector<Vector3f> emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) };
emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 2.8, -6.5 + 16.0 };
emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 1.5, -6.5 + 16.0 };
sparkEmitterPtr->setEmissionPoints(emissionPoints);
}
else
{
sparkEmitterPtr = &sparkEmitter;
static std::vector<Vector3f> emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) };
emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ -0.9, 1.4 - 1.0, -8.5 + 16.0 };
emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.9, 1.4 - 1.0, -8.5 + 16.0 };
sparkEmitterPtr->setEmissionPoints(emissionPoints);
//sparkEmitterPtr->setEmissionPoints({ /*0.5* */Environment::shipState.position });
//sparkEmitterPtr->setEmissionPoints({ Vector3f(0, 0, 0) });
std::cout << "Ship pos empo: " << Environment::shipState.position.transpose() << "\n";
}
if (Environment::shipState.velocity > 0.1f)
{
sparkEmitterPtr->setIsActive(true);
}
else
{
sparkEmitterPtr->setIsActive(false);
}
sparkEmitterPtr->update(static_cast<float>(delta));
auto latestRemotePlayers = networkClient->getRemotePlayers(); auto latestRemotePlayers = networkClient->getRemotePlayers();
std::chrono::system_clock::time_point nowRoundedWithDelay{ std::chrono::milliseconds(newTickCount - CLIENT_DELAY) }; std::chrono::system_clock::time_point nowRoundedWithDelay{ std::chrono::milliseconds(newTickCount - CLIENT_DELAY) };
@ -1594,33 +1611,11 @@ namespace ZL
for (const auto& p : projectiles) { for (const auto& p : projectiles) {
if (p && p->isActive()) { if (p && p->isActive()) {
Vector3f worldPos = p->getPosition(); Vector3f worldPos = p->getPosition();
Vector3f rel = worldPos - Environment::shipState.position; p->projectileEmitter.resetEmissionPoints({ worldPos });
Vector3f camPos = Environment::inverseShipMatrix * rel;
p->projectileEmitter.setEmissionPoints({ camPos });
p->projectileEmitter.emit();
p->projectileEmitter.update(static_cast<float>(delta)); p->projectileEmitter.update(static_cast<float>(delta));
} }
} }
/*
if (!projCameraPoints.empty()) {
projectileEmitter.setEmissionPoints(projCameraPoints);
projectileEmitter.emit();
}
else {
projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
}*/
std::vector<Vector3f> shipCameraPoints;
for (const auto& lp : shipLocalEmissionPoints) {
Vector3f adjusted = lp + Vector3f{ 0.0f, -Environment::zoom * 0.03f, 0.0f };
shipCameraPoints.push_back(adjusted);
}
if (!shipCameraPoints.empty()) {
sparkEmitter.setEmissionPoints(shipCameraPoints);
}
sparkEmitter.update(static_cast<float>(delta));
//projectileEmitter.update(static_cast<float>(delta));
explosionEmitter.update(static_cast<float>(delta)); explosionEmitter.update(static_cast<float>(delta));
if (showExplosion) { if (showExplosion) {
@ -1731,6 +1726,8 @@ namespace ZL
} }
} }
planetObject.update(static_cast<float>(delta));
// update velocity text // update velocity text
if (shipAlive && !gameOver) { if (shipAlive && !gameOver) {
@ -1773,6 +1770,18 @@ namespace ZL
void Space::update() { void Space::update() {
if (networkClient) { if (networkClient) {
if (networkClient->IsConnected()) {
wasConnectedToServer = true;
}
else if (wasConnectedToServer && shipAlive && !gameOver) {
wasConnectedToServer = false;
shipAlive = false;
gameOver = true;
Environment::shipState.velocity = 0.0f;
std::cout << "Client: Lost connection to server\n";
menuManager.showConnectionLost();
}
auto pending = networkClient->getPendingProjectiles(); auto pending = networkClient->getPendingProjectiles();
if (!pending.empty()) { if (!pending.empty()) {
const float projectileSpeed = PROJECTILE_VELOCITY; const float projectileSpeed = PROJECTILE_VELOCITY;
@ -1862,6 +1871,17 @@ namespace ZL
} }
} }
auto disconnects = networkClient->getPendingDisconnects();
for (int pid : disconnects) {
remotePlayerStates.erase(pid);
deadRemotePlayers.erase(pid);
if (trackedTargetId == pid) {
trackedTargetId = -1;
targetAcquireAnim = 0.f;
}
std::cout << "Client: Remote player " << pid << " left the game, removed from scene\n";
}
auto boxDestructions = networkClient->getPendingBoxDestructions(); auto boxDestructions = networkClient->getPendingBoxDestructions();
if (!boxDestructions.empty()) { if (!boxDestructions.empty()) {
std::cout << "Game: Received " << boxDestructions.size() << " box destruction events" << std::endl; std::cout << "Game: Received " << boxDestructions.size() << " box destruction events" << std::endl;

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"
#include "Environment.h" #include "Environment.h"
@ -88,6 +88,7 @@ namespace ZL {
VertexDataStruct boxBase; VertexDataStruct boxBase;
SparkEmitter sparkEmitter; SparkEmitter sparkEmitter;
SparkEmitter sparkEmitterCargo;
SparkEmitter projectileEmitter; SparkEmitter projectileEmitter;
SparkEmitter explosionEmitter; SparkEmitter explosionEmitter;
PlanetObject planetObject; PlanetObject planetObject;
@ -97,7 +98,7 @@ namespace ZL {
float projectileCooldownMs = 500.0f; float projectileCooldownMs = 500.0f;
int64_t lastProjectileFireTime = 0; int64_t lastProjectileFireTime = 0;
int maxProjectiles = 500; int maxProjectiles = 500;
std::vector<Vector3f> shipLocalEmissionPoints; //std::vector<Vector3f> shipLocalEmissionPoints;
bool shipAlive = true; bool shipAlive = true;
@ -123,6 +124,7 @@ namespace ZL {
std::unordered_set<int> deadRemotePlayers; std::unordered_set<int> deadRemotePlayers;
int playerScore = 0; int playerScore = 0;
bool wasConnectedToServer = false;
static constexpr float TARGET_MAX_DIST = 50000.0f; static constexpr float TARGET_MAX_DIST = 50000.0f;
static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST; static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST;
@ -139,6 +141,7 @@ namespace ZL {
void drawTargetHud(); // рисует рамку или стрелку void drawTargetHud(); // рисует рамку или стрелку
int pickTargetId() const; // ???????? ???? (????: ????????? ????? ????????? ?????) int pickTargetId() const; // ???????? ???? (????: ????????? ????? ????????? ?????)
void resetPlayerState();
void clearTextRendererCache(); void clearTextRendererCache();
// Crosshair HUD // Crosshair HUD

View File

@ -8,6 +8,7 @@
#include "Environment.h" #include "Environment.h"
#include <stdexcept> #include <stdexcept>
#include "utils/Utils.h" #include "utils/Utils.h"
#include "GameConstants.h"
namespace ZL { namespace ZL {
@ -74,7 +75,7 @@ namespace ZL {
texture = tex; texture = tex;
} }
void SparkEmitter::prepareDrawData() { void SparkEmitter::prepareDrawData(bool withRotation) {
if (!drawDataDirty) return; if (!drawDataDirty) return;
drawPositions.clear(); drawPositions.clear();
@ -91,7 +92,7 @@ namespace ZL {
for (const auto& particle : particles) { for (const auto& particle : particles) {
if (particle.active) { if (particle.active) {
Vector3f posCam; Vector3f posCam;
if (useWorldSpace) { if (withRotation) {
Vector3f rel = particle.position - Environment::shipState.position; Vector3f rel = particle.position - Environment::shipState.position;
posCam = Environment::inverseShipMatrix * rel; posCam = Environment::inverseShipMatrix * rel;
} }
@ -120,6 +121,29 @@ namespace ZL {
float size = particleSize * particle.scale; float size = particleSize * particle.scale;
if (withRotation)
{
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, -size, 0 } + posCam);
drawTexCoords.push_back({ 0.0f, 0.0f });
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, size,0 } + posCam);
drawTexCoords.push_back({ 0.0f, 1.0f });
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size,size, 0 } + posCam);
drawTexCoords.push_back({ 1.0f, 1.0f });
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, -size, 0 } + posCam);
drawTexCoords.push_back({ 0.0f, 0.0f });
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size, size,0 } + posCam);
drawTexCoords.push_back({ 1.0f, 1.0f });
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size, -size, 0 } + posCam);
drawTexCoords.push_back({ 1.0f, 0.0f });
}
else
{
drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) }); drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) });
drawTexCoords.push_back({ 0.0f, 0.0f }); drawTexCoords.push_back({ 0.0f, 0.0f });
@ -137,12 +161,31 @@ namespace ZL {
drawPositions.push_back({ posCam(0) + size, posCam(1) - size, posCam(2) }); drawPositions.push_back({ posCam(0) + size, posCam(1) - size, posCam(2) });
drawTexCoords.push_back({ 1.0f, 0.0f }); drawTexCoords.push_back({ 1.0f, 0.0f });
}
} }
drawDataDirty = false; drawDataDirty = false;
} }
void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight) { 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);
}
void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight, bool withRotation) {
if (!configured) { if (!configured) {
throw std::runtime_error("Failed to load spark emitter config file 1!"); throw std::runtime_error("Failed to load spark emitter config file 1!");
} }
@ -155,46 +198,30 @@ namespace ZL {
throw std::runtime_error("Failed to load spark emitter config file 2!"); throw std::runtime_error("Failed to load spark emitter config file 2!");
} }
prepareDrawData(); //prepareDrawData(withRotation);
if (drawPositions.empty()) { if (drawPositions.empty()) {
return; return;
} }
/*
sparkQuad.data.PositionData = drawPositions; sparkQuad.data.PositionData = drawPositions;
sparkQuad.data.TexCoordData = drawTexCoords; sparkQuad.data.TexCoordData = drawTexCoords;
sparkQuad.RefreshVBO(); sparkQuad.RefreshVBO();
*/
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(shaderProgramName); renderer.shaderManager.PushShader(shaderProgramName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName); renderer.SetMatrix();
renderer.EnableVertexAttribArray(vTexCoordName);
//float aspectRatio = static_cast<float>(screenWidth) / static_cast<float>(screenHeight);
//renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, aspectRatio, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
glBindTexture(GL_TEXTURE_2D, texture->getTexID()); glBindTexture(GL_TEXTURE_2D, texture->getTexID());
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendFunc(GL_SRC_ALPHA, GL_ONE);
renderer.PushMatrix(); //renderer.PushMatrix();
//renderer.LoadIdentity();
//renderer.TranslateMatrix({ 0, 0, -1.0f * zoom });
renderer.DrawVertexRenderStruct(sparkQuad); renderer.DrawVertexRenderStruct(sparkQuad);
//renderer.PopMatrix();
renderer.PopMatrix();
//renderer.PopProjectionMatrix();
glDisable(GL_BLEND); glDisable(GL_BLEND);
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
} }
@ -207,9 +234,19 @@ namespace ZL {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>( auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
currentTime - lastEmissionTime).count(); currentTime - lastEmissionTime).count();
if (isActive && elapsed >= emissionRate) { if (isActive && elapsed >= static_cast<long long>(emissionRate)) {
emit(); int emitCount = static_cast<int>(elapsed / emissionRate);
lastEmissionTime = currentTime; float elapsedF = static_cast<float>(elapsed);
for (int e = 0; e < emitCount; ++e) {
// e=0 — самая старая эмиссия, e=emitCount-1 — самая свежая
float ageMs = static_cast<float>(emitCount - 1 - e) * emissionRate;
// lerpT=0 → текущая позиция (новейшая), lerpT=1 → предыдущая (самая старая)
float lerpT = (elapsedF > 0.0f) ? min(ageMs / elapsedF, 1.0f) : 0.0f;
emit(ageMs, lerpT);
}
lastEmissionTime += std::chrono::milliseconds(
static_cast<long long>(emitCount * emissionRate));
drawDataDirty = true; drawDataDirty = true;
} }
@ -248,7 +285,7 @@ namespace ZL {
} }
} }
void SparkEmitter::emit() { void SparkEmitter::emit(float ageMs, float lerpT) {
if (!configured) { if (!configured) {
throw std::runtime_error("Failed to load spark emitter config file 4!"); throw std::runtime_error("Failed to load spark emitter config file 4!");
} }
@ -256,7 +293,30 @@ namespace ZL {
if (emissionPoints.empty()) return; if (emissionPoints.empty()) return;
bool emitted = false; bool emitted = false;
for (int i = 0; i < emissionPoints.size(); ++i) { auto applyAge = [](SparkParticle& particle, float age) {
if (age <= 0.0f) return;
particle.position(0) += particle.velocity(0) * age / 1000.0f;
particle.position(1) += particle.velocity(1) * age / 1000.0f;
particle.position(2) += particle.velocity(2) * age / 1000.0f;
particle.lifeTime = age;
if (particle.lifeTime >= particle.maxLifeTime) {
particle.active = false;
} else {
float lifeRatio = particle.lifeTime / particle.maxLifeTime;
particle.scale = 1.0f - lifeRatio * 0.8f;
}
};
// Вычисляем стартовую позицию с интерполяцией между prev и current
// lerpT=0 → текущая позиция (новейшая эмиссия), lerpT=1 → предыдущая (самая старая)
bool canInterp = (prevEmissionPoints.size() == emissionPoints.size());
for (int i = 0; i < (int)emissionPoints.size(); ++i) {
Vector3f birthPos = emissionPoints[i];
if (canInterp && lerpT > 0.0f) {
birthPos = emissionPoints[i] * (1.0f - lerpT) + prevEmissionPoints[i] * lerpT;
}
bool particleFound = false; bool particleFound = false;
for (auto& particle : particles) { for (auto& particle : particles) {
@ -264,8 +324,9 @@ namespace ZL {
initParticle(particle, i); initParticle(particle, i);
particle.active = true; particle.active = true;
particle.lifeTime = 0; particle.lifeTime = 0;
particle.position = emissionPoints[i]; particle.position = birthPos;
particle.emitterIndex = i; particle.emitterIndex = i;
applyAge(particle, ageMs);
particleFound = true; particleFound = true;
emitted = true; emitted = true;
break; break;
@ -274,11 +335,11 @@ namespace ZL {
if (!particleFound && !particles.empty()) { if (!particleFound && !particles.empty()) {
size_t oldestIndex = 0; size_t oldestIndex = 0;
float maxLifeTime = 0; float maxLifeRatio = 0;
for (size_t j = 0; j < particles.size(); ++j) { for (size_t j = 0; j < particles.size(); ++j) {
if (particles[j].lifeTime > maxLifeTime) { if (particles[j].lifeTime > maxLifeRatio) {
maxLifeTime = particles[j].lifeTime; maxLifeRatio = particles[j].lifeTime;
oldestIndex = j; oldestIndex = j;
} }
} }
@ -286,8 +347,9 @@ namespace ZL {
initParticle(particles[oldestIndex], i); initParticle(particles[oldestIndex], i);
particles[oldestIndex].active = true; particles[oldestIndex].active = true;
particles[oldestIndex].lifeTime = 0; particles[oldestIndex].lifeTime = 0;
particles[oldestIndex].position = emissionPoints[i]; particles[oldestIndex].position = birthPos;
particles[oldestIndex].emitterIndex = i; particles[oldestIndex].emitterIndex = i;
applyAge(particles[oldestIndex], ageMs);
emitted = true; emitted = true;
} }
} }
@ -298,6 +360,14 @@ namespace ZL {
} }
void SparkEmitter::setEmissionPoints(const std::vector<Vector3f>& positions) { void SparkEmitter::setEmissionPoints(const std::vector<Vector3f>& positions) {
prevEmissionPoints = emissionPoints;
emissionPoints = positions;
drawDataDirty = true;
}
void SparkEmitter::resetEmissionPoints(const std::vector<Vector3f>& positions)
{
prevEmissionPoints.clear();
emissionPoints = positions; emissionPoints = positions;
drawDataDirty = true; drawDataDirty = true;
} }
@ -465,8 +535,9 @@ namespace ZL {
std::cout << "Total emission points: " << emissionPoints.size() << std::endl; std::cout << "Total emission points: " << emissionPoints.size() << std::endl;
} }
else { else {
std::cerr << "Emission points parsed but empty" << std::endl; setEmissionPoints({});
throw std::runtime_error("Failed to load spark emitter config file 10!"); std::cout << "Emission points parsed but empty" << std::endl;
//throw std::runtime_error("Failed to load spark emitter config file 10!");
} }
} }
else { else {
@ -553,25 +624,6 @@ namespace ZL {
throw std::runtime_error("Failed to load spark emitter config file 18!"); throw std::runtime_error("Failed to load spark emitter config file 18!");
} }
std::cout << "Working with shaders 2" << std::endl;
/*
if (j.contains("vertexShader") && j.contains("fragmentShader")
&& j["vertexShader"].is_string() && j["fragmentShader"].is_string()) {
std::string v = j["vertexShader"].get<std::string>();
std::string f = j["fragmentShader"].get<std::string>();
std::cout << "Loading shaders - vertex: " << v << ", fragment: " << f << std::endl;
try {
renderer.shaderManager.AddShaderFromFiles(shaderProgramName, v, f, zipFile.c_str());
std::cout << "Shaders loaded successfully" << std::endl;
}
catch (const std::exception& e) {
std::cerr << "Shader load error: " << e.what() << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 19!");
}
}*/
drawDataDirty = true; drawDataDirty = true;
configured = true; configured = true;
std::cout << "SparkEmitter configuration loaded successfully!" << std::endl; std::cout << "SparkEmitter configuration loaded successfully!" << std::endl;

View File

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

View File

@ -1,4 +1,4 @@
#include "TextModel.h" #include "TextModel.h"
#include <regex> #include <regex>
#include <string> #include <string>
#include <fstream> #include <fstream>

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"
#include <unordered_map> #include <unordered_map>

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ constexpr auto NET_SECRET = "880b3713b9ff3e7a94b2712d54679e1f";
#define ENABLE_NETWORK_CHECKSUM #define ENABLE_NETWORK_CHECKSUM
constexpr float ANGULAR_ACCEL = 0.005f * 1000.0f; constexpr float ANGULAR_ACCEL = 0.005f * 1000.0f;
constexpr float SHIP_ACCEL = 1.0f * 1000.0f; constexpr float SHIP_ACCEL = 1.0f * 200.0f;
constexpr float ROTATION_SENSITIVITY = 0.002f; constexpr float ROTATION_SENSITIVITY = 0.002f;
constexpr float PLANET_RADIUS = 20000.f; constexpr float PLANET_RADIUS = 20000.f;
@ -26,6 +26,7 @@ constexpr float PITCH_LIMIT = static_cast<float>(M_PI) / 9.f;//18.0f;
constexpr long long SERVER_DELAY = 0; //ms constexpr long long SERVER_DELAY = 0; //ms
constexpr long long CLIENT_DELAY = 500; //ms constexpr long long CLIENT_DELAY = 500; //ms
constexpr long long CUTOFF_TIME = 5000; //ms constexpr long long CUTOFF_TIME = 5000; //ms
constexpr long long PLAYER_TIMEOUT_MS = 10000; //ms — disconnect if no UPD received
constexpr float PROJECTILE_VELOCITY = 600.f; constexpr float PROJECTILE_VELOCITY = 600.f;
constexpr float PROJECTILE_LIFE = 15000.f; //ms constexpr float PROJECTILE_LIFE = 15000.f; //ms

View File

@ -34,6 +34,7 @@ namespace ZL {
public: public:
virtual ~INetworkClient() = default; virtual ~INetworkClient() = default;
virtual void Connect(const std::string& host, uint16_t port) = 0; virtual void Connect(const std::string& host, uint16_t port) = 0;
virtual void Disconnect() {}
virtual void Send(const std::string& message) = 0; virtual void Send(const std::string& message) = 0;
virtual bool IsConnected() const = 0; virtual bool IsConnected() const = 0;
virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов
@ -50,6 +51,7 @@ namespace ZL {
virtual int GetClientId() const { return -1; } virtual int GetClientId() const { return -1; }
virtual std::vector<BoxDestroyedInfo> getPendingBoxDestructions() = 0; virtual std::vector<BoxDestroyedInfo> getPendingBoxDestructions() = 0;
virtual int64_t getTimeOffset() const { return 0; } virtual int64_t getTimeOffset() const { return 0; }
virtual std::vector<int> getPendingDisconnects() { return {}; }
}; };
} }

View File

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

View File

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

View File

@ -103,6 +103,19 @@ namespace ZL {
return; return;
} }
if (msg.rfind("PLAYER_LEFT:", 0) == 0) {
if (parts.size() >= 2) {
try {
int pid = std::stoi(parts[1]);
remotePlayers.erase(pid);
pendingDisconnects_.push_back(pid);
std::cout << "Client: Player " << pid << " disconnected (PLAYER_LEFT)\n";
}
catch (...) {}
}
return;
}
if (msg.rfind("RESPAWN_ACK:", 0) == 0) { if (msg.rfind("RESPAWN_ACK:", 0) == 0) {
//auto parts = split(msg, ':'); //auto parts = split(msg, ':');
if (parts.size() >= 2) { if (parts.size() >= 2) {
@ -356,6 +369,12 @@ namespace ZL {
return copy; return copy;
} }
std::vector<int> WebSocketClientBase::getPendingDisconnects() {
std::vector<int> copy;
copy.swap(pendingDisconnects_);
return copy;
}
std::vector<ClientState> WebSocketClientBase::getPendingSpawns() { std::vector<ClientState> WebSocketClientBase::getPendingSpawns() {
std::vector<ClientState> copy; std::vector<ClientState> copy;
copy.swap(pendingSpawns_); copy.swap(pendingSpawns_);

View File

@ -20,6 +20,7 @@ namespace ZL {
std::vector<DeathInfo> pendingDeaths_; std::vector<DeathInfo> pendingDeaths_;
std::vector<int> pendingRespawns_; std::vector<int> pendingRespawns_;
std::vector<BoxDestroyedInfo> pendingBoxDestructions_; std::vector<BoxDestroyedInfo> pendingBoxDestructions_;
std::vector<int> pendingDisconnects_;
int clientId = -1; int clientId = -1;
int64_t timeOffset = 0; int64_t timeOffset = 0;
std::vector<ClientState> pendingSpawns_; std::vector<ClientState> pendingSpawns_;
@ -41,7 +42,7 @@ namespace ZL {
return serverBoxes_; return serverBoxes_;
} }
std::vector<bool> getServerBoxDestroyedFlags() { std::vector<bool> getServerBoxDestroyedFlags() override {
return serverBoxesDestroyed_; return serverBoxesDestroyed_;
} }
@ -49,6 +50,7 @@ namespace ZL {
std::vector<DeathInfo> getPendingDeaths() override; std::vector<DeathInfo> getPendingDeaths() override;
std::vector<int> getPendingRespawns() override; std::vector<int> getPendingRespawns() override;
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override; std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
std::vector<int> getPendingDisconnects() override;
std::vector<ClientState> getPendingSpawns(); std::vector<ClientState> getPendingSpawns();
int getClientId() const { return clientId; } int getClientId() const { return clientId; }
}; };

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#include "FrameBuffer.h" #include "FrameBuffer.h"
#include <iostream> #include <iostream>
#include "Environment.h" #include "Environment.h"
@ -15,10 +15,10 @@ namespace ZL {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
// Настраиваем фильтрацию // Настраиваем фильтрацию
if (useMipmaps) { if (useMipmaps) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// Сразу генерируем пустые уровни, чтобы текстура считалась "полной" // Сразу генерируем пустые уровни, чтобы текстура считалась "полной"
glGenerateMipmap(GL_TEXTURE_2D); glGenerateMipmap(GL_TEXTURE_2D);
} }
else { else {
@ -49,13 +49,13 @@ namespace ZL {
void FrameBuffer::Bind() { void FrameBuffer::Bind() {
glBindFramebuffer(GL_FRAMEBUFFER, fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glViewport(0, 0, width, height); // Важно: устанавливаем вьюпорт под размер текстуры glViewport(0, 0, width, height); // Важно: устанавливаем вьюпорт под размер текстуры
} }
void FrameBuffer::Unbind() { void FrameBuffer::Unbind() {
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Здесь желательно возвращать вьюпорт к размерам экрана, // Здесь желательно возвращать вьюпорт к размерам экрана,
// например, через Environment::width/height // например, через Environment::width/height
glViewport(0, 0, Environment::width, Environment::height); glViewport(0, 0, Environment::width, Environment::height);
} }

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/OpenGlExtensions.h" #include "render/OpenGlExtensions.h"
#include <memory> #include <memory>

View File

@ -1,4 +1,4 @@
#include "OpenGlExtensions.h" #include "OpenGlExtensions.h"
#include "utils/Utils.h" #include "utils/Utils.h"
#include <iostream> #include <iostream>

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#include "SDL.h" #include "SDL.h"

View File

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

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/OpenGlExtensions.h" #include "render/OpenGlExtensions.h"
@ -127,13 +127,6 @@ namespace ZL {
void SetMatrix(); void SetMatrix();
void EnableVertexAttribArray(const std::string& attribName);
void DisableVertexAttribArray(const std::string& attribName);
void RenderUniformMatrix3fv(const std::string& uniformName, bool transpose, const float* value); void RenderUniformMatrix3fv(const std::string& uniformName, bool transpose, const float* value);
void RenderUniformMatrix4fv(const std::string& uniformName, bool transpose, const float* value); void RenderUniformMatrix4fv(const std::string& uniformName, bool transpose, const float* value);
void RenderUniform1i(const std::string& uniformName, const int value); void RenderUniform1i(const std::string& uniformName, const int value);
@ -145,6 +138,8 @@ namespace ZL {
void VertexAttribPointer3fv(const std::string& attribName, int stride, const char* pointer); void VertexAttribPointer3fv(const std::string& attribName, int stride, const char* pointer);
void DisableVertexAttribArray(const std::string& attribName);
void DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct); void DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct);
}; };

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#include "Perlin.h" #include "Perlin.h"
#include <cmath> #include <cmath>
#include <numeric> #include <numeric>
#include <random> #include <random>
@ -9,19 +9,19 @@ namespace ZL {
PerlinNoise::PerlinNoise() { PerlinNoise::PerlinNoise() {
p.resize(256); p.resize(256);
std::iota(p.begin(), p.end(), 0); std::iota(p.begin(), p.end(), 0);
// Перемешиваем для случайности (можно задать seed) // Перемешиваем для случайности (можно задать seed)
std::default_random_engine engine(77777); std::default_random_engine engine(77777);
std::shuffle(p.begin(), p.end(), engine); std::shuffle(p.begin(), p.end(), engine);
p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения
} }
PerlinNoise::PerlinNoise(uint64_t seed) { PerlinNoise::PerlinNoise(uint64_t seed) {
p.resize(256); p.resize(256);
std::iota(p.begin(), p.end(), 0); std::iota(p.begin(), p.end(), 0);
// Перемешиваем для случайности (используем переданный seed) // Перемешиваем для случайности (используем переданный seed)
std::default_random_engine engine(static_cast<unsigned int>(seed)); std::default_random_engine engine(static_cast<unsigned int>(seed));
std::shuffle(p.begin(), p.end(), engine); std::shuffle(p.begin(), p.end(), engine);
p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения
} }
float PerlinNoise::fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } float PerlinNoise::fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
@ -58,13 +58,13 @@ namespace ZL {
} }
float PerlinNoise::getSurfaceHeight(Eigen::Vector3f pos, float noiseCoeff) { float PerlinNoise::getSurfaceHeight(Eigen::Vector3f pos, float noiseCoeff) {
// Частота шума (чем больше, тем больше "холмов") // Частота шума (чем больше, тем больше "холмов")
float frequency = 7.0f; float frequency = 7.0f;
// Получаем значение шума (обычно от -1 до 1) // Получаем значение шума (обычно от -1 до 1)
float noiseValue = noise(pos(0) * frequency, pos(1) * frequency, pos(2) * frequency); float noiseValue = noise(pos(0) * frequency, pos(1) * frequency, pos(2) * frequency);
// Масштабируем: хотим отклонение от 1.0 до 1.1 (примерно) // Масштабируем: хотим отклонение от 1.0 до 1.1 (примерно)
float height = 1.0f + (noiseValue * noiseCoeff); float height = 1.0f + (noiseValue * noiseCoeff);
return height; return height;

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#include <vector> #include <vector>
#include <cstdint> #include <cstdint>

View File

@ -1,4 +1,4 @@
#include "TaskManager.h" #include "TaskManager.h"
namespace ZL namespace ZL
@ -27,8 +27,8 @@ namespace ZL
TaskManager::~TaskManager() { TaskManager::~TaskManager() {
#ifndef EMSCRIPTEN #ifndef EMSCRIPTEN
workGuard.reset(); // Разрешаем ioContext.run() завершиться, когда задач не останется workGuard.reset(); // Разрешаем ioContext.run() завершиться, когда задач не останется
ioContext.stop(); // Опционально: немедленная остановка ioContext.stop(); // Опционально: немедленная остановка
for (auto& t : workers) { for (auto& t : workers) {
if (t.joinable()) t.join(); if (t.joinable()) t.join();
} }
@ -51,7 +51,7 @@ namespace ZL
mainThreadTasks.pop(); mainThreadTasks.pop();
} }
#else #else
// Извлекаем только одну задачу, чтобы не блокировать update надолго // Извлекаем только одну задачу, чтобы не блокировать update надолго
{ {
std::lock_guard<std::mutex> lock(mainThreadMutex); std::lock_guard<std::mutex> lock(mainThreadMutex);
if (!mainThreadTasks.empty()) { if (!mainThreadTasks.empty()) {
@ -62,7 +62,7 @@ namespace ZL
#endif #endif
if (task) { if (task) {
task(); // Здесь выполняется RefreshVBO или загрузка текстуры task(); // Здесь выполняется RefreshVBO или загрузка текстуры
} }
} }
} }

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#ifndef EMSCRIPTEN #ifndef EMSCRIPTEN
#include <boost/asio.hpp> #include <boost/asio.hpp>
@ -23,7 +23,7 @@ namespace ZL {
public: public:
TaskManager(size_t threadCount = 2); TaskManager(size_t threadCount = 2);
// Метод для добавления фоновой задачи // ћетод дл¤ добавлени¤ фоновой задачи
void EnqueueBackgroundTask(std::function<void()> task); void EnqueueBackgroundTask(std::function<void()> task);
// Graceful shutdown // Graceful shutdown
@ -49,7 +49,7 @@ namespace ZL {
public: public:
void EnqueueMainThreadTask(std::function<void()> task); void EnqueueMainThreadTask(std::function<void()> task);
// Выполнение задач по одной (или пачкой) за кадр // ¬ыполнение задач по одной (или пачкой) за кадр
void processMainThreadTasks(); void processMainThreadTasks();
}; };

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
#include <string> #include <string>
#include <vector> #include <vector>