Compare commits

..

No commits in common. "59b5bea54083305e03a75ff34706a55f48eb6e0e" and "70ef730e86f44d9b4b1b3b65ecc1fa8ebf51fdcd" have entirely different histories.

83 changed files with 1430 additions and 1865 deletions

View File

@ -73,8 +73,6 @@ 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,14 +1,9 @@
# how to build # how to build
If emsdk is not installed, you need to clone it from here: https://github.com/emscripten-core/emsdk
and install: Activate the environment:
``` ```
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,10 +17,23 @@
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>
@ -36,6 +49,17 @@
} }
}; };
// Кнопка 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,8 +67,6 @@ 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)

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

View File

@ -1,22 +0,0 @@
{
"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

@ -1,52 +0,0 @@
{
"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

@ -1,52 +0,0 @@
{
"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": [5.0, 10.0], "scaleRange": [0.5, 1.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/Variant5.png", "hover": "resources/game_over/Filledbuttons.png",
"pressed": "resources/game_over/Variant6.png" "pressed": "resources/game_over/Filledbuttons.png"
} }
}, },
{ {

View File

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

View File

@ -1,14 +1,20 @@
{ {
"emissionRate": 0.4, "emissionRate": 100.0,
"maxParticles": 400, "maxParticles": 200,
"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": [300.0, 500.0], "lifeTimeRange": [600.0, 1400.0],
"texture": "resources/spark.png", "texture": "resources/spark.png",
"shaderProgramName": "spark" "shaderProgramName": "spark"
} }

View File

@ -1,20 +0,0 @@
{
"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/spark2.png", "texture": "resources/spark_white.png",
"speedRange": [5.0, 10.0], "speedRange": [10.0, 30.0],
"zSpeedRange": [-1.0, 1.0], "zSpeedRange": [-1.0, 1.0],
"scaleRange": [0.5, 2.0], "scaleRange": [0.5, 1.0],
"lifeTimeRange": [200.0, 800.0], "lifeTimeRange": [200.0, 800.0],
"emissionRate": 30.0, "emissionRate": 50.0,
"maxParticles": 150, "maxParticles": 10,
"particleSize": 1.0, "particleSize": 0.5,
"biasX": 0.1, "biasX": 0.1,
"shaderProgramName": "default" "shaderProgramName": "default"
} }

View File

@ -6,20 +6,6 @@
"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",
@ -30,10 +16,9 @@
"horizontal_gravity": "right", "horizontal_gravity": "right",
"vertical_gravity": "bottom", "vertical_gravity": "bottom",
"textures": { "textures": {
"normal": "resources/fire.png", "normal": "resources/shoot_normal.png",
"hover": "resources/fire.png", "hover": "resources/shoot_hover.png",
"pressed": "resources/fire2.png", "pressed": "resources/shoot_pressed.png"
"disabled": "resources/fire_disabled.png"
} }
}, },
{ {
@ -46,42 +31,25 @@
"horizontal_gravity": "left", "horizontal_gravity": "left",
"vertical_gravity": "bottom", "vertical_gravity": "bottom",
"textures": { "textures": {
"normal": "resources/fire.png", "normal": "resources/shoot_normal.png",
"hover": "resources/fire.png", "hover": "resources/shoot_hover.png",
"pressed": "resources/fire2.png", "pressed": "resources/shoot_pressed.png"
"disabled": "resources/fire_disabled.png"
} }
}, },
{ {
"type": "Button", "type": "Slider",
"name": "minusButton", "name": "velocitySlider",
"x": -20, "x": 10,
"y": 110, "y": 200,
"width": 150, "width": 80,
"height": 150, "height": 300,
"border" : 20, "value": 0.0,
"orientation": "vertical",
"horizontal_gravity": "right", "horizontal_gravity": "right",
"vertical_gravity": "bottom", "vertical_gravity": "bottom",
"textures": { "textures": {
"normal": "resources/button_minus.png", "track": "resources/velocitySliderTexture.png",
"hover": "resources/button_minus_pressed.png", "knob": "resources/velocitySliderButton.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)

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

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)

Binary file not shown.

View File

@ -26,7 +26,6 @@ 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,11 +1,44 @@
#include "server.h" #include <boost/beast/core.hpp>
#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;
@ -17,30 +50,75 @@ std::vector<std::string> split(const std::string& s, char delimiter) {
return tokens; return tokens;
} }
Session::Session(Server& server, tcp::socket&& socket, int id) struct ServerBox {
: server_(server) Eigen::Vector3f position;
, ws_(std::move(socket)) Eigen::Matrix3f rotation;
, id_(id) float collisionRadius = 2.0f;
, lastReceivedTime_(std::chrono::system_clock::now()) { 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;
};
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) {
} }
bool Session::is_timed_out(std::chrono::system_clock::time_point now) const { int get_id() const { return id_; }
if (!joined_) return false;
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastReceivedTime_).count();
return elapsed > PLAYER_TIMEOUT_MS;
}
void Session::force_disconnect() { bool hasSpawnReserved() const { return hasReservedSpawn_; }
ws_.async_close(websocket::close_code::normal, const Eigen::Vector3f& reservedSpawn() const { return reservedSpawn_; }
[self = shared_from_this()](beast::error_code) {});
}
int Session::get_id() const { return id_; } bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const {
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;
@ -48,7 +126,7 @@ bool Session::fetchStateAtTime(std::chrono::system_clock::time_point targetTime,
return false; return false;
} }
void Session::send_message(const std::string& msg) { void 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_);
@ -57,10 +135,10 @@ void Session::send_message(const std::string& msg) {
doWrite(); doWrite();
} }
void Session::run() { void run() {
{ {
std::lock_guard<std::mutex> lock(server_.g_sessions_mutex); std::lock_guard<std::mutex> lock(g_sessions_mutex);
server_.g_sessions.push_back(shared_from_this()); 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) {
@ -70,7 +148,7 @@ void Session::run() {
}); });
} }
bool Session::IsMessageValid(const std::string& fullMessage) { bool 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) {
@ -92,14 +170,16 @@ bool Session::IsMessageValid(const std::string& fullMessage) {
#endif #endif
} }
void Session::sendBoxesToClient() { private:
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 < server_.g_serverBoxes.size(); ++i) { for (size_t i = 0; i < g_serverBoxes.size(); ++i) {
const auto& box = server_.g_serverBoxes[i]; const auto& box = g_serverBoxes[i];
if (box.destroyed) continue; if (box.destroyed) continue;
@ -119,11 +199,14 @@ void Session::sendBoxesToClient() {
"0"; "0";
} }
// Если все коробки уничтожены — отправится просто "BOXES:" (это нормально)
send_message(boxMsg); send_message(boxMsg);
} }
void Session::init() public:
void init()
{ {
sendBoxesToClient(); sendBoxesToClient();
@ -141,17 +224,23 @@ void Session::init()
}); });
} }
ClientState Session::get_latest_state(std::chrono::system_clock::time_point now) { ClientState 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 Session::doWrite() { void 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;
@ -174,20 +263,17 @@ void Session::doWrite() {
self->doWrite(); self->doWrite();
}); });
} }
private:
void Session::do_read() { void 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) {
if (self->joined_) { std::lock_guard<std::mutex> lock(g_sessions_mutex);
self->server_.broadcastToAllExceptId("PLAYER_LEFT:" + std::to_string(self->id_), self->id_); g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(),
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();
}), self->server_.g_sessions.end()); }), g_sessions.end());
std::cout << "Client " << self->id_ << " removed from session list\n"; std::cout << "Client " << self->id_ << " disconnected\n";
return; return;
} }
@ -199,12 +285,11 @@ void Session::do_read() {
}); });
} }
void Session::process_message(const std::string& msg) { void 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;
@ -231,7 +316,7 @@ void Session::process_message(const std::string& msg) {
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 = server_.PickSafeSpawnPos(id_); Eigen::Vector3f spawnPos = PickSafeSpawnPos(id_);
this->hasReservedSpawn_ = true; this->hasReservedSpawn_ = true;
this->reservedSpawn_ = spawnPos; this->reservedSpawn_ = spawnPos;
@ -257,16 +342,27 @@ void Session::process_message(const std::string& msg) {
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(server_.g_sessions_mutex); std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : server_.g_sessions) { for (auto& session : 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);
@ -280,8 +376,8 @@ void Session::process_message(const std::string& msg) {
return; return;
} }
{ {
std::lock_guard<std::mutex> gd(server_.g_dead_mutex); std::lock_guard<std::mutex> gd(g_dead_mutex);
if (server_.g_dead_players.find(id_) != server_.g_dead_players.end()) { if (g_dead_players.find(id_) != 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;
} }
@ -303,8 +399,8 @@ void Session::process_message(const std::string& msg) {
} }
else if (type == "RESPAWN") { else if (type == "RESPAWN") {
{ {
std::lock_guard<std::mutex> gd(server_.g_dead_mutex); std::lock_guard<std::mutex> gd(g_dead_mutex);
server_.g_dead_players.erase(id_); g_dead_players.erase(id_);
} }
{ {
@ -314,7 +410,7 @@ void Session::process_message(const std::string& msg) {
ClientState st; ClientState st;
st.id = id_; st.id = id_;
Eigen::Vector3f spawnPos = server_.PickSafeSpawnPos(id_); Eigen::Vector3f spawnPos = PickSafeSpawnPos(id_);
st.position = spawnPos; st.position = spawnPos;
this->hasReservedSpawn_ = true; this->hasReservedSpawn_ = true;
@ -335,13 +431,13 @@ void Session::process_message(const std::string& msg) {
"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_);
server_.broadcastToAll(respawnMsg); 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);
server_.broadcastToAll(playerInfo); 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();
server_.broadcastToAll(eventMsg); 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";
} }
@ -358,6 +454,12 @@ void Session::process_message(const std::string& msg) {
); );
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) + ":" +
@ -370,14 +472,50 @@ void Session::process_message(const std::string& msg) {
std::to_string(dir.z()) + ":" + std::to_string(dir.z()) + ":" +
std::to_string(velocity); std::to_string(velocity);
{
server_.broadcastToAllExceptId(broadcast, id_); std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
server_.createProjectile(id_, pos, dir, velocity); if (session->get_id() != id_) {
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{}() };
@ -442,54 +580,14 @@ Eigen::Vector3f Server::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 Server::broadcastToAll(const std::string& message) { void 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 Server::broadcastToAllExceptId(const std::string& message, int id) void update_world(net::steady_timer& timer, net::io_context& ioc) {
{
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();
@ -497,24 +595,26 @@ void Server::update_world() {
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());
// --- Detect and force-disconnect timed-out players --- // --- Snapshot every 500ms ---
{ /*if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_snapshot_time).count() >= 500) {
std::vector<std::shared_ptr<Session>> timedOut; last_snapshot_time = now;
{
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) {
if (session->is_timed_out(now)) { ClientState st = session->get_latest_state(now);
timedOut.push_back(session); snapshot_msg += "|" + std::to_string(session->get_id()) + ":" + st.formPingMessageContent();
}
}
}
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) {
@ -734,13 +834,13 @@ void Server::update_world() {
// --- 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([this](const boost::system::error_code& ec) { timer.async_wait([&timer, &ioc](const boost::system::error_code& ec) {
if (ec) return; if (ec) return;
update_world(); update_world(timer, ioc);
}); });
} }
std::vector<ServerBox> Server::generateServerBoxes(int count) { std::vector<ServerBox> 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());
@ -789,42 +889,34 @@ std::vector<ServerBox> Server::generateServerBoxes(int count) {
return boxes; return boxes;
} }
Server::Server(tcp::acceptor& acceptor, net::io_context& ioc) int main() {
: acceptor_(acceptor) try {
, 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} };
Server server(acceptor, ioc); int next_id = 1000;
server.accept();
std::cout << "Server started on port 8081...\n"; std::cout << "Server started on port 8081...\n";
server.update_world(); auto do_accept = [&](auto& self_fn) -> void {
acceptor.async_accept([&, self_fn](beast::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<Session>(std::move(socket), next_id++)->run();
}
self_fn(self_fn);
});
};
net::steady_timer timer(ioc);
update_world(timer, ioc);
do_accept(do_accept);
ioc.run(); ioc.run();
} }
catch (std::exception const& e) { catch (std::exception const& e) {

View File

@ -1,140 +0,0 @@
#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 = CONST_DEFAULT_WIDTH; int Environment::width = 0;
int Environment::height = CONST_DEFAULT_HEIGHT; int Environment::height = 0;
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,8 +14,6 @@ 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,8 +86,6 @@ 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();
@ -148,28 +146,10 @@ 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();
@ -178,14 +158,15 @@ 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);
@ -202,12 +183,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;
}; };
@ -219,10 +200,21 @@ 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);
@ -238,14 +230,13 @@ namespace ZL
} }
void Game::drawScene() { void Game::drawScene() {
glViewport(0, 0, Environment::width, Environment::height);
if (!loadingCompleted) { if (!loadingCompleted) {
drawLoading(); drawLoading();
} }
else else
{ {
if (menuManager.shouldRenderSpace()) { if (spaceGameStarted) {
space.drawScene(); space.drawScene();
} }
else else
@ -259,10 +250,17 @@ namespace ZL
void Game::drawLoading() void Game::drawLoading()
{ {
static const std::string defaultShaderName = "default";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
glClear(GL_DEPTH_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT);
renderer.shaderManager.PushShader(defaultShaderName); renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
//float width = Environment::projectionWidth; //float width = Environment::projectionWidth;
//float height = Environment::projectionHeight; //float height = Environment::projectionHeight;
@ -280,6 +278,9 @@ namespace ZL
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
} }
@ -319,7 +320,7 @@ namespace ZL
//throw std::runtime_error("Synchronization is lost"); //throw std::runtime_error("Synchronization is lost");
} }
if (menuManager.shouldRenderSpace()) { if (spaceGameStarted) {
space.processTickCount(newTickCount, delta); space.processTickCount(newTickCount, delta);
} }
menuManager.uiManager.update(static_cast<float>(delta)); menuManager.uiManager.update(static_cast<float>(delta));
@ -334,10 +335,8 @@ 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);
} }
@ -371,53 +370,33 @@ 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(static_cast<int64_t>(event.tfinger.fingerId), mx, my); handleDown(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(static_cast<int64_t>(event.tfinger.fingerId), mx, my); handleUp(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(static_cast<int64_t>(event.tfinger.fingerId), mx, my); handleMotion(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(ZL::UiManager::MOUSE_FINGER_ID, mx, my); if (event.type == SDL_MOUSEBUTTONDOWN) handleDown(mx, my);
else handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my); else handleUp(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(ZL::UiManager::MOUSE_FINGER_ID, mx, my); handleMotion(mx, my);
} }
/*if (event.type == SDL_MOUSEBUTTONDOWN) { /*if (event.type == SDL_MOUSEBUTTONDOWN) {
@ -480,22 +459,6 @@ 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) {
@ -524,58 +487,67 @@ namespace ZL
} }
mainThreadHandler.processMainThreadTasks(); mainThreadHandler.processMainThreadTasks();
if (menuManager.shouldRenderSpace()) { if (spaceGameStarted) {
space.update(); space.update();
} }
} }
void Game::handleDown(int64_t fingerId, int mx, int my) void Game::handleDown(int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::projectionHeight - my; int uiY = Environment::projectionHeight - my;
menuManager.uiManager.onTouchDown(fingerId, uiX, uiY); menuManager.uiManager.onMouseDown(uiX, uiY);
if (!menuManager.uiManager.isUiInteractionForFinger(fingerId)) { bool uiHandled = false;
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(int64_t fingerId, int mx, int my) void Game::handleUp(int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::projectionHeight - my; int uiY = Environment::projectionHeight - my;
// Check BEFORE onTouchUp erases the finger from the map. menuManager.uiManager.onMouseUp(uiX, uiY);
// 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 (!wasUiInteraction) { if (!menuManager.uiManager.isUiInteraction()) {
if (menuManager.shouldRenderSpace()) { if (spaceGameStarted) {
space.handleUp(mx, my); space.handleUp(mx, my);
} }
} }
} }
void Game::handleMotion(int64_t fingerId, int mx, int my) void Game::handleMotion(int mx, int my)
{ {
int uiX = mx; int uiX = mx;
int uiY = Environment::projectionHeight - my; int uiY = Environment::projectionHeight - my;
// Check before onTouchMove so the "started on UI" state is preserved menuManager.uiManager.onMouseMove(uiX, uiY);
// regardless of what onTouchMove does internally.
bool wasUiInteraction = menuManager.uiManager.isUiInteractionForFinger(fingerId);
menuManager.uiManager.onTouchMove(fingerId, uiX, uiY);
if (!wasUiInteraction) { if (!menuManager.uiManager.isUiInteraction()) {
if (menuManager.shouldRenderSpace()) { if (spaceGameStarted) {
space.handleMotion(mx, my); space.handleMotion(mx, my);
} }
} }
} }

View File

@ -13,7 +13,6 @@
#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"
@ -51,9 +50,9 @@ namespace ZL {
void drawUI(); void drawUI();
void drawUnderMainMenu(); void drawUnderMainMenu();
void drawLoading(); void drawLoading();
void handleDown(int64_t fingerId, int mx, int my); void handleDown(int mx, int my);
void handleUp(int64_t fingerId, int mx, int my); void handleUp(int mx, int my);
void handleMotion(int64_t fingerId, int mx, int my); void handleMotion(int mx, int my);
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
static Game* s_instance; static Game* s_instance;
@ -66,14 +65,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;
}; };

View File

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

View File

@ -1,11 +0,0 @@
#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,225 +10,278 @@ 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);
enterMainMenu(); uiManager.loadFromFile("resources/config/main_menu.json", renderer, CONST_ZIP_FILE);
}
bool MenuManager::shouldRenderSpace() const uiSavedRoot = loadUiFromFile("resources/config/ui.json", renderer, CONST_ZIP_FILE);
{
return state == GameState::Gameplay
|| state == GameState::GameOver
|| state == GameState::ConnectionLost;
}
// ── State: MainMenu ────────────────────────────────────────────────────── settingsSavedRoot = loadUiFromFile("resources/config/settings.json", renderer, CONST_ZIP_FILE);
void MenuManager::enterMainMenu() multiplayerSavedRoot = loadUiFromFile("resources/config/multiplayer_menu.json", renderer, CONST_ZIP_FILE);
{
state = GameState::MainMenu;
uiManager.replaceRoot(mainMenuRoot);
if (onMainMenuEntered) onMainMenuEntered(); gameOverSavedRoot = loadUiFromFile("resources/config/game_over.json", renderer, CONST_ZIP_FILE);
uiManager.setButtonCallback("singleButton", [this](const std::string&) { auto shipSelectionRoot = loadUiFromFile("resources/config/ship_selection_menu.json", renderer, CONST_ZIP_FILE);
enterShipSelectionSingle(); std::function<void()> loadGameplayUI;
}); 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.setButtonPressCallback("shootButton", [this](const std::string&) { uiManager.setAnimationCallback("exitButton", "bgScroll", [this]() {
if (onFirePressed) onFirePressed(); std::cerr << "Exit button bgScroll animation finished" << std::endl;
g_exitBgAnimating = false;
}); });
uiManager.setButtonPressCallback("shootButton2", [this](const std::string&) {
if (onFirePressed) onFirePressed(); uiManager.setButtonCallback("playButton", [this](const std::string& name) {
std::cerr << "Play button pressed: " << name << std::endl;
}); });
uiManager.setButtonPressCallback("plusButton", [this](const std::string&) {
int newVel = Environment::shipState.selectedVelocity + 1; uiManager.setButtonCallback("settingsButton", [this](const std::string& name) {
if (newVel > 4) newVel = 4; std::cerr << "Settings button pressed: " << name << std::endl;
if (onVelocityChanged) onVelocityChanged(newVel); uiManager.startAnimationOnNode("playButton", "buttonsExit");
uiManager.startAnimationOnNode("settingsButton", "buttonsExit");
uiManager.startAnimationOnNode("exitButton", "buttonsExit");
}); });
uiManager.setButtonPressCallback("minusButton", [this](const std::string&) {
int newVel = Environment::shipState.selectedVelocity - 1; uiManager.setButtonCallback("exitButton", [this](const std::string& name) {
if (newVel < 0) newVel = 0; std::cerr << "Exit button pressed: " << name << std::endl;
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));
if (newVel > 2) newVel = 2; uiManager.setButtonCallback("shootButton", [this](const std::string& name) {
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) {
if (onVelocityChanged) onVelocityChanged(newVel); 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)) {
// ── State: GameOver ────────────────────────────────────────────────────── uiManager.setButtonCallback("spaceshipButton", [this, loadGameplayUI](const std::string& btnName) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
void MenuManager::enterGameOver(int score) if (nick.empty()) nick = "Player";
{ int shipType = 0;
state = GameState::GameOver; uiManager.popMenu();
uiManager.replaceRoot(gameOverRoot); loadGameplayUI();
if (onSingleplayerPressed) onSingleplayerPressed(nick, shipType);
uiManager.setText("scoreText", "Score: " + std::to_string(score));
uiManager.setButtonCallback("restartButton", [this](const std::string&) {
if (onRestartPressed) onRestartPressed();
enterGameplay();
}); });
uiManager.setButtonCallback("gameOverExitButton", [this](const std::string&) {
enterMainMenu(); 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 {
// ── State: ConnectionLost ───────────────────────────────────────────────── std::cerr << "Failed to push ship selection menu\n";
}
void MenuManager::enterConnectionLost()
{
state = GameState::ConnectionLost;
uiManager.replaceRoot(connectionLostRoot);
uiManager.setButtonCallback("reconnectButton", [this](const std::string&) {
// TODO: reconnect logic
}); });
uiManager.setButtonCallback("exitServerButton", [this](const std::string&) {
enterMainMenu(); 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 {
// ── Public event API ────────────────────────────────────────────────────── std::cerr << "Failed to push ship selection menu\n";
void MenuManager::notifyConnected()
{
if (state == GameState::Connecting) {
enterGameplay();
} }
});
/*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;
} }
void MenuManager::notifyConnectionFailed() uiManager.setText("statusText", "Connecting to " + serverAddress + "...");
{ std::cerr << "Connecting to server: " << serverAddress << std::endl;
if (state == GameState::Connecting) {
enterConnectionFailed(); });
uiManager.setButtonCallback("backButton", [this](const std::string& buttonName) {
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";
}
});
uiManager.setButtonCallback("exitButton", [](const std::string& name) {
std::cerr << "Exit from main menu pressed: " << name << " -> exiting\n";
Environment::exitGameLoop = true;
});*/
} }
void MenuManager::showGameOver(int score) void MenuManager::showGameOver(int score)
{ {
if (state == GameState::Gameplay) { if (!uiGameOverShown) {
enterGameOver(score); if (uiManager.pushMenuFromSavedRoot(gameOverSavedRoot)) {
} uiManager.setText("scoreText", std::string("Score: ") + std::to_string(score));
}
void MenuManager::showConnectionLost() uiManager.setButtonCallback("restartButton", [this](const std::string& name) {
{ uiManager.setText("scoreText", "");
if (state == GameState::Gameplay) { uiGameOverShown = false;
enterConnectionLost(); uiManager.popMenu();
} if (onRestartPressed) onRestartPressed();
} });
} // namespace ZL uiManager.setButtonCallback("gameOverExitButton", [this](const std::string& name) {
Environment::exitGameLoop = true;
});
uiGameOverShown = true;
}
else {
std::cerr << "Failed to load game_over.json\n";
}
}
}
}

View File

@ -7,71 +7,37 @@
namespace ZL { namespace ZL {
extern const char* CONST_ZIP_FILE; extern const char* CONST_ZIP_FILE;
//extern bool g_exitBgAnimating;
enum class GameState { class MenuManager
MainMenu, {
ShipSelectionSingle,
ShipSelectionMulti,
Connecting,
ConnectionFailed,
Gameplay,
GameOver,
ConnectionLost
};
class MenuManager {
protected: protected:
Renderer& renderer; Renderer& renderer;
std::shared_ptr<UiNode> uiSavedRoot;
// Pre-loaded UI roots (loaded once in setupMenu) std::shared_ptr<UiNode> gameOverSavedRoot;
std::shared_ptr<UiNode> mainMenuRoot; std::shared_ptr<UiNode> settingsSavedRoot;
std::shared_ptr<UiNode> shipSelectionRoot; std::shared_ptr<UiNode> multiplayerSavedRoot;
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();
// Returns true for states where Space should render and run (Gameplay, GameOver, ConnectionLost) //void showGameOver();
bool shouldRenderSpace() const;
GameState getState() const { return state; }
// Called by game events
void showGameOver(int score); 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,8 +25,6 @@
#include "network/LocalClient.h" #include "network/LocalClient.h"
#endif #endif
#include "GameConstants.h"
namespace ZL namespace ZL
{ {
@ -254,26 +252,20 @@ namespace ZL
Space::~Space() { Space::~Space() {
} }
void Space::resetPlayerState() void Space::setup() {
{
shipAlive = true;
gameOver = false; menuManager.onRestartPressed = [this]() {
showExplosion = false; this->shipAlive = true;
explosionEmitter.setEmissionPoints(std::vector<Vector3f>()); this->gameOver = false;
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 {
@ -284,25 +276,12 @@ 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]() {
@ -310,16 +289,10 @@ 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({ Vector3f{0,0,45000} }); projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
//projectileEmitter.setUseWorldSpace(true);
cubemapTexture = std::make_shared<Texture>( cubemapTexture = std::make_shared<Texture>(
std::array<TextureDataStruct, 6>{ std::array<TextureDataStruct, 6>{
@ -342,9 +315,7 @@ 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();
@ -423,13 +394,17 @@ namespace ZL
void Space::drawCubemap(float skyPercent) void Space::drawCubemap(float skyPercent)
{ {
static const std::string envSkyShaderName = "env_sky"; static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env_sky";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
static const std::string skyPercentUniformName = "skyPercent"; static const std::string skyPercentUniformName = "skyPercent";
renderer.shaderManager.PushShader(envSkyShaderName); renderer.shaderManager.PushShader(envShaderName);
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);
@ -477,14 +452,24 @@ 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),
@ -505,59 +490,69 @@ 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(defaultShaderName); renderer.shaderManager.PushShader("default");
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();
if (showExplosion) { //projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height, false);
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) {
explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
}
//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);
@ -576,32 +571,31 @@ 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;
@ -639,9 +633,17 @@ 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);
@ -682,6 +684,7 @@ 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);
@ -703,6 +706,8 @@ namespace ZL
} }
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader(); renderer.shaderManager.PopShader();
CheckGlError(); CheckGlError();
@ -956,11 +961,17 @@ 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();
@ -1289,6 +1300,7 @@ namespace ZL
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.EnableVertexAttribArray("vPosition");
renderer.RenderUniform4fv("uColor", enemyColor.data()); renderer.RenderUniform4fv("uColor", enemyColor.data());
// рамки // рамки
@ -1311,6 +1323,7 @@ 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();
@ -1396,6 +1409,7 @@ namespace ZL
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.EnableVertexAttribArray("vPosition");
renderer.RenderUniform4fv("uColor", enemyColor.data()); renderer.RenderUniform4fv("uColor", enemyColor.data());
// стрелка // стрелка
@ -1413,6 +1427,7 @@ 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();
@ -1435,6 +1450,9 @@ 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;
@ -1545,41 +1563,6 @@ 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) };
@ -1611,11 +1594,33 @@ 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();
p->projectileEmitter.resetEmissionPoints({ worldPos }); Vector3f rel = worldPos - Environment::shipState.position;
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) {
@ -1726,8 +1731,6 @@ namespace ZL
} }
} }
planetObject.update(static_cast<float>(delta));
// update velocity text // update velocity text
if (shipAlive && !gameOver) { if (shipAlive && !gameOver) {
@ -1770,18 +1773,6 @@ 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;
@ -1871,17 +1862,6 @@ 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,7 +88,6 @@ namespace ZL {
VertexDataStruct boxBase; VertexDataStruct boxBase;
SparkEmitter sparkEmitter; SparkEmitter sparkEmitter;
SparkEmitter sparkEmitterCargo;
SparkEmitter projectileEmitter; SparkEmitter projectileEmitter;
SparkEmitter explosionEmitter; SparkEmitter explosionEmitter;
PlanetObject planetObject; PlanetObject planetObject;
@ -98,7 +97,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;
@ -124,7 +123,6 @@ 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;
@ -141,7 +139,6 @@ 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,7 +8,6 @@
#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 {
@ -75,7 +74,7 @@ namespace ZL {
texture = tex; texture = tex;
} }
void SparkEmitter::prepareDrawData(bool withRotation) { void SparkEmitter::prepareDrawData() {
if (!drawDataDirty) return; if (!drawDataDirty) return;
drawPositions.clear(); drawPositions.clear();
@ -92,7 +91,7 @@ namespace ZL {
for (const auto& particle : particles) { for (const auto& particle : particles) {
if (particle.active) { if (particle.active) {
Vector3f posCam; Vector3f posCam;
if (withRotation) { if (useWorldSpace) {
Vector3f rel = particle.position - Environment::shipState.position; Vector3f rel = particle.position - Environment::shipState.position;
posCam = Environment::inverseShipMatrix * rel; posCam = Environment::inverseShipMatrix * rel;
} }
@ -121,29 +120,6 @@ 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 });
@ -161,31 +137,12 @@ 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::prepareForDraw(bool withRotation) { void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight) {
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!");
} }
@ -198,30 +155,46 @@ namespace ZL {
throw std::runtime_error("Failed to load spark emitter config file 2!"); throw std::runtime_error("Failed to load spark emitter config file 2!");
} }
//prepareDrawData(withRotation); prepareDrawData();
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.SetMatrix(); renderer.EnableVertexAttribArray(vPositionName);
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();
} }
@ -234,19 +207,9 @@ 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 >= static_cast<long long>(emissionRate)) { if (isActive && elapsed >= emissionRate) {
int emitCount = static_cast<int>(elapsed / emissionRate); emit();
float elapsedF = static_cast<float>(elapsed); lastEmissionTime = currentTime;
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;
} }
@ -285,7 +248,7 @@ namespace ZL {
} }
} }
void SparkEmitter::emit(float ageMs, float lerpT) { void SparkEmitter::emit() {
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!");
} }
@ -293,30 +256,7 @@ namespace ZL {
if (emissionPoints.empty()) return; if (emissionPoints.empty()) return;
bool emitted = false; bool emitted = false;
auto applyAge = [](SparkParticle& particle, float age) { for (int i = 0; i < emissionPoints.size(); ++i) {
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) {
@ -324,9 +264,8 @@ namespace ZL {
initParticle(particle, i); initParticle(particle, i);
particle.active = true; particle.active = true;
particle.lifeTime = 0; particle.lifeTime = 0;
particle.position = birthPos; particle.position = emissionPoints[i];
particle.emitterIndex = i; particle.emitterIndex = i;
applyAge(particle, ageMs);
particleFound = true; particleFound = true;
emitted = true; emitted = true;
break; break;
@ -335,11 +274,11 @@ namespace ZL {
if (!particleFound && !particles.empty()) { if (!particleFound && !particles.empty()) {
size_t oldestIndex = 0; size_t oldestIndex = 0;
float maxLifeRatio = 0; float maxLifeTime = 0;
for (size_t j = 0; j < particles.size(); ++j) { for (size_t j = 0; j < particles.size(); ++j) {
if (particles[j].lifeTime > maxLifeRatio) { if (particles[j].lifeTime > maxLifeTime) {
maxLifeRatio = particles[j].lifeTime; maxLifeTime = particles[j].lifeTime;
oldestIndex = j; oldestIndex = j;
} }
} }
@ -347,9 +286,8 @@ 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 = birthPos; particles[oldestIndex].position = emissionPoints[i];
particles[oldestIndex].emitterIndex = i; particles[oldestIndex].emitterIndex = i;
applyAge(particles[oldestIndex], ageMs);
emitted = true; emitted = true;
} }
} }
@ -360,14 +298,6 @@ 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;
} }
@ -535,9 +465,8 @@ namespace ZL {
std::cout << "Total emission points: " << emissionPoints.size() << std::endl; std::cout << "Total emission points: " << emissionPoints.size() << std::endl;
} }
else { else {
setEmissionPoints({}); std::cerr << "Emission points parsed but empty" << std::endl;
std::cout << "Emission points parsed but empty" << std::endl; throw std::runtime_error("Failed to load spark emitter config file 10!");
//throw std::runtime_error("Failed to load spark emitter config file 10!");
} }
} }
else { else {
@ -624,6 +553,25 @@ 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,7 +26,6 @@ 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;
@ -51,7 +50,7 @@ namespace ZL {
std::string shaderProgramName; std::string shaderProgramName;
bool configured; bool configured;
void prepareDrawData(bool withRotation); void prepareDrawData();
bool useWorldSpace; bool useWorldSpace;
public: public:
@ -63,7 +62,6 @@ 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; }
@ -75,23 +73,14 @@ 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(float ageMs = 0.0f, float lerpT = 0.0f); void emit();
// Вызывать ДО 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,10 +1,9 @@
#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 {
@ -58,17 +57,26 @@ 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();
} }
@ -139,7 +147,14 @@ 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);
@ -148,44 +163,9 @@ namespace ZL {
glBindTexture(GL_TEXTURE_2D, texKnob->getTexID()); glBindTexture(GL_TEXTURE_2D, texKnob->getTexID());
renderer.DrawVertexRenderStruct(knobMesh); renderer.DrawVertexRenderStruct(knobMesh);
} }
}
void UiStaticImage::buildMesh() { renderer.DisableVertexAttribArray(vPositionName);
mesh.data.PositionData.clear(); renderer.DisableVertexAttribArray(vTexCoordName);
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 {
@ -268,7 +248,6 @@ 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;
} }
@ -276,7 +255,6 @@ 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;
} }
@ -312,9 +290,6 @@ 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;
} }
@ -424,32 +399,6 @@ 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>();
@ -539,7 +488,6 @@ namespace ZL {
sliders.clear(); sliders.clear();
textViews.clear(); textViews.clear();
textFields.clear(); textFields.clear();
staticImages.clear();
collectButtonsAndSliders(root); collectButtonsAndSliders(root);
nodeActiveAnims.clear(); nodeActiveAnims.clear();
@ -551,9 +499,6 @@ 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) {
@ -669,15 +614,9 @@ 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);
@ -718,12 +657,6 @@ 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() {
@ -753,9 +686,6 @@ 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);
} }
@ -769,16 +699,6 @@ 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) {
@ -864,9 +784,8 @@ namespace ZL {
prev.buttons = buttons; prev.buttons = buttons;
prev.sliders = sliders; prev.sliders = sliders;
prev.textFields = textFields; prev.textFields = textFields;
prev.staticImages = staticImages; prev.pressedButton = pressedButton;
prev.pressedButtons = pressedButtons; prev.pressedSlider = pressedSlider;
prev.pressedSliders = pressedSliders;
prev.focusedTextField = focusedTextField; prev.focusedTextField = focusedTextField;
prev.path = ""; prev.path = "";
@ -915,9 +834,8 @@ namespace ZL {
buttons = s.buttons; buttons = s.buttons;
sliders = s.sliders; sliders = s.sliders;
textFields = s.textFields; textFields = s.textFields;
staticImages = s.staticImages; pressedButton = s.pressedButton;
pressedButtons = s.pressedButtons; pressedSlider = s.pressedSlider;
pressedSliders = s.pressedSliders;
focusedTextField = s.focusedTextField; focusedTextField = s.focusedTextField;
animCallbacks = s.animCallbacks; animCallbacks = s.animCallbacks;
@ -950,9 +868,6 @@ 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);
} }
@ -1132,25 +1047,18 @@ namespace ZL {
} }
} }
void UiManager::onTouchMove(int64_t fingerId, int x, int y) { void UiManager::onMouseMove(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->state != ButtonState::Disabled) if (b->rect.contains((float)x, (float)y)) {
{
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;
} }
} }
}
}
auto it = pressedSliders.find(fingerId); if (pressedSlider) {
if (it != pressedSliders.end()) { auto s = pressedSlider;
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;
@ -1166,23 +1074,17 @@ namespace ZL {
} }
} }
void UiManager::onMouseDown(int x, int y) {
void UiManager::onTouchDown(int64_t fingerId, int x, int y) {
for (auto& b : buttons) { for (auto& b : buttons) {
if (b->state != ButtonState::Disabled) if (b->rect.contains((float)x, (float)y)) {
{
if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) {
b->state = ButtonState::Pressed; b->state = ButtonState::Pressed;
pressedButtons[fingerId] = b; pressedButton = 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)) {
pressedSliders[fingerId] = s; pressedSlider = 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;
@ -1210,32 +1112,29 @@ namespace ZL {
} }
} }
void UiManager::onTouchUp(int64_t fingerId, int x, int y) { void UiManager::onMouseUp(int x, int y) {
std::vector<std::shared_ptr<UiButton>> clicked; std::vector<std::shared_ptr<UiButton>> clicked;
auto btnIt = pressedButtons.find(fingerId); for (auto& b : buttons) {
if (btnIt != pressedButtons.end()) { if (!b) continue;
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) { if (contains && pressedButton == b) {
clicked.push_back(b); clicked.push_back(b);
} }
// On mouse: leave Hover if still over button. On touch: always Normal. b->state = contains ? ButtonState::Hover : ButtonState::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) {
@ -1351,13 +1250,6 @@ 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,7 +10,6 @@
#include <memory> #include <memory>
#include <functional> #include <functional>
#include <map> #include <map>
#include <cstdint>
namespace ZL { namespace ZL {
@ -24,17 +23,12 @@ 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 {
@ -61,13 +55,11 @@ 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
}; };
@ -84,17 +76,14 @@ 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;
@ -163,17 +152,6 @@ 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;
@ -200,7 +178,6 @@ 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 {
@ -226,35 +203,19 @@ 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);
// Multi-touch methods (used directly for touch events with per-finger IDs) void onMouseMove(int x, int y);
void onTouchDown(int64_t fingerId, int x, int y); void onMouseDown(int x, int y);
void onTouchUp(int64_t fingerId, int x, int y); void onMouseUp(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 !pressedButtons.empty() || !pressedSliders.empty() || focusedTextField != nullptr; return pressedButton != nullptr || pressedSlider != nullptr || 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() {
@ -273,7 +234,6 @@ 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);
@ -289,8 +249,6 @@ 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();
@ -336,14 +294,12 @@ 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)
// Per-finger tracking for multi-touch support std::shared_ptr<UiButton> pressedButton;
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons; std::shared_ptr<UiSlider> pressedSlider;
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
std::shared_ptr<UiTextField> focusedTextField; std::shared_ptr<UiTextField> focusedTextField;
struct MenuState { struct MenuState {
@ -351,9 +307,8 @@ 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::vector<std::shared_ptr<UiStaticImage>> staticImages; std::shared_ptr<UiButton> pressedButton;
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons; std::shared_ptr<UiSlider> pressedSlider;
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,6 +26,10 @@ 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
@ -51,8 +55,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);
@ -68,6 +72,10 @@ 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;
@ -75,10 +83,6 @@ 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*/) {
@ -127,11 +131,7 @@ 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,6 +240,68 @@ 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;
@ -265,6 +327,8 @@ 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 * 200.0f; constexpr float SHIP_ACCEL = 1.0f * 1000.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,7 +26,6 @@ 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,7 +34,6 @@ 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; // ƒл¤ обработки вход¤щих пакетов
@ -51,7 +50,6 @@ 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,15 +28,6 @@ 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,7 +54,6 @@ 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,19 +103,6 @@ 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) {
@ -369,12 +356,6 @@ 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,7 +20,6 @@ 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_;
@ -42,7 +41,7 @@ namespace ZL {
return serverBoxes_; return serverBoxes_;
} }
std::vector<bool> getServerBoxDestroyedFlags() override { std::vector<bool> getServerBoxDestroyedFlags() {
return serverBoxesDestroyed_; return serverBoxesDestroyed_;
} }
@ -50,7 +49,6 @@ 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,15 +26,6 @@ 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,7 +28,6 @@ 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,7 +6,6 @@
#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 {
@ -186,10 +185,16 @@ namespace ZL {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
static const std::string planetBakeShaderName = "planetBake"; static const std::string defaultShaderName2 = "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];
@ -255,6 +260,8 @@ 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();
} }
@ -291,11 +298,22 @@ namespace ZL {
void PlanetObject::drawPlanet(Renderer& renderer) void PlanetObject::drawPlanet(Renderer& renderer)
{ {
static const std::string planetLandShaderName = "planetLand"; static const std::string defaultShaderName = "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(planetLandShaderName); renderer.shaderManager.PushShader(defaultShaderName);
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);
@ -317,9 +335,10 @@ namespace ZL {
const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix();
renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1i("Texture", 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); // Та же матрица, что и при запекании
@ -357,6 +376,12 @@ 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();
@ -365,10 +390,20 @@ namespace ZL {
void PlanetObject::drawStones(Renderer& renderer) void PlanetObject::drawStones(Renderer& renderer)
{ {
static const std::string planetStoneShaderName = "planetStone"; static const std::string defaultShaderName2 = "planetStone";
static const std::string vPositionName = "vPosition";
static const std::string vColorName = "vColor";
static const std::string vNormalName = "vNormal";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(planetStoneShaderName); 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);
@ -433,6 +468,10 @@ 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();
@ -441,10 +480,16 @@ 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;
@ -526,6 +571,9 @@ 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();
@ -533,8 +581,20 @@ namespace ZL {
void PlanetObject::drawCamp(Renderer& renderer) void PlanetObject::drawCamp(Renderer& renderer)
{ {
renderer.shaderManager.PushShader(defaultShaderName); static const std::string defaultShaderName2 = "default";
static const std::string vPositionName = "vPosition";
static const std::string vColorName = "vColor";
static const std::string vNormalName = "vNormal";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName2);
renderer.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);
@ -595,6 +655,10 @@ 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,14 +50,17 @@ 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;
@ -381,7 +384,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_DYNAMIC_DRAW); glBufferData(GL_ARRAY_BUFFER, data.PositionData.size() * 12, &data.PositionData[0], GL_STATIC_DRAW);
if (data.TexCoordData.size() > 0) if (data.TexCoordData.size() > 0)
{ {
@ -392,7 +395,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_DYNAMIC_DRAW); glBufferData(GL_ARRAY_BUFFER, data.TexCoordData.size() * 8, &data.TexCoordData[0], GL_STATIC_DRAW);
} }
if (data.NormalData.size() > 0) if (data.NormalData.size() > 0)
@ -404,7 +407,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_DYNAMIC_DRAW); glBufferData(GL_ARRAY_BUFFER, data.NormalData.size() * 12, &data.NormalData[0], GL_STATIC_DRAW);
} }
if (data.TangentData.size() > 0) if (data.TangentData.size() > 0)
@ -416,7 +419,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_DYNAMIC_DRAW); glBufferData(GL_ARRAY_BUFFER, data.TangentData.size() * 12, &data.TangentData[0], GL_STATIC_DRAW);
} }
if (data.BinormalData.size() > 0) if (data.BinormalData.size() > 0)
@ -428,7 +431,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_DYNAMIC_DRAW); glBufferData(GL_ARRAY_BUFFER, data.BinormalData.size() * 12, &data.BinormalData[0], GL_STATIC_DRAW);
} }
if (data.ColorData.size() > 0) if (data.ColorData.size() > 0)
@ -440,7 +443,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_DYNAMIC_DRAW); glBufferData(GL_ARRAY_BUFFER, data.ColorData.size() * 12, &data.ColorData[0], GL_STATIC_DRAW);
} }
} }
@ -700,7 +703,19 @@ 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)
@ -718,7 +733,17 @@ 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;
@ -757,6 +782,22 @@ 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();
@ -846,25 +887,8 @@ 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");
@ -878,6 +902,7 @@ 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
{ {
@ -887,6 +912,7 @@ 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
{ {
@ -896,6 +922,7 @@ 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
{ {
@ -905,6 +932,7 @@ 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
{ {
@ -914,6 +942,7 @@ 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
{ {
@ -922,7 +951,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,6 +127,13 @@ 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);
@ -138,8 +145,6 @@ 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,6 +167,7 @@ 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);
} }
@ -180,26 +181,9 @@ 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);
auto& shaderResource = shaderResourceMap[shaderName]; glUseProgram(shaderResourceMap[shaderName]->getShaderProgram());
glUseProgram(shaderResource->getShaderProgram());
auto& shaderAttribList = shaderResource->attribList;
for (auto& attrib : shaderAttribList)
{
glEnableVertexAttribArray(attrib.second);
}
} }
@ -208,39 +192,12 @@ 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 {
auto& shaderResource = shaderResourceMap[shaderStack.top()]; glUseProgram(shaderResourceMap[shaderStack.top()]->getShaderProgram());
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,7 +42,6 @@ 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,6 +380,10 @@ 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;
@ -397,6 +401,9 @@ 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>