diff --git a/proj-web/CMakeLists.txt b/proj-web/CMakeLists.txt
index 8b02c39..0f353b1 100644
--- a/proj-web/CMakeLists.txt
+++ b/proj-web/CMakeLists.txt
@@ -63,8 +63,6 @@ set(SOURCES
../src/network/LocalClient.cpp
../src/network/ClientState.h
../src/network/ClientState.cpp
- ../src/network/WebSocketClient.h
- ../src/network/WebSocketClient.cpp
../src/network/WebSocketClientBase.h
../src/network/WebSocketClientBase.cpp
../src/network/WebSocketClientEmscripten.h
@@ -95,10 +93,7 @@ set(ENABLE_COMMONCRYPTO OFF CACHE BOOL "" FORCE)
add_subdirectory("../thirdparty/libzip-1.11.4" libzip-build)
-# Линковка:
-# 'zip' берется из add_subdirectory
-# 'z' - это системный zlib Emscripten-а (флаг -sUSE_ZLIB=1 добавим ниже)
-target_link_libraries(space-game001 PRIVATE zip z websocket)
+target_link_libraries(space-game001 PRIVATE zip z websocket.js)
# Эмскриптен-флаги
set(EMSCRIPTEN_FLAGS
@@ -107,8 +102,8 @@ set(EMSCRIPTEN_FLAGS
"-sUSE_LIBPNG=1"
"-sUSE_ZLIB=1"
"-sUSE_SDL_TTF=2"
- "-pthread"
- "-sUSE_PTHREADS=1"
+ #"-pthread"
+ #"-sUSE_PTHREADS=1"
"-fexceptions"
"-DNETWORK"
)
@@ -120,8 +115,9 @@ target_compile_options(space-game001 PRIVATE ${EMSCRIPTEN_FLAGS} "-O2")
set(EMSCRIPTEN_LINK_FLAGS
${EMSCRIPTEN_FLAGS}
"-O2"
- "-sPTHREAD_POOL_SIZE=4"
+ #"-sPTHREAD_POOL_SIZE=4"
"-sALLOW_MEMORY_GROWTH=1"
+ "-sFULL_ES3=1"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/loading.png@resources/loading.png"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/shaders@resources/shaders"
)
diff --git a/proj-web/README.md b/proj-web/README.md
index 3d0307f..7969215 100644
--- a/proj-web/README.md
+++ b/proj-web/README.md
@@ -3,6 +3,7 @@
Activate the environment:
```
+C:\Work\Projects\emsdk\emsdk.bat install latest
C:\Work\Projects\emsdk\emsdk.bat activate latest
C:\Work\Projects\emsdk\emsdk_env.bat
```
diff --git a/proj-web/space-game001.html b/proj-web/space-game001.html
new file mode 100644
index 0000000..710f6f4
--- /dev/null
+++ b/proj-web/space-game001.html
@@ -0,0 +1,2 @@
+
Emscripten-Generated Code
+![]()
Downloading...
Resize canvas Lock/hide mouse pointer
\ No newline at end of file
diff --git a/server/server.cpp b/server/server.cpp
index db2b78f..2a498b0 100644
--- a/server/server.cpp
+++ b/server/server.cpp
@@ -502,7 +502,7 @@ private:
float len = worldForward.norm();
if (len > 1e-6f) worldForward /= len;
pr.vel = worldForward * velocity;
- pr.lifeMs = 5000.0f;
+ pr.lifeMs = 15000.0f;
g_projectiles.push_back(pr);
std::cout << "Server: Created projectile from player " << id_
@@ -670,9 +670,7 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) {
if (!session->fetchStateAtTime(now, targetState)) continue;
Eigen::Vector3f diff = pr.pos - targetState.position;
- const float shipRadius = 15.0f;
- const float projectileRadius = 1.5f;
- float combinedRadius = shipRadius + projectileRadius;
+ float combinedRadius = shipCollisionRadius + projectileHitRadius;
if (diff.squaredNorm() <= combinedRadius * combinedRadius) {
DeathInfo death;
diff --git a/src/Game.cpp b/src/Game.cpp
index 82c81fd..014c42e 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -58,9 +58,9 @@ namespace ZL
#endif
Game::Game()
- : window(nullptr)
+ : /*window(nullptr)
, glContext(nullptr)
- , newTickCount(0)
+ , */newTickCount(0)
, lastTickCount(0)
, menuManager(renderer)
, space(renderer, taskManager, mainThreadHandler, networkClient, menuManager)
@@ -68,12 +68,13 @@ namespace ZL
}
Game::~Game() {
+ /*
if (glContext) {
SDL_GL_DeleteContext(glContext);
}
if (window) {
SDL_DestroyWindow(window);
- }
+ }*/
#ifndef EMSCRIPTEN
// In Emscripten, SDL must stay alive across context loss/restore cycles
// so the window remains valid when the game object is re-created.
@@ -82,14 +83,15 @@ namespace ZL
}
void Game::setup() {
- glContext = SDL_GL_CreateContext(ZL::Environment::window);
+ //glContext = SDL_GL_CreateContext(ZL::Environment::window);
+ //glContext = in_glContext;
Environment::computeProjectionDimensions();
ZL::BindOpenGlFunctions();
ZL::CheckGlError();
renderer.InitOpenGL();
-
+
#ifdef EMSCRIPTEN
// These shaders and loading.png are preloaded separately (not from zip),
// so they are available immediately without waiting for resources.zip.
@@ -102,19 +104,23 @@ namespace ZL
loadingTexture = std::make_unique(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE));
#endif
- loadingMesh.data = CreateRect2D({ Environment::projectionWidth * 0.5f, Environment::projectionHeight * 0.5f }, { Environment::projectionWidth * 0.5f, Environment::projectionHeight * 0.5f }, 3);
+ loadingMesh.data = CreateRect2D({ 0.5f, 0.5f }, { 0.5f, 0.5f }, 3);
loadingMesh.RefreshVBO();
-
+
#ifdef EMSCRIPTEN
// Asynchronously download resources.zip; setupPart2() is called on completion.
// The loading screen stays visible until the download finishes.
s_instance = this;
+ std::cout << "Load resurces step 1" << std::endl;
emscripten_async_wget("resources.zip", "resources.zip", onResourcesZipLoaded, onResourcesZipError);
#else
mainThreadHandler.EnqueueMainThreadTask([this]() {
+ std::cout << "Load resurces step 2" << std::endl;
this->setupPart2();
+ std::cout << "Load resurces step 3" << std::endl;
});
#endif
+
}
@@ -168,13 +174,19 @@ namespace ZL
networkClient = std::make_unique(taskManager.getIOContext());
networkClient->Connect("localhost", 8081);
#endif
-
+
if (networkClient) {
std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType);
networkClient->Send(joinMsg);
std::cerr << "Sent JOIN: " << joinMsg << std::endl;
}
+ space.boxCoordsArr.clear();
+ space.boxRenderArr.clear();
+ //space.boxLabels.clear();
+ space.boxAlive.clear();
+ space.serverBoxesApplied = false;
+
lastTickCount = 0;
spaceGameStarted = 1;
};
@@ -250,12 +262,12 @@ namespace ZL
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
- float width = Environment::projectionWidth;
- float height = Environment::projectionHeight;
+ //float width = Environment::projectionWidth;
+ //float height = Environment::projectionHeight;
renderer.PushProjectionMatrix(
- 0, width,
- 0, height,
+ 0, 1,
+ 0, 1,
-10, 10);
renderer.PushMatrix();
@@ -276,8 +288,7 @@ namespace ZL
int64_t Game::getSyncTimeMs() {
int64_t localNow = std::chrono::duration_cast(
std::chrono::system_clock::now().time_since_epoch()).count();
- // Добавляем смещение, полученное от сервера
- if (networkClient)
+ if(networkClient)
{
return localNow + networkClient->getTimeOffset();
}
@@ -318,10 +329,10 @@ namespace ZL
}
void Game::render() {
- SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
+ //SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
ZL::CheckGlError();
- glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
drawScene();
@@ -439,14 +450,14 @@ namespace ZL
#endif
}
render();
-
+
if (networkClient) {
-#ifndef NETWORK
+//#ifndef NETWORK
auto localClient = dynamic_cast(networkClient.get());
if (localClient) {
localClient->setLocalPlayerState(Environment::shipState);
}
-#endif
+//#endif
networkClient->Poll();
#ifdef NETWORK
auto* wsBase = dynamic_cast(networkClient.get());
diff --git a/src/Game.h b/src/Game.h
index ab4ab9f..208fa1e 100644
--- a/src/Game.h
+++ b/src/Game.h
@@ -60,8 +60,8 @@ namespace ZL {
static void onResourcesZipError(const char* filename);
#endif
- SDL_Window* window;
- SDL_GLContext glContext;
+ //SDL_Window* window;
+ //SDL_GLContext glContext;
int64_t newTickCount;
int64_t lastTickCount;
diff --git a/src/Space.cpp b/src/Space.cpp
index 4b515e1..4285f90 100644
--- a/src/Space.cpp
+++ b/src/Space.cpp
@@ -1147,67 +1147,112 @@ namespace ZL
Vector3f shooterPos = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0f, 0.9f - 6.0f, 5.0f };
// скорость цели в мире (вектор)
- Vector3f shooterVel = ForwardFromRotation(Environment::shipState.rotation) * Environment::shipState.velocity;
+ // Vector3f shooterVel = ForwardFromRotation(Environment::shipState.rotation) * Environment::shipState.velocity;
+
+ float shooterSpeed = std::abs(Environment::shipState.velocity);
+ // В нашей физике линейная скорость корабля всегда направлена по его forward (-Z)
+ // Когда игрок наводится на lead indicator, forward (и скорость) становятся сонаправлены с выстрелом
+ // поэтому эффективная скорость снаряда в мире ≈ muzzle + shipSpeed.
+ const float effectiveProjectileSpeed = projectileSpeed + shooterSpeed;
+ Vector3f shooterVel = Vector3f::Zero(); // скорость уже учтена в effectiveProjectileSpeed
Vector3f targetVel = ForwardFromRotation(st.rotation) * st.velocity;
+ // ВАЖНО: remote state берется на now_ms - CLIENT_DELAY
+ // Значит shipWorld - это позиция ~0.5 сек назад.
+ // Для корректного lead нужно предсказать положение цели на сейчас
+ const float clientDelaySec = (float)CLIENT_DELAY / 1000.0f;
+ Vector3f targetPosNow = shipWorld + targetVel * clientDelaySec;
+
const float minTargetSpeed = 0.5f; // подобрать (в твоих единицах)
bool targetMoving = (targetVel.norm() > minTargetSpeed);
// альфа круга
float leadAlpha = targetMoving ? 1.0f : 0.5f;
- Vector3f leadWorld = shipWorld;
+ Vector3f leadWorld = targetPosNow;
bool haveLead = false;
- // чтобы круг не улетал далеко: максимум 4 секунды (подстроить под игру)
- float distToTarget = (Environment::shipState.position - shipWorld).norm();
- float maxLeadTime = std::clamp((distToTarget / projectileSpeed) * 1.2f, 0.05f, 4.0f);
+ // Дистанцию лучше считать от реальной точки вылета
+ float distToTarget = (shooterPos - targetPosNow).norm();
+ // Максимальное время перехвата ограничиваем жизнью пули
+ const float projectileLifeSec = (float)PROJECTILE_LIFE / 1000.0f;
+ float maxLeadTime = std::clamp((distToTarget / effectiveProjectileSpeed) * 1.25f, 0.01f, projectileLifeSec * 0.98f);
if (!targetMoving) {
// Цель стоит: рисуем lead прямо на ней, но полупрозрачный
- leadWorld = shipWorld;
+ leadWorld = targetPosNow;
haveLead = true;
}
else {
float tLead = 0.0f;
// 1) Пытаемся “правильное” решение перехвата
- bool ok = SolveLeadInterceptTime(shooterPos, shooterVel, shipWorld, targetVel, projectileSpeed, tLead);
+ bool ok = SolveLeadInterceptTime(shooterPos, shooterVel, targetPosNow, targetVel, effectiveProjectileSpeed, tLead);
// 2) Если решения нет / оно плохое — fallback (чтобы круг не пропадал при пролёте "вбок")
// Это ключевое изменение: lead всегда будет.
if (!ok || !(tLead > 0.0f) || tLead > maxLeadTime) {
- tLead = std::clamp(distToTarget / projectileSpeed, 0.05f, maxLeadTime);
+ tLead = std::clamp(distToTarget / effectiveProjectileSpeed, 0.05f, maxLeadTime);
}
- leadWorld = shipWorld + targetVel * tLead;
+ leadWorld = targetPosNow + targetVel * tLead;
haveLead = true;
}
- // 2) проекция
+ // Проекция цели (для рамок/стрелки)
float ndcX, ndcY, ndcZ, clipW;
if (!projectToNDC(shipWorld, ndcX, ndcY, ndcZ, clipW)) return;
- // behind camera?
bool behind = (clipW <= 0.0f);
- // on-screen check (NDC)
bool onScreen = (!behind &&
ndcX >= -1.0f && ndcX <= 1.0f &&
ndcY >= -1.0f && ndcY <= 1.0f);
- // 3) расстояние
float dist = (Environment::shipState.position - shipWorld).norm();
-
- // time for arrow bob
float t = static_cast(SDL_GetTicks64()) * 0.001f;
- // 4) Настройки стиля
+ // Проекция Lead
+ float leadNdcX = 0.f, leadNdcY = 0.f, leadNdcZ = 0.f, leadClipW = 0.f;
+ bool leadOnScreen = false;
+
+ if (haveLead) {
+ if (projectToNDC(leadWorld, leadNdcX, leadNdcY, leadNdcZ, leadClipW) && leadClipW > 0.0f) {
+ leadOnScreen =
+ (leadNdcX >= -1.0f && leadNdcX <= 1.0f &&
+ leadNdcY >= -1.0f && leadNdcY <= 1.0f);
+ }
+ }
+
+ // Настройки HUD стилизация
Eigen::Vector4f enemyColor(1.f, 0.f, 0.f, 1.f); // красный
float thickness = 2.0f; // толщина линий (px)
float z = 0.0f; // 2D слой
- // 5) Если цель в кадре: рисуем скобки
+ auto drawLeadRing2D = [&](float lx, float ly)
+ {
+ float distLead = (Environment::shipState.position - leadWorld).norm();
+ float r = 30.0f / (distLead * 0.01f + 1.0f);
+ r = std::clamp(r, 6.0f, 18.0f);
+
+ float thicknessPx = 2.5f;
+ float innerR = max(1.0f, r - thicknessPx);
+ float outerR = r + thicknessPx;
+
+ Eigen::Vector4f leadColor = enemyColor;
+ leadColor.w() = leadAlpha;
+
+ renderer.RenderUniform4fv("uColor", leadColor.data());
+ VertexDataStruct ring = MakeRing2D(lx, ly, innerR, outerR, 0.0f, 32, enemyColor);
+ hudTempMesh.AssignFrom(ring);
+ renderer.DrawVertexRenderStruct(hudTempMesh);
+
+ // вернуть цвет HUD обратно
+ renderer.RenderUniform4fv("uColor", enemyColor.data());
+ };
+
+
+ // Цель в кадре: рамки
if (onScreen)
{
// перевод NDC -> экран (в пикселях)
@@ -1238,11 +1283,11 @@ namespace ZL
// рисуем 8 тонких прямоугольников (2 на угол)
auto drawBar = [&](float cx, float cy, float w, float h)
- {
- VertexDataStruct v = MakeColoredRect2D(cx, cy, w * 0.5f, h * 0.5f, z, enemyColor);
- hudTempMesh.AssignFrom(v);
- renderer.DrawVertexRenderStruct(hudTempMesh);
- };
+ {
+ VertexDataStruct v = MakeColoredRect2D(cx, cy, w * 0.5f, h * 0.5f, z, enemyColor);
+ hudTempMesh.AssignFrom(v);
+ renderer.DrawVertexRenderStruct(hudTempMesh);
+ };
// включаем 2D режим
glDisable(GL_DEPTH_TEST);
@@ -1256,40 +1301,9 @@ namespace ZL
renderer.LoadIdentity();
renderer.EnableVertexAttribArray("vPosition");
+ renderer.RenderUniform4fv("uColor", enemyColor.data());
- Eigen::Vector4f hudColor = enemyColor;
- renderer.RenderUniform4fv("uColor", hudColor.data());
-
-
- if (haveLead) {
- float leadNdcX, leadNdcY, leadNdcZ, leadClipW;
- if (projectToNDC(leadWorld, leadNdcX, leadNdcY, leadNdcZ, leadClipW) && leadClipW > 0.0f) {
- if (leadNdcX >= -1 && leadNdcX <= 1 && leadNdcY >= -1 && leadNdcY <= 1) {
- float lx = (leadNdcX * 0.5f + 0.5f) * Environment::projectionWidth;
- float ly = (leadNdcY * 0.5f + 0.5f) * Environment::projectionHeight;
-
- float distLead = (Environment::shipState.position - leadWorld).norm();
- float r = 30.0f / (distLead * 0.01f + 1.0f);
- r = std::clamp(r, 6.0f, 18.0f);
-
- float thicknessPx = 2.5f;
- float innerR = max(1.0f, r - thicknessPx);
- float outerR = r + thicknessPx;
- Eigen::Vector4f leadColor = enemyColor;
- leadColor.w() = leadAlpha;
- renderer.RenderUniform4fv("uColor", leadColor.data());
-
- VertexDataStruct ring = MakeRing2D(lx, ly, innerR, outerR, 0.0f, 32, enemyColor);
- hudTempMesh.AssignFrom(ring);
- renderer.DrawVertexRenderStruct(hudTempMesh);
-
- renderer.RenderUniform4fv("uColor", hudColor.data());
- }
- }
- }
-
- renderer.EnableVertexAttribArray("vPosition");
-
+ // рамки
drawBar(left + cornerLen * 0.5f, top, cornerLen, thickness);
drawBar(left, top - cornerLen * 0.5f, thickness, cornerLen);
@@ -1302,9 +1316,14 @@ namespace ZL
drawBar(right - cornerLen * 0.5f, bottom, cornerLen, thickness);
drawBar(right, bottom + cornerLen * 0.5f, thickness, cornerLen);
+ // LEAD — независимо от рамок: если его точка на экране, рисуем
+ if (haveLead && leadOnScreen) {
+ float lx = (leadNdcX * 0.5f + 0.5f) * Environment::projectionWidth;
+ float ly = (leadNdcY * 0.5f + 0.5f) * Environment::projectionHeight;
+ drawLeadRing2D(lx, ly);
+ }
+
renderer.DisableVertexAttribArray("vPosition");
-
-
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
@@ -1316,6 +1335,8 @@ namespace ZL
return;
}
+
+ // Цель вне экрана: стрелка
float dirX = ndcX;
float dirY = ndcY;
@@ -1358,26 +1379,26 @@ namespace ZL
Vector3f right{ edgeX - px * (arrowWid * 0.5f), edgeY - py * (arrowWid * 0.5f), z };
auto drawTri = [&](const Vector3f& a, const Vector3f& b, const Vector3f& c)
- {
- VertexDataStruct v;
- v.PositionData = { a, b, c };
- Vector3f rgb{ enemyColor.x(), enemyColor.y(), enemyColor.z() };
- v.ColorData = { rgb, rgb, rgb };
- // defaultColor vertex shader expects vNormal and vTexCoord (avoids NaN on WebGL).
- const Vector3f n{ 0.f, 0.f, 1.f };
- v.NormalData = { n, n, n };
- const Vector2f uv{ 0.f, 0.f };
- v.TexCoordData = { uv, uv, uv };
- hudTempMesh.AssignFrom(v);
- renderer.DrawVertexRenderStruct(hudTempMesh);
- };
+ {
+ VertexDataStruct v;
+ v.PositionData = { a, b, c };
+ Vector3f rgb{ enemyColor.x(), enemyColor.y(), enemyColor.z() };
+ v.ColorData = { rgb, rgb, rgb };
+ // defaultColor vertex shader expects vNormal and vTexCoord (avoids NaN on WebGL).
+ const Vector3f n{ 0.f, 0.f, 1.f };
+ v.NormalData = { n, n, n };
+ const Vector2f uv{ 0.f, 0.f };
+ v.TexCoordData = { uv, uv, uv };
+ hudTempMesh.AssignFrom(v);
+ renderer.DrawVertexRenderStruct(hudTempMesh);
+ };
auto drawBar = [&](float cx, float cy, float w, float h)
- {
- VertexDataStruct v = MakeColoredRect2D(cx, cy, w * 0.5f, h * 0.5f, z, enemyColor);
- hudTempMesh.AssignFrom(v);
- renderer.DrawVertexRenderStruct(hudTempMesh);
- };
+ {
+ VertexDataStruct v = MakeColoredRect2D(cx, cy, w * 0.5f, h * 0.5f, z, enemyColor);
+ hudTempMesh.AssignFrom(v);
+ renderer.DrawVertexRenderStruct(hudTempMesh);
+ };
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
@@ -1388,18 +1409,30 @@ namespace ZL
renderer.PushMatrix();
renderer.LoadIdentity();
+ renderer.EnableVertexAttribArray("vPosition");
+ renderer.RenderUniform4fv("uColor", enemyColor.data());
+
+ // стрелка
drawTri(tip, left, right);
float tailLen = 14.0f;
float tailX = edgeX - dirX * 6.0f;
float tailY = edgeY - dirY * 6.0f;
-
drawBar(tailX, tailY, max(thickness, tailLen), thickness);
+ // LEAD — рисуем даже когда цель вне экрана (если lead точка на экране)
+ if (haveLead && leadOnScreen) {
+ float lx = (leadNdcX * 0.5f + 0.5f) * Environment::projectionWidth;
+ float ly = (leadNdcY * 0.5f + 0.5f) * Environment::projectionHeight;
+ drawLeadRing2D(lx, ly);
+ }
+
+ renderer.DisableVertexAttribArray("vPosition");
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
+ // дистанция около стрелки
{
std::string d = std::to_string((int)dist) + "m";
float tx = edgeX + px * 18.0f;
@@ -1425,7 +1458,7 @@ namespace ZL
firePressed = false;
if (now_ms - lastProjectileFireTime >= static_cast(projectileCooldownMs)) {
lastProjectileFireTime = now_ms;
- const float projectileSpeed = 250.0f;
+ const float projectileSpeed = PROJECTILE_VELOCITY;
this->fireProjectiles();
@@ -1433,7 +1466,7 @@ namespace ZL
Eigen::Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized();
Eigen::Vector3f centerPos = Environment::shipState.position +
- Environment::shipState.rotation * Vector3f{ 0, 0.9f, 5.0f };
+ Environment::shipState.rotation * Vector3f{ 0, 0.9f - 6.0f, 5.0f };
Eigen::Quaternionf q(Environment::shipState.rotation);
float speedToSend = projectileSpeed + Environment::shipState.velocity;
@@ -1747,8 +1780,8 @@ namespace ZL
const float size = 0.5f;
for (const auto& pi : pending) {
const std::vector localOffsets = {
- Vector3f{ -1.5f, 0.9f, 5.0f },
- Vector3f{ 1.5f, 0.9f, 5.0f }
+ Vector3f{ -1.5f, 0.9f - 6.0f, 5.0f },
+ Vector3f{ 1.5f, 0.9f - 6.0f, 5.0f }
};
Vector3f localForward = { 0, 0, -1 };
diff --git a/src/main.cpp b/src/main.cpp
index 6b62092..dd7224b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -16,37 +16,47 @@
// For Android and Desktop a plain global value is used (no context loss).
#ifdef EMSCRIPTEN
ZL::Game* g_game = nullptr;
+//static SDL_Window* win_x = nullptr;
+static SDL_GLContext glContext_x; // Не указатель, а сам объект (который суть void*)
+
#else
ZL::Game game;
#endif
-void MainLoop() {
#ifdef EMSCRIPTEN
- if (g_game) g_game->update();
-#else
- game.update();
-#endif
+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();
}
+#else
+void MainLoop() {
+ game.update();
+}
+#endif
#ifdef EMSCRIPTEN
EM_BOOL onWebGLContextLost(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) {
- delete g_game;
- g_game = nullptr;
+ //delete g_game;
+ //g_game = nullptr;
return EM_TRUE;
}
EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) {
- g_game = new ZL::Game();
- g_game->setup();
+ //g_game = new ZL::Game();
+ //g_game->setup();
return EM_TRUE;
}
static void applyResize(int logicalW, int logicalH) {
// Получаем коэффициент плотности пикселей (например, 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
// Вычисляем реальные физические пиксели
int physicalW = static_cast(logicalW * dpr);
@@ -91,6 +101,44 @@ EM_BOOL onFullscreenChanged(int /*eventType*/, const EmscriptenFullscreenChangeE
return EM_FALSE;
}
+int main(int argc, char* argv[]) {
+ SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
+
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); // Для WebGL 2.0
+
+ ZL::Environment::window = SDL_CreateWindow("Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
+ glContext_x = SDL_GL_CreateContext(ZL::Environment::window);
+
+ SDL_GL_MakeCurrent(ZL::Environment::window, glContext_x);
+
+ g_game = new ZL::Game();
+ g_game->setup();
+
+ 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);
+
+ SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
+
+ emscripten_set_main_loop(MainLoop, 0, 1);
+
+
+ return 0;
+}
+
#endif
@@ -178,7 +226,9 @@ extern "C" int SDL_main(int argc, char* argv[]) {
}
-#else
+#endif
+
+#ifdef WIN32_LEAN_AND_MEAN
int main(int argc, char *argv[]) {
try
@@ -192,7 +242,7 @@ int main(int argc, char *argv[]) {
ZL::Environment::height = CONST_HEIGHT;
-#ifdef EMSCRIPTEN
+/*#ifdef EMSCRIPTEN
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
return 1;
@@ -251,7 +301,7 @@ int main(int argc, char *argv[]) {
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
emscripten_set_main_loop(MainLoop, 0, 1);
-#else
+#else*/
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
SDL_Log("SDL init failed: %s", SDL_GetError());
return 1;
@@ -277,7 +327,7 @@ int main(int argc, char *argv[]) {
game.update();
SDL_Delay(2);
}
-#endif
+//#endif
}
catch (const std::exception& e)
diff --git a/src/network/LocalClient.cpp b/src/network/LocalClient.cpp
index 1d43a78..4125593 100644
--- a/src/network/LocalClient.cpp
+++ b/src/network/LocalClient.cpp
@@ -418,11 +418,13 @@ namespace ZL {
pinfo.position = pr.pos;
pinfo.rotation = dir.toRotationMatrix();
pinfo.velocity = velocity;
- pendingProjectiles.push_back(pinfo);
- std::cout << "LocalClient: Created projectile at pos (" << shotPos.x() << ", "
+ if (pinfo.shooterId != GetClientId()) {
+ pendingProjectiles.push_back(pinfo);
+ }
+ std::cout << "LocalClient: Created server projectile at pos (" << shotPos.x() << ", "
<< shotPos.y() << ", " << shotPos.z() << ") vel (" << pr.vel.x() << ", "
- << pr.vel.y() << ", " << pr.vel.z() << ")" << std::endl;
+ << pr.vel.y() << ", " << pr.vel.z() << ") shooter=" << pr.shooterId << std::endl;
}
}
}
diff --git a/src/network/WebSocketClientEmscripten.cpp b/src/network/WebSocketClientEmscripten.cpp
index 9534cf0..e879a89 100644
--- a/src/network/WebSocketClientEmscripten.cpp
+++ b/src/network/WebSocketClientEmscripten.cpp
@@ -7,8 +7,8 @@
namespace ZL {
void WebSocketClientEmscripten::Connect(const std::string& host, uint16_t port) {
// Формируем URL. Обратите внимание, что в Web часто лучше использовать ws://localhost
- std::string url = "ws://" + host + ":" + std::to_string(port);
- //std::string url = "wss://api.spacegame.fishrungames.com";
+ //std::string url = "ws://" + host + ":" + std::to_string(port);
+ std::string url = "wss://api.spacegame.fishrungames.com";
EmscriptenWebSocketCreateAttributes attr = {
url.c_str(),
@@ -22,13 +22,32 @@ namespace ZL {
emscripten_websocket_set_onmessage_callback(socket_, this, onMessage);
emscripten_websocket_set_onerror_callback(socket_, this, onError);
emscripten_websocket_set_onclose_callback(socket_, this, onClose);
+
+ connected = false;
+ }
+
+ void WebSocketClientEmscripten::flushOutgoingQueue() {
+ std::lock_guard lock(outgoingMutex);
+ if (!socket_) return;
+ while (!outgoingQueue.empty()) {
+ const std::string &m = outgoingQueue.front();
+ emscripten_websocket_send_utf8_text(socket_, m.c_str());
+ outgoingQueue.pop();
+ }
}
void WebSocketClientEmscripten::Send(const std::string& message) {
- if (connected && socket_ > 0) {
- auto signedMsg = SignMessage(message);
- std::cout << "[WebWS] Sending message: " << signedMsg << std::endl;
- emscripten_websocket_send_utf8_text(socket_, signedMsg.c_str());
+ std::string signedMsg = SignMessage(message);
+
+ {
+ std::lock_guard lock(outgoingMutex);
+ if (connected && socket_ > 0) {
+ std::cout << "[WebWS] Sending message (immediate): " << signedMsg << std::endl;
+ emscripten_websocket_send_utf8_text(socket_, signedMsg.c_str());
+ return;
+ }
+ outgoingQueue.push(signedMsg);
+ std::cout << "[WebWS] Queued outgoing message (waiting for open): " << signedMsg << std::endl;
}
}
@@ -36,19 +55,16 @@ namespace ZL {
// Локальная очередь для минимизации времени блокировки мьютекса
std::queue localQueue;
- {
- std::lock_guard lock(queueMutex);
- if (messageQueue.empty()) return;
- std::swap(localQueue, messageQueue);
- }
+ if (messageQueue.empty()) return;
+ std::swap(localQueue, messageQueue);
while (!localQueue.empty()) {
const std::string& msg = localQueue.front();
- std::cout << "[WebWS] Processing message: " << msg << std::endl;
+ std::cout << "[WebWS] Processing message: " << msg << std::endl;
// Передаем в базовый класс для парсинга игровых событий (BOXES, UPD, и т.д.)
HandlePollMessage(msg);
-
+
localQueue.pop();
}
}
@@ -59,6 +75,9 @@ namespace ZL {
auto* self = static_cast(userData);
self->connected = true;
std::cout << "[WebWS] Connection opened" << std::endl;
+
+ self->flushOutgoingQueue();
+
return EM_TRUE;
}
@@ -67,7 +86,6 @@ namespace ZL {
auto* self = static_cast(userData);
if (e->isText && e->data) {
std::string msg(reinterpret_cast(e->data), e->numBytes);
- std::lock_guard lock(self->queueMutex);
self->messageQueue.push(msg);
}
return EM_TRUE;
diff --git a/src/network/WebSocketClientEmscripten.h b/src/network/WebSocketClientEmscripten.h
index 2aac059..a6467ba 100644
--- a/src/network/WebSocketClientEmscripten.h
+++ b/src/network/WebSocketClientEmscripten.h
@@ -18,7 +18,10 @@ namespace ZL {
// Очередь для хранения сырых строк от браузера
std::queue messageQueue;
- std::mutex queueMutex;
+
+ std::queue outgoingQueue;
+ std::mutex outgoingMutex;
+ void flushOutgoingQueue();
public:
WebSocketClientEmscripten() = default;
diff --git a/src/utils/TaskManager.cpp b/src/utils/TaskManager.cpp
index aef62a1..c4fe7f1 100644
--- a/src/utils/TaskManager.cpp
+++ b/src/utils/TaskManager.cpp
@@ -5,35 +5,52 @@ namespace ZL
{
TaskManager::TaskManager(size_t threadCount) {
- workGuard = std::make_unique>(ioContext.get_executor());
+
+#ifndef EMSCRIPTEN
+ workGuard = std::make_unique>(ioContext.get_executor());
for (size_t i = 0; i < threadCount; ++i) {
workers.emplace_back([this]() {
ioContext.run();
});
}
+#endif
}
void TaskManager::EnqueueBackgroundTask(std::function task) {
+#ifdef EMSCRIPTEN
+ task();
+#else
boost::asio::post(ioContext, task);
+#endif
}
TaskManager::~TaskManager() {
+#ifndef EMSCRIPTEN
workGuard.reset(); // ioContext.run() ,
ioContext.stop(); // :
for (auto& t : workers) {
if (t.joinable()) t.join();
}
+#endif
}
void MainThreadHandler::EnqueueMainThreadTask(std::function task) {
+#ifndef EMSCRIPTEN
std::lock_guard lock(mainThreadMutex);
+#endif
mainThreadTasks.push(task);
}
void MainThreadHandler::processMainThreadTasks() {
std::function task;
+#ifdef EMSCRIPTEN
+ if (!mainThreadTasks.empty()) {
+ task = std::move(mainThreadTasks.front());
+ mainThreadTasks.pop();
+ }
+#else
// , update
{
std::lock_guard lock(mainThreadMutex);
@@ -42,6 +59,7 @@ namespace ZL
mainThreadTasks.pop();
}
}
+#endif
if (task) {
task(); // RefreshVBO
diff --git a/src/utils/TaskManager.h b/src/utils/TaskManager.h
index 66423f8..afb18df 100644
--- a/src/utils/TaskManager.h
+++ b/src/utils/TaskManager.h
@@ -1,8 +1,12 @@
#pragma once
+
+#ifndef EMSCRIPTEN
#include
+#include
+#endif
+
#include
#include
-#include
#include
#include
@@ -11,12 +15,12 @@ namespace ZL {
class TaskManager {
private:
+#ifndef EMSCRIPTEN
boost::asio::io_context ioContext;
std::unique_ptr> workGuard;
std::vector workers;
-
+#endif
public:
- //TaskManager(size_t threadCount = std::thread::hardware_concurrency());
TaskManager(size_t threadCount = 2);
//
@@ -24,11 +28,13 @@ namespace ZL {
// Graceful shutdown
~TaskManager();
-
+
+#ifndef EMSCRIPTEN
boost::asio::io_context& getIOContext()
{
return ioContext;
}
+#endif
};
@@ -36,8 +42,10 @@ namespace ZL {
{
private:
std::queue> mainThreadTasks;
- std::mutex mainThreadMutex;
+#ifndef EMSCRIPTEN
+ std::mutex mainThreadMutex;
+#endif
public:
void EnqueueMainThreadTask(std::function task);