Merge branch 'main' into linux
This commit is contained in:
commit
2884865b53
@ -63,8 +63,6 @@ set(SOURCES
|
|||||||
../src/network/LocalClient.cpp
|
../src/network/LocalClient.cpp
|
||||||
../src/network/ClientState.h
|
../src/network/ClientState.h
|
||||||
../src/network/ClientState.cpp
|
../src/network/ClientState.cpp
|
||||||
../src/network/WebSocketClient.h
|
|
||||||
../src/network/WebSocketClient.cpp
|
|
||||||
../src/network/WebSocketClientBase.h
|
../src/network/WebSocketClientBase.h
|
||||||
../src/network/WebSocketClientBase.cpp
|
../src/network/WebSocketClientBase.cpp
|
||||||
../src/network/WebSocketClientEmscripten.h
|
../src/network/WebSocketClientEmscripten.h
|
||||||
@ -95,10 +93,7 @@ set(ENABLE_COMMONCRYPTO OFF CACHE BOOL "" FORCE)
|
|||||||
|
|
||||||
add_subdirectory("../thirdparty/libzip-1.11.4" libzip-build)
|
add_subdirectory("../thirdparty/libzip-1.11.4" libzip-build)
|
||||||
|
|
||||||
# Линковка:
|
target_link_libraries(space-game001 PRIVATE zip z websocket.js)
|
||||||
# 'zip' берется из add_subdirectory
|
|
||||||
# 'z' - это системный zlib Emscripten-а (флаг -sUSE_ZLIB=1 добавим ниже)
|
|
||||||
target_link_libraries(space-game001 PRIVATE zip z websocket)
|
|
||||||
|
|
||||||
# Эмскриптен-флаги
|
# Эмскриптен-флаги
|
||||||
set(EMSCRIPTEN_FLAGS
|
set(EMSCRIPTEN_FLAGS
|
||||||
@ -107,8 +102,8 @@ set(EMSCRIPTEN_FLAGS
|
|||||||
"-sUSE_LIBPNG=1"
|
"-sUSE_LIBPNG=1"
|
||||||
"-sUSE_ZLIB=1"
|
"-sUSE_ZLIB=1"
|
||||||
"-sUSE_SDL_TTF=2"
|
"-sUSE_SDL_TTF=2"
|
||||||
"-pthread"
|
#"-pthread"
|
||||||
"-sUSE_PTHREADS=1"
|
#"-sUSE_PTHREADS=1"
|
||||||
"-fexceptions"
|
"-fexceptions"
|
||||||
"-DNETWORK"
|
"-DNETWORK"
|
||||||
)
|
)
|
||||||
@ -120,8 +115,9 @@ target_compile_options(space-game001 PRIVATE ${EMSCRIPTEN_FLAGS} "-O2")
|
|||||||
set(EMSCRIPTEN_LINK_FLAGS
|
set(EMSCRIPTEN_LINK_FLAGS
|
||||||
${EMSCRIPTEN_FLAGS}
|
${EMSCRIPTEN_FLAGS}
|
||||||
"-O2"
|
"-O2"
|
||||||
"-sPTHREAD_POOL_SIZE=4"
|
#"-sPTHREAD_POOL_SIZE=4"
|
||||||
"-sALLOW_MEMORY_GROWTH=1"
|
"-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/loading.png@resources/loading.png"
|
||||||
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/shaders@resources/shaders"
|
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/shaders@resources/shaders"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
Activate the environment:
|
Activate the environment:
|
||||||
```
|
```
|
||||||
|
C:\Work\Projects\emsdk\emsdk.bat install latest
|
||||||
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
|
||||||
```
|
```
|
||||||
|
|||||||
2
proj-web/space-game001.html
Normal file
2
proj-web/space-game001.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<!doctypehtml><html lang=en-us><head><meta charset=utf-8><meta content="text/html; charset=utf-8"http-equiv=Content-Type><title>Emscripten-Generated Code</title><style>body{font-family:arial;margin:0;padding:none}.emscripten{padding-right:0;margin-left:auto;margin-right:auto;display:block}div.emscripten{text-align:center}div.emscripten_border{border:1px solid #000}canvas.emscripten{border:0 none;background-color:#000}#emscripten_logo{display:inline-block;margin:0;padding:6px;width:265px}.spinner{height:30px;width:30px;margin:0;margin-top:20px;margin-left:20px;display:inline-block;vertical-align:top;-webkit-animation:rotation .8s linear infinite;-moz-animation:rotation .8s linear infinite;-o-animation:rotation .8s linear infinite;animation:rotation .8s linear infinite;border-left:5px solid #ebebeb;border-right:5px solid #ebebeb;border-bottom:5px solid #ebebeb;border-top:5px solid #787878;border-radius:100%;background-color:#bdd72e}@-webkit-keyframes rotation{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(360deg)}}@-moz-keyframes rotation{from{-moz-transform:rotate(0)}to{-moz-transform:rotate(360deg)}}@-o-keyframes rotation{from{-o-transform:rotate(0)}to{-o-transform:rotate(360deg)}}@keyframes rotation{from{transform:rotate(0)}to{transform:rotate(360deg)}}#status{display:inline-block;vertical-align:top;margin-top:30px;margin-left:20px;font-weight:700;color:#787878}#progress{height:20px;width:300px}#controls{display:inline-block;float:right;vertical-align:top;margin-top:30px;margin-right:20px}#output{width:100%;height:200px;margin:0 auto;margin-top:10px;border-left:0;border-right:0px;padding-left:0;padding-right:0;display:block;background-color:#000;color:#fff;font-family:'Lucida Console',Monaco,monospace;outline:0}</style></head><body><script src="https://cdn.jsdelivr.net/npm/eruda"></script>
|
||||||
|
<script>eruda.init();</script><a href=http://emscripten.org><img id=emscripten_logo src=""></a><div class=spinner id=spinner></div><div class=emscripten id=status>Downloading...</div><span id=controls><span><input type=checkbox id=resize>Resize canvas</span> <span><input type=checkbox id=pointerLock checked>Lock/hide mouse pointer </span><span><input type=button onclick='Module.requestFullscreen(document.getElementById("pointerLock").checked,document.getElementById("resize").checked)'value=Fullscreen></span></span><div class=emscripten><progress hidden id=progress max=100 value=0></progress></div><div class=emscripten_border><canvas class=emscripten id=canvas oncontextmenu=event.preventDefault() tabindex=-1></canvas></div><textarea id=output rows=8></textarea><script>var statusElement=document.getElementById("status"),progressElement=document.getElementById("progress"),spinnerElement=document.getElementById("spinner"),canvasElement=document.getElementById("canvas"),outputElement=document.getElementById("output");outputElement&&(outputElement.value=""),canvasElement.addEventListener("webglcontextlost",(e=>{alert("WebGL context lost. You will need to reload the page."),e.preventDefault()}),!1);var Module={print(...e){if(console.log(...e),outputElement){var t=e.join(" ");outputElement.value+=t+"\n",outputElement.scrollTop=outputElement.scrollHeight}},canvas:canvasElement,setStatus(e){if(Module.setStatus.last||(Module.setStatus.last={time:Date.now(),text:""}),e!==Module.setStatus.last.text){var t=e.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/),n=Date.now();t&&n-Module.setStatus.last.time<30||(Module.setStatus.last.time=n,Module.setStatus.last.text=e,t?(e=t[1],progressElement.value=100*parseInt(t[2]),progressElement.max=100*parseInt(t[4]),progressElement.hidden=!1,spinnerElement.hidden=!1):(progressElement.value=null,progressElement.max=null,progressElement.hidden=!0,e||(spinnerElement.style.display="none")),statusElement.innerHTML=e)}},totalDependencies:0,monitorRunDependencies(e){this.totalDependencies=Math.max(this.totalDependencies,e),Module.setStatus(e?"Preparing... ("+(this.totalDependencies-e)+"/"+this.totalDependencies+")":"All downloads complete.")}};Module.setStatus("Downloading..."),window.onerror=e=>{Module.setStatus("Exception thrown, see JavaScript console"),spinnerElement.style.display="none",Module.setStatus=e=>{e&&console.error("[post-exception status] "+e)}}</script><script async src="space-game001.js" crossorigin="anonymous"></script></body></html>
|
||||||
@ -423,7 +423,7 @@ private:
|
|||||||
float len = worldForward.norm();
|
float len = worldForward.norm();
|
||||||
if (len > 1e-6f) worldForward /= len;
|
if (len > 1e-6f) worldForward /= len;
|
||||||
pr.vel = worldForward * velocity;
|
pr.vel = worldForward * velocity;
|
||||||
pr.lifeMs = 5000.0f;
|
pr.lifeMs = 15000.0f;
|
||||||
g_projectiles.push_back(pr);
|
g_projectiles.push_back(pr);
|
||||||
|
|
||||||
std::cout << "Server: Created projectile from player " << id_
|
std::cout << "Server: Created projectile from player " << id_
|
||||||
@ -526,9 +526,7 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) {
|
|||||||
if (!session->fetchStateAtTime(now, targetState)) continue;
|
if (!session->fetchStateAtTime(now, targetState)) continue;
|
||||||
|
|
||||||
Eigen::Vector3f diff = pr.pos - targetState.position;
|
Eigen::Vector3f diff = pr.pos - targetState.position;
|
||||||
const float shipRadius = 15.0f;
|
float combinedRadius = shipCollisionRadius + projectileHitRadius;
|
||||||
const float projectileRadius = 1.5f;
|
|
||||||
float combinedRadius = shipRadius + projectileRadius;
|
|
||||||
|
|
||||||
if (diff.squaredNorm() <= combinedRadius * combinedRadius) {
|
if (diff.squaredNorm() <= combinedRadius * combinedRadius) {
|
||||||
DeathInfo death;
|
DeathInfo death;
|
||||||
|
|||||||
43
src/Game.cpp
43
src/Game.cpp
@ -57,9 +57,9 @@ namespace ZL
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
Game::Game()
|
Game::Game()
|
||||||
: window(nullptr)
|
: /*window(nullptr)
|
||||||
, glContext(nullptr)
|
, glContext(nullptr)
|
||||||
, newTickCount(0)
|
, */newTickCount(0)
|
||||||
, lastTickCount(0)
|
, lastTickCount(0)
|
||||||
, menuManager(renderer)
|
, menuManager(renderer)
|
||||||
, space(renderer, taskManager, mainThreadHandler, networkClient, menuManager)
|
, space(renderer, taskManager, mainThreadHandler, networkClient, menuManager)
|
||||||
@ -67,12 +67,13 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
|
|
||||||
Game::~Game() {
|
Game::~Game() {
|
||||||
|
/*
|
||||||
if (glContext) {
|
if (glContext) {
|
||||||
SDL_GL_DeleteContext(glContext);
|
SDL_GL_DeleteContext(glContext);
|
||||||
}
|
}
|
||||||
if (window) {
|
if (window) {
|
||||||
SDL_DestroyWindow(window);
|
SDL_DestroyWindow(window);
|
||||||
}
|
}*/
|
||||||
#ifndef EMSCRIPTEN
|
#ifndef EMSCRIPTEN
|
||||||
// In Emscripten, SDL must stay alive across context loss/restore cycles
|
// In Emscripten, SDL must stay alive across context loss/restore cycles
|
||||||
// so the window remains valid when the game object is re-created.
|
// so the window remains valid when the game object is re-created.
|
||||||
@ -81,7 +82,8 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Game::setup() {
|
void Game::setup() {
|
||||||
glContext = SDL_GL_CreateContext(ZL::Environment::window);
|
//glContext = SDL_GL_CreateContext(ZL::Environment::window);
|
||||||
|
//glContext = in_glContext;
|
||||||
|
|
||||||
Environment::computeProjectionDimensions();
|
Environment::computeProjectionDimensions();
|
||||||
|
|
||||||
@ -101,19 +103,23 @@ namespace ZL
|
|||||||
loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE));
|
loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
loadingMesh.data = CreateRect2D({ 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();
|
loadingMesh.RefreshVBO();
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
// Asynchronously download resources.zip; setupPart2() is called on completion.
|
// Asynchronously download resources.zip; setupPart2() is called on completion.
|
||||||
// The loading screen stays visible until the download finishes.
|
// The loading screen stays visible until the download finishes.
|
||||||
s_instance = this;
|
s_instance = this;
|
||||||
|
std::cout << "Load resurces step 1" << std::endl;
|
||||||
emscripten_async_wget("resources.zip", "resources.zip", onResourcesZipLoaded, onResourcesZipError);
|
emscripten_async_wget("resources.zip", "resources.zip", onResourcesZipLoaded, onResourcesZipError);
|
||||||
#else
|
#else
|
||||||
mainThreadHandler.EnqueueMainThreadTask([this]() {
|
mainThreadHandler.EnqueueMainThreadTask([this]() {
|
||||||
|
std::cout << "Load resurces step 2" << std::endl;
|
||||||
this->setupPart2();
|
this->setupPart2();
|
||||||
|
std::cout << "Load resurces step 3" << std::endl;
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -164,8 +170,8 @@ namespace ZL
|
|||||||
networkClient = std::make_unique<WebSocketClientEmscripten>();
|
networkClient = std::make_unique<WebSocketClientEmscripten>();
|
||||||
networkClient->Connect("localhost", 8081);
|
networkClient->Connect("localhost", 8081);
|
||||||
#else
|
#else
|
||||||
networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
|
//networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
|
||||||
networkClient->Connect("localhost", 8081);
|
//networkClient->Connect("localhost", 8081);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (networkClient) {
|
if (networkClient) {
|
||||||
@ -174,6 +180,12 @@ namespace ZL
|
|||||||
std::cerr << "Sent JOIN: " << joinMsg << std::endl;
|
std::cerr << "Sent JOIN: " << joinMsg << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
space.boxCoordsArr.clear();
|
||||||
|
space.boxRenderArr.clear();
|
||||||
|
//space.boxLabels.clear();
|
||||||
|
space.boxAlive.clear();
|
||||||
|
space.serverBoxesApplied = false;
|
||||||
|
|
||||||
lastTickCount = 0;
|
lastTickCount = 0;
|
||||||
spaceGameStarted = 1;
|
spaceGameStarted = 1;
|
||||||
};
|
};
|
||||||
@ -249,12 +261,12 @@ namespace ZL
|
|||||||
renderer.EnableVertexAttribArray(vPositionName);
|
renderer.EnableVertexAttribArray(vPositionName);
|
||||||
renderer.EnableVertexAttribArray(vTexCoordName);
|
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||||
|
|
||||||
float width = Environment::projectionWidth;
|
//float width = Environment::projectionWidth;
|
||||||
float height = Environment::projectionHeight;
|
//float height = Environment::projectionHeight;
|
||||||
|
|
||||||
renderer.PushProjectionMatrix(
|
renderer.PushProjectionMatrix(
|
||||||
0, width,
|
0, 1,
|
||||||
0, height,
|
0, 1,
|
||||||
-10, 10);
|
-10, 10);
|
||||||
|
|
||||||
renderer.PushMatrix();
|
renderer.PushMatrix();
|
||||||
@ -275,7 +287,6 @@ namespace ZL
|
|||||||
int64_t Game::getSyncTimeMs() {
|
int64_t Game::getSyncTimeMs() {
|
||||||
int64_t localNow = std::chrono::duration_cast<std::chrono::milliseconds>(
|
int64_t localNow = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
// Добавляем смещение, полученное от сервера
|
|
||||||
if(networkClient)
|
if(networkClient)
|
||||||
{
|
{
|
||||||
return localNow + networkClient->getTimeOffset();
|
return localNow + networkClient->getTimeOffset();
|
||||||
@ -317,10 +328,10 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Game::render() {
|
void Game::render() {
|
||||||
SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
|
//SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
|
||||||
ZL::CheckGlError();
|
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);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
drawScene();
|
drawScene();
|
||||||
@ -440,12 +451,12 @@ namespace ZL
|
|||||||
render();
|
render();
|
||||||
|
|
||||||
if (networkClient) {
|
if (networkClient) {
|
||||||
#ifndef NETWORK
|
//#ifndef NETWORK
|
||||||
auto localClient = dynamic_cast<ZL::LocalClient*>(networkClient.get());
|
auto localClient = dynamic_cast<ZL::LocalClient*>(networkClient.get());
|
||||||
if (localClient) {
|
if (localClient) {
|
||||||
localClient->setLocalPlayerState(Environment::shipState);
|
localClient->setLocalPlayerState(Environment::shipState);
|
||||||
}
|
}
|
||||||
#endif
|
//#endif
|
||||||
networkClient->Poll();
|
networkClient->Poll();
|
||||||
}
|
}
|
||||||
mainThreadHandler.processMainThreadTasks();
|
mainThreadHandler.processMainThreadTasks();
|
||||||
|
|||||||
@ -60,8 +60,8 @@ namespace ZL {
|
|||||||
static void onResourcesZipError(const char* filename);
|
static void onResourcesZipError(const char* filename);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SDL_Window* window;
|
//SDL_Window* window;
|
||||||
SDL_GLContext glContext;
|
//SDL_GLContext glContext;
|
||||||
|
|
||||||
int64_t newTickCount;
|
int64_t newTickCount;
|
||||||
int64_t lastTickCount;
|
int64_t lastTickCount;
|
||||||
|
|||||||
147
src/Space.cpp
147
src/Space.cpp
@ -1143,67 +1143,112 @@ namespace ZL
|
|||||||
Vector3f shooterPos = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0f, 0.9f - 6.0f, 5.0f };
|
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;
|
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; // подобрать (в твоих единицах)
|
const float minTargetSpeed = 0.5f; // подобрать (в твоих единицах)
|
||||||
bool targetMoving = (targetVel.norm() > minTargetSpeed);
|
bool targetMoving = (targetVel.norm() > minTargetSpeed);
|
||||||
|
|
||||||
// альфа круга
|
// альфа круга
|
||||||
float leadAlpha = targetMoving ? 1.0f : 0.5f;
|
float leadAlpha = targetMoving ? 1.0f : 0.5f;
|
||||||
|
|
||||||
Vector3f leadWorld = shipWorld;
|
Vector3f leadWorld = targetPosNow;
|
||||||
bool haveLead = false;
|
bool haveLead = false;
|
||||||
|
|
||||||
// чтобы круг не улетал далеко: максимум 4 секунды (подстроить под игру)
|
// Дистанцию лучше считать от реальной точки вылета
|
||||||
float distToTarget = (Environment::shipState.position - shipWorld).norm();
|
float distToTarget = (shooterPos - targetPosNow).norm();
|
||||||
float maxLeadTime = std::clamp((distToTarget / projectileSpeed) * 1.2f, 0.05f, 4.0f);
|
// Максимальное время перехвата ограничиваем жизнью пули
|
||||||
|
const float projectileLifeSec = (float)PROJECTILE_LIFE / 1000.0f;
|
||||||
|
float maxLeadTime = std::clamp((distToTarget / effectiveProjectileSpeed) * 1.25f, 0.01f, projectileLifeSec * 0.98f);
|
||||||
|
|
||||||
if (!targetMoving) {
|
if (!targetMoving) {
|
||||||
// Цель стоит: рисуем lead прямо на ней, но полупрозрачный
|
// Цель стоит: рисуем lead прямо на ней, но полупрозрачный
|
||||||
leadWorld = shipWorld;
|
leadWorld = targetPosNow;
|
||||||
haveLead = true;
|
haveLead = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
float tLead = 0.0f;
|
float tLead = 0.0f;
|
||||||
|
|
||||||
// 1) Пытаемся “правильное” решение перехвата
|
// 1) Пытаемся “правильное” решение перехвата
|
||||||
bool ok = SolveLeadInterceptTime(shooterPos, shooterVel, shipWorld, targetVel, projectileSpeed, tLead);
|
bool ok = SolveLeadInterceptTime(shooterPos, shooterVel, targetPosNow, targetVel, effectiveProjectileSpeed, tLead);
|
||||||
|
|
||||||
// 2) Если решения нет / оно плохое — fallback (чтобы круг не пропадал при пролёте "вбок")
|
// 2) Если решения нет / оно плохое — fallback (чтобы круг не пропадал при пролёте "вбок")
|
||||||
// Это ключевое изменение: lead всегда будет.
|
// Это ключевое изменение: lead всегда будет.
|
||||||
if (!ok || !(tLead > 0.0f) || tLead > maxLeadTime) {
|
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;
|
haveLead = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) проекция
|
// Проекция цели (для рамок/стрелки)
|
||||||
float ndcX, ndcY, ndcZ, clipW;
|
float ndcX, ndcY, ndcZ, clipW;
|
||||||
if (!projectToNDC(shipWorld, ndcX, ndcY, ndcZ, clipW)) return;
|
if (!projectToNDC(shipWorld, ndcX, ndcY, ndcZ, clipW)) return;
|
||||||
|
|
||||||
// behind camera?
|
|
||||||
bool behind = (clipW <= 0.0f);
|
bool behind = (clipW <= 0.0f);
|
||||||
|
|
||||||
// on-screen check (NDC)
|
|
||||||
bool onScreen = (!behind &&
|
bool onScreen = (!behind &&
|
||||||
ndcX >= -1.0f && ndcX <= 1.0f &&
|
ndcX >= -1.0f && ndcX <= 1.0f &&
|
||||||
ndcY >= -1.0f && ndcY <= 1.0f);
|
ndcY >= -1.0f && ndcY <= 1.0f);
|
||||||
|
|
||||||
// 3) расстояние
|
|
||||||
float dist = (Environment::shipState.position - shipWorld).norm();
|
float dist = (Environment::shipState.position - shipWorld).norm();
|
||||||
|
|
||||||
// time for arrow bob
|
|
||||||
float t = static_cast<float>(SDL_GetTicks64()) * 0.001f;
|
float t = static_cast<float>(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); // красный
|
Eigen::Vector4f enemyColor(1.f, 0.f, 0.f, 1.f); // красный
|
||||||
float thickness = 2.0f; // толщина линий (px)
|
float thickness = 2.0f; // толщина линий (px)
|
||||||
float z = 0.0f; // 2D слой
|
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)
|
if (onScreen)
|
||||||
{
|
{
|
||||||
// перевод NDC -> экран (в пикселях)
|
// перевод NDC -> экран (в пикселях)
|
||||||
@ -1252,40 +1297,9 @@ namespace ZL
|
|||||||
renderer.LoadIdentity();
|
renderer.LoadIdentity();
|
||||||
|
|
||||||
renderer.EnableVertexAttribArray("vPosition");
|
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 + cornerLen * 0.5f, top, cornerLen, thickness);
|
||||||
drawBar(left, top - cornerLen * 0.5f, thickness, cornerLen);
|
drawBar(left, top - cornerLen * 0.5f, thickness, cornerLen);
|
||||||
|
|
||||||
@ -1298,9 +1312,14 @@ namespace ZL
|
|||||||
drawBar(right - cornerLen * 0.5f, bottom, cornerLen, thickness);
|
drawBar(right - cornerLen * 0.5f, bottom, cornerLen, thickness);
|
||||||
drawBar(right, bottom + cornerLen * 0.5f, thickness, cornerLen);
|
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.DisableVertexAttribArray("vPosition");
|
||||||
|
|
||||||
|
|
||||||
renderer.PopMatrix();
|
renderer.PopMatrix();
|
||||||
renderer.PopProjectionMatrix();
|
renderer.PopProjectionMatrix();
|
||||||
renderer.shaderManager.PopShader();
|
renderer.shaderManager.PopShader();
|
||||||
@ -1312,6 +1331,8 @@ namespace ZL
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Цель вне экрана: стрелка
|
||||||
float dirX = ndcX;
|
float dirX = ndcX;
|
||||||
float dirY = ndcY;
|
float dirY = ndcY;
|
||||||
|
|
||||||
@ -1384,18 +1405,30 @@ namespace ZL
|
|||||||
renderer.PushMatrix();
|
renderer.PushMatrix();
|
||||||
renderer.LoadIdentity();
|
renderer.LoadIdentity();
|
||||||
|
|
||||||
|
renderer.EnableVertexAttribArray("vPosition");
|
||||||
|
renderer.RenderUniform4fv("uColor", enemyColor.data());
|
||||||
|
|
||||||
|
// стрелка
|
||||||
drawTri(tip, left, right);
|
drawTri(tip, left, right);
|
||||||
|
|
||||||
float tailLen = 14.0f;
|
float tailLen = 14.0f;
|
||||||
float tailX = edgeX - dirX * 6.0f;
|
float tailX = edgeX - dirX * 6.0f;
|
||||||
float tailY = edgeY - dirY * 6.0f;
|
float tailY = edgeY - dirY * 6.0f;
|
||||||
|
|
||||||
drawBar(tailX, tailY, max(thickness, tailLen), thickness);
|
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.PopMatrix();
|
||||||
renderer.PopProjectionMatrix();
|
renderer.PopProjectionMatrix();
|
||||||
renderer.shaderManager.PopShader();
|
renderer.shaderManager.PopShader();
|
||||||
|
|
||||||
|
// дистанция около стрелки
|
||||||
{
|
{
|
||||||
std::string d = std::to_string((int)dist) + "m";
|
std::string d = std::to_string((int)dist) + "m";
|
||||||
float tx = edgeX + px * 18.0f;
|
float tx = edgeX + px * 18.0f;
|
||||||
@ -1421,7 +1454,7 @@ namespace ZL
|
|||||||
firePressed = false;
|
firePressed = false;
|
||||||
if (now_ms - lastProjectileFireTime >= static_cast<uint64_t>(projectileCooldownMs)) {
|
if (now_ms - lastProjectileFireTime >= static_cast<uint64_t>(projectileCooldownMs)) {
|
||||||
lastProjectileFireTime = now_ms;
|
lastProjectileFireTime = now_ms;
|
||||||
const float projectileSpeed = 250.0f;
|
const float projectileSpeed = PROJECTILE_VELOCITY;
|
||||||
|
|
||||||
this->fireProjectiles();
|
this->fireProjectiles();
|
||||||
|
|
||||||
@ -1429,7 +1462,7 @@ namespace ZL
|
|||||||
Eigen::Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized();
|
Eigen::Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized();
|
||||||
|
|
||||||
Eigen::Vector3f centerPos = Environment::shipState.position +
|
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);
|
Eigen::Quaternionf q(Environment::shipState.rotation);
|
||||||
float speedToSend = projectileSpeed + Environment::shipState.velocity;
|
float speedToSend = projectileSpeed + Environment::shipState.velocity;
|
||||||
@ -1743,8 +1776,8 @@ namespace ZL
|
|||||||
const float size = 0.5f;
|
const float size = 0.5f;
|
||||||
for (const auto& pi : pending) {
|
for (const auto& pi : pending) {
|
||||||
const std::vector<Vector3f> localOffsets = {
|
const std::vector<Vector3f> localOffsets = {
|
||||||
Vector3f{ -1.5f, 0.9f, 5.0f },
|
Vector3f{ -1.5f, 0.9f - 6.0f, 5.0f },
|
||||||
Vector3f{ 1.5f, 0.9f, 5.0f }
|
Vector3f{ 1.5f, 0.9f - 6.0f, 5.0f }
|
||||||
};
|
};
|
||||||
|
|
||||||
Vector3f localForward = { 0, 0, -1 };
|
Vector3f localForward = { 0, 0, -1 };
|
||||||
|
|||||||
79
src/main.cpp
79
src/main.cpp
@ -16,37 +16,46 @@
|
|||||||
// For Android and Desktop a plain global value is used (no context loss).
|
// For Android and Desktop a plain global value is used (no context loss).
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
ZL::Game* g_game = nullptr;
|
ZL::Game* g_game = nullptr;
|
||||||
|
//static SDL_Window* win_x = nullptr;
|
||||||
|
static SDL_GLContext glContext_x; // Не указатель, а сам объект (который суть void*)
|
||||||
|
|
||||||
#else
|
#else
|
||||||
ZL::Game game;
|
ZL::Game game;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
void MainLoop() {
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
if (g_game) g_game->update();
|
void MainLoop() {
|
||||||
#else
|
// SDL_GL_MakeCurrent тут не нужен каждый раз
|
||||||
game.update();
|
/*glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||||
#endif
|
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
|
#ifdef EMSCRIPTEN
|
||||||
|
|
||||||
EM_BOOL onWebGLContextLost(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) {
|
EM_BOOL onWebGLContextLost(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) {
|
||||||
delete g_game;
|
//delete g_game;
|
||||||
g_game = nullptr;
|
//g_game = nullptr;
|
||||||
return EM_TRUE;
|
return EM_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) {
|
EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) {
|
||||||
g_game = new ZL::Game();
|
//g_game = new ZL::Game();
|
||||||
g_game->setup();
|
//g_game->setup();
|
||||||
return EM_TRUE;
|
return EM_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
// Вычисляем реальные физические пиксели
|
// Вычисляем реальные физические пиксели
|
||||||
int physicalW = static_cast<int>(logicalW * dpr);
|
int physicalW = static_cast<int>(logicalW * dpr);
|
||||||
@ -72,7 +81,7 @@ static void applyResize(int logicalW, int logicalH) {
|
|||||||
e.window.event = SDL_WINDOWEVENT_RESIZED;
|
e.window.event = SDL_WINDOWEVENT_RESIZED;
|
||||||
e.window.data1 = physicalW;
|
e.window.data1 = physicalW;
|
||||||
e.window.data2 = physicalH;
|
e.window.data2 = physicalH;
|
||||||
SDL_PushEvent(&e);
|
SDL_PushEvent(&e);*/
|
||||||
}
|
}
|
||||||
|
|
||||||
EM_BOOL onWindowResized(int /*eventType*/, const EmscriptenUiEvent* e, void* /*userData*/) {
|
EM_BOOL onWindowResized(int /*eventType*/, const EmscriptenUiEvent* e, void* /*userData*/) {
|
||||||
@ -91,6 +100,44 @@ EM_BOOL onFullscreenChanged(int /*eventType*/, const EmscriptenFullscreenChangeE
|
|||||||
return EM_FALSE;
|
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
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@ -178,7 +225,9 @@ extern "C" int SDL_main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#else
|
#endif
|
||||||
|
|
||||||
|
#ifdef WIN32_LEAN_AND_MEAN
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
try
|
try
|
||||||
@ -192,7 +241,7 @@ int main(int argc, char *argv[]) {
|
|||||||
ZL::Environment::height = CONST_HEIGHT;
|
ZL::Environment::height = CONST_HEIGHT;
|
||||||
|
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
/*#ifdef EMSCRIPTEN
|
||||||
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
||||||
std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
|
std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
@ -251,7 +300,7 @@ int main(int argc, char *argv[]) {
|
|||||||
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
|
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
|
||||||
|
|
||||||
emscripten_set_main_loop(MainLoop, 0, 1);
|
emscripten_set_main_loop(MainLoop, 0, 1);
|
||||||
#else
|
#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;
|
||||||
@ -277,7 +326,7 @@ int main(int argc, char *argv[]) {
|
|||||||
game.update();
|
game.update();
|
||||||
SDL_Delay(2);
|
SDL_Delay(2);
|
||||||
}
|
}
|
||||||
#endif
|
//#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
|
|||||||
@ -418,11 +418,13 @@ namespace ZL {
|
|||||||
pinfo.position = pr.pos;
|
pinfo.position = pr.pos;
|
||||||
pinfo.rotation = dir.toRotationMatrix();
|
pinfo.rotation = dir.toRotationMatrix();
|
||||||
pinfo.velocity = velocity;
|
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() << ", "
|
<< 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
namespace ZL {
|
namespace ZL {
|
||||||
void WebSocketClientEmscripten::Connect(const std::string& host, uint16_t port) {
|
void WebSocketClientEmscripten::Connect(const std::string& host, uint16_t port) {
|
||||||
// Формируем URL. Обратите внимание, что в Web часто лучше использовать ws://localhost
|
// Формируем URL. Обратите внимание, что в Web часто лучше использовать ws://localhost
|
||||||
std::string url = "ws://" + host + ":" + std::to_string(port);
|
//std::string url = "ws://" + host + ":" + std::to_string(port);
|
||||||
//std::string url = "wss://api.spacegame.fishrungames.com";
|
std::string url = "wss://api.spacegame.fishrungames.com";
|
||||||
|
|
||||||
EmscriptenWebSocketCreateAttributes attr = {
|
EmscriptenWebSocketCreateAttributes attr = {
|
||||||
url.c_str(),
|
url.c_str(),
|
||||||
@ -22,13 +22,32 @@ namespace ZL {
|
|||||||
emscripten_websocket_set_onmessage_callback(socket_, this, onMessage);
|
emscripten_websocket_set_onmessage_callback(socket_, this, onMessage);
|
||||||
emscripten_websocket_set_onerror_callback(socket_, this, onError);
|
emscripten_websocket_set_onerror_callback(socket_, this, onError);
|
||||||
emscripten_websocket_set_onclose_callback(socket_, this, onClose);
|
emscripten_websocket_set_onclose_callback(socket_, this, onClose);
|
||||||
|
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClientEmscripten::flushOutgoingQueue() {
|
||||||
|
std::lock_guard<std::mutex> 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) {
|
void WebSocketClientEmscripten::Send(const std::string& message) {
|
||||||
|
std::string signedMsg = SignMessage(message);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(outgoingMutex);
|
||||||
if (connected && socket_ > 0) {
|
if (connected && socket_ > 0) {
|
||||||
auto signedMsg = SignMessage(message);
|
std::cout << "[WebWS] Sending message (immediate): " << signedMsg << std::endl;
|
||||||
std::cout << "[WebWS] Sending message: " << signedMsg << std::endl;
|
|
||||||
emscripten_websocket_send_utf8_text(socket_, signedMsg.c_str());
|
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,11 +55,8 @@ namespace ZL {
|
|||||||
// Локальная очередь для минимизации времени блокировки мьютекса
|
// Локальная очередь для минимизации времени блокировки мьютекса
|
||||||
std::queue<std::string> localQueue;
|
std::queue<std::string> localQueue;
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(queueMutex);
|
|
||||||
if (messageQueue.empty()) return;
|
if (messageQueue.empty()) return;
|
||||||
std::swap(localQueue, messageQueue);
|
std::swap(localQueue, messageQueue);
|
||||||
}
|
|
||||||
|
|
||||||
while (!localQueue.empty()) {
|
while (!localQueue.empty()) {
|
||||||
const std::string& msg = localQueue.front();
|
const std::string& msg = localQueue.front();
|
||||||
@ -59,6 +75,9 @@ namespace ZL {
|
|||||||
auto* self = static_cast<WebSocketClientEmscripten*>(userData);
|
auto* self = static_cast<WebSocketClientEmscripten*>(userData);
|
||||||
self->connected = true;
|
self->connected = true;
|
||||||
std::cout << "[WebWS] Connection opened" << std::endl;
|
std::cout << "[WebWS] Connection opened" << std::endl;
|
||||||
|
|
||||||
|
self->flushOutgoingQueue();
|
||||||
|
|
||||||
return EM_TRUE;
|
return EM_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +86,6 @@ namespace ZL {
|
|||||||
auto* self = static_cast<WebSocketClientEmscripten*>(userData);
|
auto* self = static_cast<WebSocketClientEmscripten*>(userData);
|
||||||
if (e->isText && e->data) {
|
if (e->isText && e->data) {
|
||||||
std::string msg(reinterpret_cast<const char*>(e->data), e->numBytes);
|
std::string msg(reinterpret_cast<const char*>(e->data), e->numBytes);
|
||||||
std::lock_guard<std::mutex> lock(self->queueMutex);
|
|
||||||
self->messageQueue.push(msg);
|
self->messageQueue.push(msg);
|
||||||
}
|
}
|
||||||
return EM_TRUE;
|
return EM_TRUE;
|
||||||
|
|||||||
@ -18,7 +18,10 @@ namespace ZL {
|
|||||||
|
|
||||||
// Очередь для хранения сырых строк от браузера
|
// Очередь для хранения сырых строк от браузера
|
||||||
std::queue<std::string> messageQueue;
|
std::queue<std::string> messageQueue;
|
||||||
std::mutex queueMutex;
|
|
||||||
|
std::queue<std::string> outgoingQueue;
|
||||||
|
std::mutex outgoingMutex;
|
||||||
|
void flushOutgoingQueue();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WebSocketClientEmscripten() = default;
|
WebSocketClientEmscripten() = default;
|
||||||
|
|||||||
@ -5,6 +5,8 @@ namespace ZL
|
|||||||
{
|
{
|
||||||
|
|
||||||
TaskManager::TaskManager(size_t threadCount) {
|
TaskManager::TaskManager(size_t threadCount) {
|
||||||
|
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
workGuard = std::make_unique<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>>(ioContext.get_executor());
|
workGuard = std::make_unique<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>>(ioContext.get_executor());
|
||||||
|
|
||||||
for (size_t i = 0; i < threadCount; ++i) {
|
for (size_t i = 0; i < threadCount; ++i) {
|
||||||
@ -12,28 +14,43 @@ namespace ZL
|
|||||||
ioContext.run();
|
ioContext.run();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void TaskManager::EnqueueBackgroundTask(std::function<void()> task) {
|
void TaskManager::EnqueueBackgroundTask(std::function<void()> task) {
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
task();
|
||||||
|
#else
|
||||||
boost::asio::post(ioContext, task);
|
boost::asio::post(ioContext, task);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskManager::~TaskManager() {
|
TaskManager::~TaskManager() {
|
||||||
|
#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();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainThreadHandler::EnqueueMainThreadTask(std::function<void()> task) {
|
void MainThreadHandler::EnqueueMainThreadTask(std::function<void()> task) {
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
std::lock_guard<std::mutex> lock(mainThreadMutex);
|
std::lock_guard<std::mutex> lock(mainThreadMutex);
|
||||||
|
#endif
|
||||||
mainThreadTasks.push(task);
|
mainThreadTasks.push(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainThreadHandler::processMainThreadTasks() {
|
void MainThreadHandler::processMainThreadTasks() {
|
||||||
std::function<void()> task;
|
std::function<void()> task;
|
||||||
|
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
if (!mainThreadTasks.empty()) {
|
||||||
|
task = std::move(mainThreadTasks.front());
|
||||||
|
mainThreadTasks.pop();
|
||||||
|
}
|
||||||
|
#else
|
||||||
// Èçâëåêàåì òîëüêî îäíó çàäà÷ó, ÷òîáû íå áëîêèðîâàòü update íàäîëãî
|
// Èçâëåêàåì òîëüêî îäíó çàäà÷ó, ÷òîáû íå áëîêèðîâàòü update íàäîëãî
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mainThreadMutex);
|
std::lock_guard<std::mutex> lock(mainThreadMutex);
|
||||||
@ -42,6 +59,7 @@ namespace ZL
|
|||||||
mainThreadTasks.pop();
|
mainThreadTasks.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (task) {
|
if (task) {
|
||||||
task(); // Çäåñü âûïîëíÿåòñÿ RefreshVBO èëè çàãðóçêà òåêñòóðû
|
task(); // Çäåñü âûïîëíÿåòñÿ RefreshVBO èëè çàãðóçêà òåêñòóðû
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
|
#include <thread>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <thread>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
@ -11,12 +15,12 @@ namespace ZL {
|
|||||||
|
|
||||||
class TaskManager {
|
class TaskManager {
|
||||||
private:
|
private:
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
boost::asio::io_context ioContext;
|
boost::asio::io_context ioContext;
|
||||||
std::unique_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> workGuard;
|
std::unique_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> workGuard;
|
||||||
std::vector<std::thread> workers;
|
std::vector<std::thread> workers;
|
||||||
|
#endif
|
||||||
public:
|
public:
|
||||||
//TaskManager(size_t threadCount = std::thread::hardware_concurrency());
|
|
||||||
TaskManager(size_t threadCount = 2);
|
TaskManager(size_t threadCount = 2);
|
||||||
|
|
||||||
// Ìåòîä äëÿ äîáàâëåíèÿ ôîíîâîé çàäà÷è
|
// Ìåòîä äëÿ äîáàâëåíèÿ ôîíîâîé çàäà÷è
|
||||||
@ -25,10 +29,12 @@ namespace ZL {
|
|||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
~TaskManager();
|
~TaskManager();
|
||||||
|
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
boost::asio::io_context& getIOContext()
|
boost::asio::io_context& getIOContext()
|
||||||
{
|
{
|
||||||
return ioContext;
|
return ioContext;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -36,8 +42,10 @@ namespace ZL {
|
|||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
std::queue<std::function<void()>> mainThreadTasks;
|
std::queue<std::function<void()>> mainThreadTasks;
|
||||||
std::mutex mainThreadMutex;
|
|
||||||
|
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
|
std::mutex mainThreadMutex;
|
||||||
|
#endif
|
||||||
public:
|
public:
|
||||||
void EnqueueMainThreadTask(std::function<void()> task);
|
void EnqueueMainThreadTask(std::function<void()> task);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user