Merge remote-tracking branch 'origin/main' into ShipSpawn
This commit is contained in:
commit
78046e5e2d
37 files changed
+1174
-744
No files matched your search
@@ -36,5 +36,25 @@ ClientState Environment::shipState;
|
||||
const float Environment::CONST_Z_NEAR = 5.f;
|
||||
const float Environment::CONST_Z_FAR = 5000.f;
|
||||
|
||||
float Environment::projectionWidth = 1280.0f;
|
||||
float Environment::projectionHeight = 720.0f;
|
||||
|
||||
void Environment::computeProjectionDimensions()
|
||||
{
|
||||
if (width <= 0 || height <= 0) return;
|
||||
|
||||
const float refShortSide = 720.0f;
|
||||
float aspect = (float)width / (float)height;
|
||||
|
||||
if (width >= height) {
|
||||
// Landscape: fix height to 720, scale width to preserve aspect
|
||||
projectionHeight = refShortSide;
|
||||
projectionWidth = refShortSide * aspect;
|
||||
} else {
|
||||
// Portrait: fix width to 720, scale height to preserve aspect
|
||||
projectionWidth = refShortSide;
|
||||
projectionHeight = refShortSide / aspect;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
+8
-1
@@ -35,8 +35,15 @@ public:
|
||||
static const float CONST_Z_NEAR;
|
||||
static const float CONST_Z_FAR;
|
||||
|
||||
// Virtual projection dimensions used for all 2D/UI rendering.
|
||||
// These maintain the screen's actual aspect ratio but normalize the
|
||||
// height to 720 (landscape) or width to 720 (portrait), giving a
|
||||
// consistent coordinate space regardless of physical screen resolution.
|
||||
static float projectionWidth;
|
||||
static float projectionHeight;
|
||||
|
||||
|
||||
// Call this once at startup and whenever the window is resized.
|
||||
static void computeProjectionDimensions();
|
||||
};
|
||||
|
||||
} // namespace ZL
|
||||
+46
-42
@@ -84,6 +84,8 @@ namespace ZL
|
||||
void Game::setup() {
|
||||
glContext = SDL_GL_CreateContext(ZL::Environment::window);
|
||||
|
||||
Environment::computeProjectionDimensions();
|
||||
|
||||
ZL::BindOpenGlFunctions();
|
||||
ZL::CheckGlError();
|
||||
renderer.InitOpenGL();
|
||||
@@ -100,7 +102,7 @@ namespace ZL
|
||||
loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE));
|
||||
#endif
|
||||
|
||||
loadingMesh.data = CreateRect2D({ Environment::width * 0.5, Environment::height * 0.5 }, { Environment::width * 0.5, Environment::height*0.5 }, 3);
|
||||
loadingMesh.data = CreateRect2D({ Environment::projectionWidth * 0.5f, Environment::projectionHeight * 0.5f }, { Environment::projectionWidth * 0.5f, Environment::projectionHeight * 0.5f }, 3);
|
||||
loadingMesh.RefreshVBO();
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
@@ -142,17 +144,14 @@ namespace ZL
|
||||
Environment::shipState.nickname = nickname;
|
||||
Environment::shipState.shipType = shipType;
|
||||
|
||||
networkClient = std::make_unique<LocalClient>();
|
||||
auto localClient = new LocalClient;
|
||||
ClientState st = Environment::shipState;
|
||||
st.id = localClient->GetClientId();
|
||||
localClient->setLocalPlayerState(st);
|
||||
|
||||
networkClient = std::unique_ptr<INetworkClient>(localClient);
|
||||
networkClient->Connect("", 0);
|
||||
|
||||
#ifndef NETWORK
|
||||
auto localClient = dynamic_cast<ZL::LocalClient*>(networkClient.get());
|
||||
if (localClient) {
|
||||
ZL::ClientState st = Environment::shipState;
|
||||
st.id = localClient->GetClientId();
|
||||
localClient->setLocalPlayerState(st);
|
||||
}
|
||||
#endif
|
||||
lastTickCount = 0;
|
||||
spaceGameStarted = 1;
|
||||
};
|
||||
@@ -161,8 +160,7 @@ namespace ZL
|
||||
Environment::shipState.nickname = nickname;
|
||||
Environment::shipState.shipType = shipType;
|
||||
|
||||
networkClient = std::make_unique<LocalClient>();
|
||||
#ifdef NETWORK
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
networkClient = std::make_unique<WebSocketClientEmscripten>();
|
||||
networkClient->Connect("localhost", 8081);
|
||||
@@ -170,19 +168,6 @@ namespace ZL
|
||||
networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
|
||||
networkClient->Connect("localhost", 8081);
|
||||
#endif
|
||||
#else
|
||||
networkClient->Connect("", 0);
|
||||
#endif
|
||||
|
||||
#ifndef NETWORK
|
||||
auto localClient = dynamic_cast<ZL::LocalClient*>(networkClient.get());
|
||||
if (localClient) {
|
||||
ZL::ClientState st = Environment::shipState;
|
||||
st.id = localClient->GetClientId();
|
||||
localClient->setLocalPlayerState(st);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if (networkClient) {
|
||||
std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType);
|
||||
@@ -265,8 +250,8 @@ namespace ZL
|
||||
renderer.EnableVertexAttribArray(vPositionName);
|
||||
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||
|
||||
float width = Environment::width;
|
||||
float height = Environment::height;
|
||||
float width = Environment::projectionWidth;
|
||||
float height = Environment::projectionHeight;
|
||||
|
||||
renderer.PushProjectionMatrix(
|
||||
0, width,
|
||||
@@ -351,16 +336,19 @@ namespace ZL
|
||||
if (event.type == SDL_QUIT) {
|
||||
Environment::exitGameLoop = true;
|
||||
}
|
||||
#if SDL_VERSION_ATLEAST(2,0,5)
|
||||
else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||
|
||||
|
||||
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||
// Обновляем размеры и сбрасываем кеш текстов, т.к. меши хранятся в пикселях
|
||||
Environment::width = event.window.data1;
|
||||
Environment::height = event.window.data2;
|
||||
std::cout << "Window resized: " << Environment::width << "x" << Environment::height << std::endl;
|
||||
Environment::computeProjectionDimensions();
|
||||
menuManager.uiManager.updateAllLayouts();
|
||||
std::cout << "Window resized: " << Environment::width << "x" << Environment::height << std::endl;
|
||||
|
||||
space.clearTextRendererCache();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) {
|
||||
Environment::exitGameLoop = true;
|
||||
@@ -369,22 +357,38 @@ namespace ZL
|
||||
|
||||
#ifdef __ANDROID__
|
||||
if (event.type == SDL_FINGERDOWN) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::width);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::height);
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleDown(mx, my);
|
||||
}
|
||||
else if (event.type == SDL_FINGERUP) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::width);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::height);
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleUp(mx, my);
|
||||
}
|
||||
else if (event.type == SDL_FINGERMOTION) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::width);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::height);
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleMotion(mx, my);
|
||||
}
|
||||
#else
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN) {
|
||||
|
||||
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
|
||||
// Преобразуем экранные пиксели в проекционные единицы
|
||||
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);
|
||||
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN) handleDown(mx, my);
|
||||
else handleUp(mx, my);
|
||||
}
|
||||
else if (event.type == SDL_MOUSEMOTION) {
|
||||
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);
|
||||
handleMotion(mx, my);
|
||||
}
|
||||
|
||||
/*if (event.type == SDL_MOUSEBUTTONDOWN) {
|
||||
int mx = event.button.x;
|
||||
int my = event.button.y;
|
||||
handleDown(mx, my);
|
||||
@@ -398,7 +402,7 @@ namespace ZL
|
||||
int mx = event.motion.x;
|
||||
int my = event.motion.y;
|
||||
handleMotion(mx, my);
|
||||
}
|
||||
}*/
|
||||
|
||||
if (event.type == SDL_MOUSEWHEEL) {
|
||||
static const float zoomstep = 2.0f;
|
||||
@@ -480,7 +484,7 @@ namespace ZL
|
||||
void Game::handleDown(int mx, int my)
|
||||
{
|
||||
int uiX = mx;
|
||||
int uiY = Environment::height - my;
|
||||
int uiY = Environment::projectionHeight - my;
|
||||
|
||||
menuManager.uiManager.onMouseDown(uiX, uiY);
|
||||
|
||||
@@ -508,7 +512,7 @@ namespace ZL
|
||||
void Game::handleUp(int mx, int my)
|
||||
{
|
||||
int uiX = mx;
|
||||
int uiY = Environment::height - my;
|
||||
int uiY = Environment::projectionHeight - my;
|
||||
|
||||
menuManager.uiManager.onMouseUp(uiX, uiY);
|
||||
|
||||
@@ -523,7 +527,7 @@ namespace ZL
|
||||
void Game::handleMotion(int mx, int my)
|
||||
{
|
||||
int uiX = mx;
|
||||
int uiY = Environment::height - my;
|
||||
int uiY = Environment::projectionHeight - my;
|
||||
|
||||
menuManager.uiManager.onMouseMove(uiX, uiY);
|
||||
|
||||
|
||||
+6
-4
@@ -96,6 +96,7 @@ namespace ZL {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
uiManager.setButtonCallback("shootButton", [this](const std::string& name) {
|
||||
onFirePressed();
|
||||
});
|
||||
@@ -103,6 +104,7 @@ namespace ZL {
|
||||
onFirePressed();
|
||||
});
|
||||
uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) {
|
||||
|
||||
int newVel = roundf(value * 10);
|
||||
if (newVel > 2)
|
||||
{
|
||||
@@ -183,8 +185,8 @@ namespace ZL {
|
||||
}
|
||||
});
|
||||
|
||||
uiManager.setButtonCallback("multiplayerButton2", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) {
|
||||
/*std::cerr << "Multiplayer button pressed → opening multiplayer menu\n";
|
||||
/*uiManager.setButtonCallback("multiplayerButton2", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) {
|
||||
std::cerr << "Multiplayer button pressed → opening multiplayer menu\n";
|
||||
|
||||
uiManager.startAnimationOnNode("playButton", "buttonsExit");
|
||||
uiManager.startAnimationOnNode("settingsButton", "buttonsExit");
|
||||
@@ -219,7 +221,7 @@ namespace ZL {
|
||||
}
|
||||
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";
|
||||
@@ -255,7 +257,7 @@ namespace ZL {
|
||||
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)
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ namespace ZL {
|
||||
}
|
||||
|
||||
void Projectile::rebuildMesh(Renderer&) {
|
||||
float half = size * 0.5f;
|
||||
float half = 10 * size * 0.5f;
|
||||
|
||||
mesh.data.PositionData.clear();
|
||||
mesh.data.TexCoordData.clear();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "render/Renderer.h"
|
||||
#include "render/TextureManager.h"
|
||||
#include <memory>
|
||||
#include "SparkEmitter.h"
|
||||
|
||||
namespace ZL {
|
||||
|
||||
@@ -19,6 +20,8 @@ namespace ZL {
|
||||
|
||||
Vector3f getPosition() const { return pos; }
|
||||
void deactivate() { active = false; }
|
||||
|
||||
SparkEmitter projectileEmitter;
|
||||
private:
|
||||
Vector3f pos;
|
||||
Vector3f vel;
|
||||
|
||||
+56
-38
@@ -159,15 +159,15 @@ namespace ZL
|
||||
|
||||
// В пределах экрана?
|
||||
// (можно оставить, можно клампить)
|
||||
float sx = (ndc.x() * 0.5f + 0.5f) * Environment::width;
|
||||
float sy = (ndc.y() * 0.5f + 0.5f) * Environment::height;
|
||||
float sx = (ndc.x() * 0.5f + 0.5f) * Environment::projectionWidth;
|
||||
float sy = (ndc.y() * 0.5f + 0.5f) * Environment::projectionHeight;
|
||||
|
||||
outX = sx;
|
||||
outY = sy;
|
||||
|
||||
// Можно отсеять те, что вне:
|
||||
if (sx < -200 || sx > Environment::width + 200) return false;
|
||||
if (sy < -200 || sy > Environment::height + 200) return false;
|
||||
if (sx < -200 || sx > Environment::projectionWidth + 200) return false;
|
||||
if (sy < -200 || sy > Environment::projectionHeight + 200) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -296,12 +296,12 @@ namespace ZL
|
||||
|
||||
cubemapTexture = std::make_shared<Texture>(
|
||||
std::array<TextureDataStruct, 6>{
|
||||
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE)
|
||||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
|
||||
CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE)
|
||||
});
|
||||
|
||||
|
||||
@@ -335,6 +335,7 @@ namespace ZL
|
||||
cargo.AssignFrom(cargoBase);
|
||||
cargo.RefreshVBO();
|
||||
|
||||
//projectileTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/spark2.png", CONST_ZIP_FILE));
|
||||
|
||||
//Boxes
|
||||
boxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/box/box.png", CONST_ZIP_FILE));
|
||||
@@ -362,7 +363,7 @@ namespace ZL
|
||||
}
|
||||
|
||||
crosshairCfgLoaded = loadCrosshairConfig("resources/config/crosshair_config.json");
|
||||
std::cerr << "[Crosshair] loaded=" << crosshairCfgLoaded
|
||||
std::cout << "[Crosshair] loaded=" << crosshairCfgLoaded
|
||||
<< " enabled=" << crosshairCfg.enabled
|
||||
<< " w=" << Environment::width << " h=" << Environment::height
|
||||
<< " alpha=" << crosshairCfg.alpha
|
||||
@@ -490,13 +491,25 @@ namespace ZL
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
renderer.shaderManager.PushShader("default");
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
renderer.EnableVertexAttribArray(vPositionName);
|
||||
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
||||
for (const auto& p : projectiles) {
|
||||
if (p && p->isActive()) {
|
||||
p->draw(renderer);
|
||||
p->projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||
}
|
||||
}
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||
renderer.DisableVertexAttribArray(vPositionName);
|
||||
renderer.DisableVertexAttribArray(vTexCoordName);
|
||||
renderer.shaderManager.PopShader();
|
||||
|
||||
//projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||
|
||||
if (shipAlive) {
|
||||
renderer.PushMatrix();
|
||||
@@ -859,8 +872,8 @@ namespace ZL
|
||||
|
||||
// если ничего не изменилось — не трогаем VBO
|
||||
if (crosshairMeshValid &&
|
||||
crosshairLastW == Environment::width &&
|
||||
crosshairLastH == Environment::height &&
|
||||
crosshairLastW == Environment::projectionWidth &&
|
||||
crosshairLastH == Environment::projectionHeight &&
|
||||
std::abs(crosshairLastAlpha - crosshairCfg.alpha) < 1e-6f &&
|
||||
std::abs(crosshairLastThickness - crosshairCfg.thicknessPx) < 1e-6f &&
|
||||
std::abs(crosshairLastGap - crosshairCfg.gapPx) < 1e-6f &&
|
||||
@@ -869,18 +882,18 @@ namespace ZL
|
||||
return;
|
||||
}
|
||||
|
||||
crosshairLastW = Environment::width;
|
||||
crosshairLastH = Environment::height;
|
||||
crosshairLastW = Environment::projectionWidth;
|
||||
crosshairLastH = Environment::projectionHeight;
|
||||
crosshairLastAlpha = crosshairCfg.alpha;
|
||||
crosshairLastThickness = crosshairCfg.thicknessPx;
|
||||
crosshairLastGap = crosshairCfg.gapPx;
|
||||
crosshairLastScaleMul = crosshairCfg.scaleMul;
|
||||
|
||||
float cx = Environment::width * 0.5f;
|
||||
float cy = Environment::height * 0.5f;
|
||||
float cx = Environment::projectionWidth * 0.5f;
|
||||
float cy = Environment::projectionHeight * 0.5f;
|
||||
|
||||
// масштаб от reference (стандартно: по высоте)
|
||||
float scale = (crosshairCfg.refH > 0) ? (Environment::height / (float)crosshairCfg.refH) : 1.0f;
|
||||
float scale = (crosshairCfg.refH > 0) ? (Environment::projectionHeight / (float)crosshairCfg.refH) : 1.0f;
|
||||
scale *= crosshairCfg.scaleMul;
|
||||
|
||||
float thickness = crosshairCfg.thicknessPx * scale;
|
||||
@@ -940,7 +953,7 @@ namespace ZL
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
renderer.shaderManager.PushShader("defaultColor");
|
||||
renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f);
|
||||
renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, 0.f, 1.f);
|
||||
renderer.PushMatrix();
|
||||
renderer.LoadIdentity();
|
||||
|
||||
@@ -1124,7 +1137,7 @@ namespace ZL
|
||||
|
||||
// Lead Indicator
|
||||
// скорость пули (как в fireProjectiles)
|
||||
const float projectileSpeed = 60.0f;
|
||||
const float projectileSpeed = PROJECTILE_VELOCITY;
|
||||
|
||||
// позиция вылета
|
||||
Vector3f shooterPos = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0f, 0.9f - 6.0f, 5.0f };
|
||||
@@ -1187,15 +1200,15 @@ namespace ZL
|
||||
|
||||
// 4) Настройки стиля
|
||||
Eigen::Vector4f enemyColor(1.f, 0.f, 0.f, 1.f); // красный
|
||||
float thickness = 10.0f; // толщина линий (px)
|
||||
float thickness = 2.0f; // толщина линий (px)
|
||||
float z = 0.0f; // 2D слой
|
||||
|
||||
// 5) Если цель в кадре: рисуем скобки
|
||||
if (onScreen)
|
||||
{
|
||||
// перевод NDC -> экран (в пикселях)
|
||||
float sx = (ndcX * 0.5f + 0.5f) * Environment::width;
|
||||
float sy = (ndcY * 0.5f + 0.5f) * Environment::height;
|
||||
float sx = (ndcX * 0.5f + 0.5f) * Environment::projectionWidth;
|
||||
float sy = (ndcY * 0.5f + 0.5f) * Environment::projectionHeight;
|
||||
|
||||
// анимация “снаружи внутрь”
|
||||
// targetAcquireAnim растёт к 1, быстро (похоже на захват)
|
||||
@@ -1234,7 +1247,7 @@ namespace ZL
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
renderer.shaderManager.PushShader("defaultColor");
|
||||
renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f);
|
||||
renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, 0.f, 1.f);
|
||||
renderer.PushMatrix();
|
||||
renderer.LoadIdentity();
|
||||
|
||||
@@ -1248,8 +1261,8 @@ namespace ZL
|
||||
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::width;
|
||||
float ly = (leadNdcY * 0.5f + 0.5f) * Environment::height;
|
||||
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);
|
||||
@@ -1323,8 +1336,8 @@ namespace ZL
|
||||
float edgeNdcX = dirX * k;
|
||||
float edgeNdcY = dirY * k;
|
||||
|
||||
float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::width;
|
||||
float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::height;
|
||||
float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::projectionWidth;
|
||||
float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::projectionHeight;
|
||||
|
||||
float bob = std::sin(t * 6.0f) * 6.0f;
|
||||
edgeX += dirX * bob;
|
||||
@@ -1367,7 +1380,7 @@ namespace ZL
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
renderer.shaderManager.PushShader("defaultColor");
|
||||
renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f);
|
||||
renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, 0.f, 1.f);
|
||||
renderer.PushMatrix();
|
||||
renderer.LoadIdentity();
|
||||
|
||||
@@ -1540,22 +1553,25 @@ namespace ZL
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Vector3f> projCameraPoints;
|
||||
|
||||
for (const auto& p : projectiles) {
|
||||
if (p && p->isActive()) {
|
||||
Vector3f worldPos = p->getPosition();
|
||||
Vector3f rel = worldPos - Environment::shipState.position;
|
||||
Vector3f camPos = Environment::inverseShipMatrix * rel;
|
||||
projCameraPoints.push_back(camPos);
|
||||
p->projectileEmitter.setEmissionPoints({ camPos });
|
||||
p->projectileEmitter.emit();
|
||||
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) {
|
||||
@@ -1567,7 +1583,7 @@ namespace ZL
|
||||
}
|
||||
|
||||
sparkEmitter.update(static_cast<float>(delta));
|
||||
projectileEmitter.update(static_cast<float>(delta));
|
||||
//projectileEmitter.update(static_cast<float>(delta));
|
||||
|
||||
explosionEmitter.update(static_cast<float>(delta));
|
||||
if (showExplosion) {
|
||||
@@ -1696,8 +1712,8 @@ namespace ZL
|
||||
Vector3f{ 1.5f, 0.9f - 6.f, 5.0f }
|
||||
};
|
||||
|
||||
const float projectileSpeed = 60.0f;
|
||||
const float lifeMs = 50000.0f;
|
||||
const float projectileSpeed = PROJECTILE_VELOCITY;
|
||||
const float lifeMs = PROJECTILE_LIFE;
|
||||
const float size = 0.5f;
|
||||
|
||||
Vector3f localForward = { 0,0,-1 };
|
||||
@@ -1710,6 +1726,7 @@ namespace ZL
|
||||
for (auto& p : projectiles) {
|
||||
if (!p->isActive()) {
|
||||
p->init(worldPos, worldVel, lifeMs, size, projectileTexture, renderer);
|
||||
p->projectileEmitter = SparkEmitter(projectileEmitter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1721,8 +1738,8 @@ namespace ZL
|
||||
if (networkClient) {
|
||||
auto pending = networkClient->getPendingProjectiles();
|
||||
if (!pending.empty()) {
|
||||
const float projectileSpeed = 60.0f;
|
||||
const float lifeMs = 5000.0f;
|
||||
const float projectileSpeed = PROJECTILE_VELOCITY;
|
||||
const float lifeMs = PROJECTILE_LIFE;
|
||||
const float size = 0.5f;
|
||||
for (const auto& pi : pending) {
|
||||
const std::vector<Vector3f> localOffsets = {
|
||||
@@ -1747,6 +1764,7 @@ namespace ZL
|
||||
for (auto& p : projectiles) {
|
||||
if (!p->isActive()) {
|
||||
p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer);
|
||||
p->projectileEmitter = SparkEmitter(projectileEmitter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ namespace ZL {
|
||||
std::shared_ptr<Texture> projectileTexture;
|
||||
float projectileCooldownMs = 500.0f;
|
||||
int64_t lastProjectileFireTime = 0;
|
||||
int maxProjectiles = 32;
|
||||
int maxProjectiles = 500;
|
||||
std::vector<Vector3f> shipLocalEmissionPoints;
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,23 @@ namespace ZL {
|
||||
sparkQuad.data = VertexDataStruct();
|
||||
}
|
||||
|
||||
SparkEmitter::SparkEmitter(const SparkEmitter& copyFrom)
|
||||
: particles(copyFrom.particles), emissionPoints(copyFrom.emissionPoints),
|
||||
lastEmissionTime(copyFrom.lastEmissionTime), emissionRate(copyFrom.emissionRate),
|
||||
isActive(copyFrom.isActive), drawPositions(copyFrom.drawPositions),
|
||||
drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(copyFrom.drawDataDirty),
|
||||
sparkQuad(copyFrom.sparkQuad), texture(copyFrom.texture),
|
||||
maxParticles(copyFrom.maxParticles), particleSize(copyFrom.particleSize),
|
||||
biasX(copyFrom.biasX), speedRange(copyFrom.speedRange),
|
||||
zSpeedRange(copyFrom.zSpeedRange),
|
||||
scaleRange(copyFrom.scaleRange),
|
||||
lifeTimeRange(copyFrom.lifeTimeRange),
|
||||
shaderProgramName(copyFrom.shaderProgramName),
|
||||
configured(copyFrom.configured), useWorldSpace(copyFrom.useWorldSpace)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions, float rate)
|
||||
: emissionPoints(positions), emissionRate(rate), isActive(true),
|
||||
drawDataDirty(true), maxParticles(positions.size() * 100),
|
||||
|
||||
+2
-1
@@ -41,7 +41,7 @@ namespace ZL {
|
||||
float biasX;
|
||||
|
||||
// Ranges (used when config supplies intervals)
|
||||
struct FloatRange { float min; float max; };
|
||||
struct FloatRange { float min=0; float max=0; };
|
||||
FloatRange speedRange; // XY speed
|
||||
FloatRange zSpeedRange; // Z speed
|
||||
FloatRange scaleRange;
|
||||
@@ -55,6 +55,7 @@ namespace ZL {
|
||||
|
||||
public:
|
||||
SparkEmitter();
|
||||
SparkEmitter(const SparkEmitter& copyFrom);
|
||||
SparkEmitter(const std::vector<Vector3f>& positions, float rate = 100.0f);
|
||||
SparkEmitter(const std::vector<Vector3f>& positions,
|
||||
std::shared_ptr<Texture> tex,
|
||||
|
||||
+247
-40
@@ -184,21 +184,89 @@ namespace ZL {
|
||||
|
||||
std::shared_ptr<UiNode> parseNode(const json& j, Renderer& renderer, const std::string& zipFile) {
|
||||
auto node = std::make_shared<UiNode>();
|
||||
if (j.contains("type") && j["type"].is_string()) node->type = j["type"].get<std::string>();
|
||||
if (j.contains("name") && j["name"].is_string()) node->name = j["name"].get<std::string>();
|
||||
|
||||
if (j.contains("x")) node->rect.x = j["x"].get<float>();
|
||||
if (j.contains("y")) node->rect.y = j["y"].get<float>();
|
||||
if (j.contains("width")) node->rect.w = j["width"].get<float>();
|
||||
if (j.contains("height")) node->rect.h = j["height"].get<float>();
|
||||
// 1. Определяем тип контейнера и ориентацию
|
||||
std::string typeStr = j.value("type", "FrameLayout"); // По умолчанию FrameLayout
|
||||
if (typeStr == "LinearLayout") {
|
||||
node->layoutType = LayoutType::Linear;
|
||||
}
|
||||
else {
|
||||
node->layoutType = LayoutType::Frame;
|
||||
}
|
||||
|
||||
if (j.contains("orientation") && j["orientation"].is_string()) node->orientation = j["orientation"].get<std::string>();
|
||||
if (j.contains("spacing")) node->spacing = j["spacing"].get<float>();
|
||||
if (j.contains("name")) node->name = j["name"].get<std::string>();
|
||||
|
||||
if (node->type == "Button") {
|
||||
// 2. Читаем размеры во временные "локальные" поля
|
||||
// Это критически важно: мы не пишем сразу в screenRect,
|
||||
// так как LinearLayout их пересчитает.
|
||||
node->localX = j.value("x", 0.0f);
|
||||
node->localY = j.value("y", 0.0f);
|
||||
if (j.contains("width")) {
|
||||
if (j["width"].is_string() && j["width"] == "match_parent") {
|
||||
node->width = -1.0f; // Наш маркер для match_parent
|
||||
}
|
||||
else {
|
||||
node->width = j["width"].get<float>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
node->width = 0.0f;
|
||||
}
|
||||
if (j.contains("height")) {
|
||||
if (j["height"].is_string() && j["height"] == "match_parent") {
|
||||
node->height = -1.0f; // Наш маркер для match_parent
|
||||
}
|
||||
else {
|
||||
node->height = j["height"].get<float>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
node->height = 0.0f;
|
||||
}
|
||||
|
||||
// 3. Параметры компоновки
|
||||
if (j.contains("orientation")) {
|
||||
std::string orient = j["orientation"].get<std::string>();
|
||||
node->orientation = (orient == "horizontal") ? Orientation::Horizontal : Orientation::Vertical;
|
||||
}
|
||||
node->spacing = j.value("spacing", 0.0f);
|
||||
|
||||
if (j.contains("horizontal_align")) {
|
||||
std::string halign = j["horizontal_align"];
|
||||
if (halign == "center") node->layoutSettings.hAlign = HorizontalAlign::Center;
|
||||
else if (halign == "right") node->layoutSettings.hAlign = HorizontalAlign::Right;
|
||||
}
|
||||
|
||||
if (j.contains("vertical_align")) {
|
||||
std::string valign = j["vertical_align"];
|
||||
if (valign == "center") node->layoutSettings.vAlign = VerticalAlign::Center;
|
||||
else if (valign == "bottom") node->layoutSettings.vAlign = VerticalAlign::Bottom;
|
||||
}
|
||||
|
||||
if (j.contains("horizontal_gravity")) {
|
||||
std::string hg = j["horizontal_gravity"].get<std::string>();
|
||||
if (hg == "right") node->layoutSettings.hGravity = HorizontalGravity::Right;
|
||||
else node->layoutSettings.hGravity = HorizontalGravity::Left;
|
||||
}
|
||||
|
||||
// Читаем Vertical Gravity
|
||||
if (j.contains("vertical_gravity")) {
|
||||
std::string vg = j["vertical_gravity"].get<std::string>();
|
||||
if (vg == "bottom") node->layoutSettings.vGravity = VerticalGravity::Bottom;
|
||||
else node->layoutSettings.vGravity = VerticalGravity::Top;
|
||||
}
|
||||
|
||||
// Подготавливаем базовый rect для компонентов (кнопок и т.д.)
|
||||
// На этапе парсинга мы даем им "желаемый" размер
|
||||
UiRect initialRect = { node->localX, node->localY, node->width, node->height };
|
||||
|
||||
|
||||
if (typeStr == "Button") {
|
||||
auto btn = std::make_shared<UiButton>();
|
||||
btn->name = node->name;
|
||||
btn->rect = node->rect;
|
||||
btn->rect = initialRect;
|
||||
|
||||
if (!j.contains("textures") || !j["textures"].is_object()) {
|
||||
std::cerr << "UiManager: Button '" << btn->name << "' missing textures" << std::endl;
|
||||
@@ -225,10 +293,10 @@ namespace ZL {
|
||||
|
||||
node->button = btn;
|
||||
}
|
||||
else if (node->type == "Slider") {
|
||||
else if (typeStr == "Slider") {
|
||||
auto s = std::make_shared<UiSlider>();
|
||||
s->name = node->name;
|
||||
s->rect = node->rect;
|
||||
s->rect = initialRect;
|
||||
|
||||
if (!j.contains("textures") || !j["textures"].is_object()) {
|
||||
std::cerr << "UiManager: Slider '" << s->name << "' missing textures" << std::endl;
|
||||
@@ -261,10 +329,10 @@ namespace ZL {
|
||||
|
||||
node->slider = s;
|
||||
}
|
||||
else if (node->type == "TextField") {
|
||||
else if (typeStr == "TextField") {
|
||||
auto tf = std::make_shared<UiTextField>();
|
||||
tf->name = node->name;
|
||||
tf->rect = node->rect;
|
||||
tf->rect = initialRect;
|
||||
|
||||
if (j.contains("placeholder")) tf->placeholder = j["placeholder"].get<std::string>();
|
||||
if (j.contains("fontPath")) tf->fontPath = j["fontPath"].get<std::string>();
|
||||
@@ -331,11 +399,11 @@ namespace ZL {
|
||||
}
|
||||
}
|
||||
|
||||
if (node->type == "TextView") {
|
||||
if (typeStr == "TextView") {
|
||||
auto tv = std::make_shared<UiTextView>();
|
||||
tv->name = node->name;
|
||||
tv->rect = node->rect;
|
||||
|
||||
tv->name = node->name;
|
||||
tv->rect = initialRect;
|
||||
if (j.contains("text")) tv->text = j["text"].get<std::string>();
|
||||
if (j.contains("fontPath")) tv->fontPath = j["fontPath"].get<std::string>();
|
||||
if (j.contains("fontSize")) tv->fontSize = j["fontSize"].get<int>();
|
||||
@@ -400,6 +468,7 @@ namespace ZL {
|
||||
throw std::runtime_error("Failed to load UI file: " + path);
|
||||
}
|
||||
|
||||
|
||||
root = parseNode(j["root"], renderer, zipFile);
|
||||
|
||||
return root;
|
||||
@@ -407,7 +476,14 @@ namespace ZL {
|
||||
|
||||
void UiManager::replaceRoot(std::shared_ptr<UiNode> newRoot) {
|
||||
root = newRoot;
|
||||
layoutNode(root);
|
||||
layoutNode(
|
||||
root,
|
||||
0.0f, 0.0f, // parentX, parentY (экран начинается с 0,0)
|
||||
Environment::projectionWidth, // parentW
|
||||
Environment::projectionHeight, // parentH
|
||||
root->localX, // finalLocalX
|
||||
root->localY // finalLocalY
|
||||
);
|
||||
buttons.clear();
|
||||
sliders.clear();
|
||||
textViews.clear();
|
||||
@@ -431,39 +507,170 @@ namespace ZL {
|
||||
replaceRoot(newRoot);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void UiManager::layoutNode(const std::shared_ptr<UiNode>& node) {
|
||||
for (auto& child : node->children) {
|
||||
child->rect.x += node->rect.x;
|
||||
child->rect.y += node->rect.y;
|
||||
}
|
||||
void UiManager::layoutNode(const std::shared_ptr<UiNode>& node, float parentX, float parentY, float parentW, float parentH, float finalLocalX, float finalLocalY) {
|
||||
|
||||
if (node->type == "LinearLayout") {
|
||||
std::string orient = node->orientation;
|
||||
std::transform(orient.begin(), orient.end(), orient.begin(), ::tolower);
|
||||
node->screenRect.w = (node->width < 0) ? parentW : node->width;
|
||||
node->screenRect.h = (node->height < 0) ? parentH : node->height;
|
||||
|
||||
float cursorX = node->rect.x;
|
||||
float cursorY = node->rect.y;
|
||||
for (auto& child : node->children) {
|
||||
if (orient == "horizontal") {
|
||||
child->rect.x = cursorX;
|
||||
child->rect.y = node->rect.y;
|
||||
cursorX += child->rect.w + node->spacing;
|
||||
// ТЕПЕРЬ используем переданные координаты, а не node->localX напрямую
|
||||
node->screenRect.x = parentX + finalLocalX;
|
||||
node->screenRect.y = parentY + finalLocalY;
|
||||
|
||||
float currentW = node->screenRect.w;
|
||||
float currentH = node->screenRect.h;
|
||||
|
||||
if (node->layoutType == LayoutType::Linear) {
|
||||
float totalContentWidth = 0;
|
||||
float totalContentHeight = 0;
|
||||
|
||||
// Предварительный расчет занимаемого места всеми детьми
|
||||
for (size_t i = 0; i < node->children.size(); ++i) {
|
||||
if (node->orientation == Orientation::Vertical) {
|
||||
totalContentHeight += node->children[i]->height;
|
||||
if (i < node->children.size() - 1) totalContentHeight += node->spacing;
|
||||
}
|
||||
else {
|
||||
child->rect.x = node->rect.x;
|
||||
child->rect.y = cursorY;
|
||||
cursorY += child->rect.h + node->spacing;
|
||||
totalContentWidth += node->children[i]->width;
|
||||
if (i < node->children.size() - 1) totalContentWidth += node->spacing;
|
||||
}
|
||||
layoutNode(child);
|
||||
}
|
||||
|
||||
float startX = 0;
|
||||
float startY = currentH;
|
||||
|
||||
if (node->orientation == Orientation::Vertical) {
|
||||
if (node->layoutSettings.vAlign == VerticalAlign::Center) {
|
||||
startY = (currentH + totalContentHeight) / 2.0f;
|
||||
}
|
||||
else if (node->layoutSettings.vAlign == VerticalAlign::Bottom) {
|
||||
startY = totalContentHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Горизонтальное выравнивание всего блока
|
||||
if (node->orientation == Orientation::Horizontal) {
|
||||
if (node->layoutSettings.hAlign == HorizontalAlign::Center) {
|
||||
startX = (currentW - totalContentWidth) / 2.0f;
|
||||
}
|
||||
else if (node->layoutSettings.hAlign == HorizontalAlign::Right) {
|
||||
startX = currentW - totalContentWidth;
|
||||
}
|
||||
}
|
||||
|
||||
float cursorX = startX;
|
||||
float cursorY = startY;
|
||||
|
||||
for (auto& child : node->children) {
|
||||
|
||||
float childW = (child->width < 0) ? currentW : child->width;
|
||||
float childH = (child->height < 0) ? currentH : child->height;
|
||||
|
||||
if (node->orientation == Orientation::Vertical) {
|
||||
cursorY -= childH; // используем вычисленный childH
|
||||
|
||||
float childX = 0;
|
||||
float freeSpaceX = currentW - childW;
|
||||
if (node->layoutSettings.hAlign == HorizontalAlign::Center) childX = freeSpaceX / 2.0f;
|
||||
else if (node->layoutSettings.hAlign == HorizontalAlign::Right) childX = freeSpaceX;
|
||||
|
||||
child->localX = childX;
|
||||
child->localY = cursorY;
|
||||
cursorY -= node->spacing;
|
||||
}
|
||||
else {
|
||||
child->localX = cursorX;
|
||||
|
||||
// Вертикальное выравнивание внутри "строки" (Cross-axis alignment)
|
||||
float childY = 0;
|
||||
float freeSpaceY = currentH - childH;
|
||||
|
||||
if (node->layoutSettings.vAlign == VerticalAlign::Center) {
|
||||
childY = freeSpaceY / 2.0f;
|
||||
}
|
||||
else if (node->layoutSettings.vAlign == VerticalAlign::Top) {
|
||||
childY = freeSpaceY; // Прижимаем к верхнему краю (т.к. Y растет вверх)
|
||||
}
|
||||
else if (node->layoutSettings.vAlign == VerticalAlign::Bottom) {
|
||||
childY = 0; // Прижимаем к нижнему краю
|
||||
}
|
||||
|
||||
child->localY = childY;
|
||||
|
||||
// Сдвигаем курсор вправо для следующего элемента
|
||||
cursorX += childW + node->spacing;
|
||||
}
|
||||
layoutNode(child, node->screenRect.x, node->screenRect.y, currentW, currentH, child->localX, child->localY);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (auto& child : node->children) {
|
||||
layoutNode(child);
|
||||
float childW = (child->width < 0) ? currentW : child->width;
|
||||
float childH = (child->height < 0) ? currentH : child->height;
|
||||
|
||||
float fLX = child->localX;
|
||||
float fLY = child->localY;
|
||||
|
||||
if (child->layoutSettings.hGravity == HorizontalGravity::Right) {
|
||||
fLX = currentW - childW - child->localX;
|
||||
}
|
||||
if (child->layoutSettings.vGravity == VerticalGravity::Top) {
|
||||
fLY = currentH - childH - child->localY;
|
||||
}
|
||||
|
||||
// Передаем рассчитанные fLX, fLY в рекурсию
|
||||
layoutNode(child, node->screenRect.x, node->screenRect.y, currentW, currentH, fLX, fLY);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем меши визуальных компонентов
|
||||
syncComponentRects(node);
|
||||
}
|
||||
|
||||
void UiManager::syncComponentRects(const std::shared_ptr<UiNode>& node) {
|
||||
if (!node) return;
|
||||
|
||||
// 1. Обновляем кнопку
|
||||
if (node->button) {
|
||||
node->button->rect = node->screenRect;
|
||||
// Если у кнопки есть анимационные смещения, они учитываются внутри buildMesh
|
||||
// или при рендеринге через Uniform-переменные матрицы модели.
|
||||
node->button->buildMesh();
|
||||
}
|
||||
|
||||
// 2. Обновляем слайдер
|
||||
if (node->slider) {
|
||||
node->slider->rect = node->screenRect;
|
||||
node->slider->buildTrackMesh();
|
||||
node->slider->buildKnobMesh();
|
||||
}
|
||||
|
||||
// 3. Обновляем текстовое поле (TextView)
|
||||
if (node->textView) {
|
||||
node->textView->rect = node->screenRect;
|
||||
// Если в TextView реализован кэш меша для текста, его нужно обновить здесь
|
||||
// node->textView->rebuildText();
|
||||
}
|
||||
|
||||
// 4. Обновляем поле ввода (TextField)
|
||||
if (node->textField) {
|
||||
node->textField->rect = node->screenRect;
|
||||
// Аналогично для курсора и фонового меша
|
||||
}
|
||||
}
|
||||
|
||||
void UiManager::updateAllLayouts() {
|
||||
if (!root) return;
|
||||
|
||||
// Запускаем расчет от корня, передавая размеры экрана как "родительские"
|
||||
layoutNode(
|
||||
root,
|
||||
0.0f, 0.0f, // parentX, parentY (экран начинается с 0,0)
|
||||
Environment::projectionWidth, // parentW
|
||||
Environment::projectionHeight, // parentH
|
||||
root->localX, // finalLocalX
|
||||
root->localY // finalLocalY
|
||||
);
|
||||
}
|
||||
|
||||
void UiManager::collectButtonsAndSliders(const std::shared_ptr<UiNode>& node) {
|
||||
@@ -657,7 +864,7 @@ namespace ZL {
|
||||
}
|
||||
|
||||
void UiManager::draw(Renderer& renderer) {
|
||||
renderer.PushProjectionMatrix(Environment::width, Environment::height, -1, 1);
|
||||
renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, -1, 1);
|
||||
renderer.PushMatrix();
|
||||
renderer.LoadIdentity();
|
||||
|
||||
|
||||
+66
-5
@@ -31,6 +31,48 @@ namespace ZL {
|
||||
Pressed
|
||||
};
|
||||
|
||||
enum class LayoutType {
|
||||
Frame, // Позиционирование по X, Y
|
||||
Linear // Автоматическое позиционирование
|
||||
};
|
||||
|
||||
enum class Orientation {
|
||||
Vertical,
|
||||
Horizontal
|
||||
};
|
||||
|
||||
enum class HorizontalAlign {
|
||||
Left,
|
||||
Center,
|
||||
Right
|
||||
};
|
||||
|
||||
enum class VerticalAlign {
|
||||
Top,
|
||||
Center,
|
||||
Bottom
|
||||
};
|
||||
|
||||
enum class HorizontalGravity {
|
||||
Left,
|
||||
Right
|
||||
};
|
||||
|
||||
enum class VerticalGravity {
|
||||
Bottom, // Обычно в OpenGL Y растет вверх, так что низ - это 0
|
||||
Top
|
||||
};
|
||||
|
||||
|
||||
// В структуру или класс, отвечающий за LinearLayout (вероятно, это свойства UiNode)
|
||||
struct LayoutSettings {
|
||||
HorizontalAlign hAlign = HorizontalAlign::Left;
|
||||
VerticalAlign vAlign = VerticalAlign::Top;
|
||||
|
||||
HorizontalGravity hGravity = HorizontalGravity::Left;
|
||||
VerticalGravity vGravity = VerticalGravity::Top;
|
||||
};
|
||||
|
||||
struct UiButton {
|
||||
std::string name;
|
||||
UiRect rect;
|
||||
@@ -111,21 +153,38 @@ namespace ZL {
|
||||
};
|
||||
|
||||
struct UiNode {
|
||||
std::string type;
|
||||
UiRect rect;
|
||||
std::string name;
|
||||
LayoutType layoutType = LayoutType::Frame;
|
||||
Orientation orientation = Orientation::Vertical;
|
||||
float spacing = 0.0f;
|
||||
|
||||
LayoutSettings layoutSettings;
|
||||
|
||||
// Внутренние вычисленные координаты для OpenGL
|
||||
// Именно их мы передаем в Vertex Buffer при buildMesh()
|
||||
UiRect screenRect;
|
||||
|
||||
// Данные из JSON (желаемые размеры и смещения)
|
||||
float localX = 0;
|
||||
float localY = 0;
|
||||
float width = 0;
|
||||
float height = 0;
|
||||
|
||||
// Иерархия
|
||||
std::vector<std::shared_ptr<UiNode>> children;
|
||||
|
||||
// Компоненты (только один из них обычно активен для ноды)
|
||||
std::shared_ptr<UiButton> button;
|
||||
std::shared_ptr<UiSlider> slider;
|
||||
std::shared_ptr<UiTextView> textView;
|
||||
std::shared_ptr<UiTextField> textField;
|
||||
std::string orientation = "vertical";
|
||||
float spacing = 0.0f;
|
||||
|
||||
// Анимации
|
||||
struct AnimStep {
|
||||
std::string type;
|
||||
float toX = 0.0f;
|
||||
float toY = 0.0f;
|
||||
float toScale = 1.0f; // Полезно добавить для UI
|
||||
float durationMs = 0.0f;
|
||||
std::string easing = "linear";
|
||||
};
|
||||
@@ -200,9 +259,11 @@ namespace ZL {
|
||||
bool startAnimationOnNode(const std::string& nodeName, const std::string& animName);
|
||||
bool stopAnimationOnNode(const std::string& nodeName, const std::string& animName);
|
||||
bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function<void()> cb);
|
||||
void updateAllLayouts();
|
||||
|
||||
private:
|
||||
void layoutNode(const std::shared_ptr<UiNode>& node);
|
||||
void layoutNode(const std::shared_ptr<UiNode>& node, float parentX, float parentY, float parentW, float parentH, float finalLocalX, float finalLocalY);
|
||||
void syncComponentRects(const std::shared_ptr<UiNode>& node);
|
||||
void collectButtonsAndSliders(const std::shared_ptr<UiNode>& node);
|
||||
|
||||
struct ActiveAnim {
|
||||
|
||||
+45
-22
@@ -44,20 +44,34 @@ EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void
|
||||
return EM_TRUE;
|
||||
}
|
||||
|
||||
// Resize the canvas, notify SDL, and push a synthetic SDL_WINDOWEVENT_RESIZED
|
||||
// so Game::update()'s existing handler updates Environment::width/height and clears caches.
|
||||
static void applyResize(int w, int h) {
|
||||
if (w <= 0 || h <= 0) return;
|
||||
// Resize the actual WebGL canvas — without this the rendered pixels stay at
|
||||
// the original size no matter what Environment::width/height say.
|
||||
emscripten_set_canvas_element_size("#canvas", w, h);
|
||||
if (ZL::Environment::window)
|
||||
SDL_SetWindowSize(ZL::Environment::window, w, h);
|
||||
static void applyResize(int logicalW, int logicalH) {
|
||||
// Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung)
|
||||
double dpr = emscripten_get_device_pixel_ratio();
|
||||
|
||||
// Вычисляем реальные физические пиксели
|
||||
int physicalW = static_cast<int>(logicalW * dpr);
|
||||
int physicalH = static_cast<int>(logicalH * dpr);
|
||||
|
||||
// Устанавливаем размер внутреннего буфера канваса
|
||||
emscripten_set_canvas_element_size("#canvas", physicalW, physicalH);
|
||||
|
||||
// Сообщаем SDL о новом размере.
|
||||
// ВАЖНО: SDL2 в Emscripten ожидает здесь именно физические пиксели
|
||||
// для корректной работы последующих вызовов glViewport.
|
||||
if (ZL::Environment::window) {
|
||||
SDL_SetWindowSize(ZL::Environment::window, physicalW, physicalH);
|
||||
}
|
||||
|
||||
// Обновляем ваши внутренние переменные окружения
|
||||
ZL::Environment::width = physicalW;
|
||||
ZL::Environment::height = physicalH;
|
||||
|
||||
// Пушим событие, чтобы движок пересчитал матрицы проекции
|
||||
SDL_Event e = {};
|
||||
e.type = SDL_WINDOWEVENT;
|
||||
e.window.event = SDL_WINDOWEVENT_RESIZED;
|
||||
e.window.data1 = w;
|
||||
e.window.data2 = h;
|
||||
e.window.data1 = physicalW;
|
||||
e.window.data2 = physicalH;
|
||||
SDL_PushEvent(&e);
|
||||
}
|
||||
|
||||
@@ -69,17 +83,11 @@ EM_BOOL onWindowResized(int /*eventType*/, const EmscriptenUiEvent* e, void* /*u
|
||||
}
|
||||
|
||||
EM_BOOL onFullscreenChanged(int /*eventType*/, const EmscriptenFullscreenChangeEvent* e, void* /*userData*/) {
|
||||
if (e->isFullscreen) {
|
||||
// e->screenWidth/screenHeight comes from screen.width/screen.height in JS,
|
||||
// which on mobile browsers returns physical pixels (e.g. 2340x1080),
|
||||
// causing the canvas to extend far off-screen. window.innerWidth/innerHeight
|
||||
// always gives CSS logical pixels and is correct on both desktop and mobile.
|
||||
int w = EM_ASM_INT({ return window.innerWidth; });
|
||||
int h = EM_ASM_INT({ return window.innerHeight; });
|
||||
applyResize(w, h);
|
||||
}
|
||||
// Exiting fullscreen: the browser fires a window resize event next,
|
||||
// which onWindowResized handles automatically.
|
||||
// Вместо window.innerWidth, попробуйте запросить размер целевого элемента
|
||||
// так как после перехода в FS именно он растягивается на весь экран.
|
||||
double clientW, clientH;
|
||||
emscripten_get_element_css_size("#canvas", &clientW, &clientH);
|
||||
applyResize(clientW, clientH);
|
||||
return EM_FALSE;
|
||||
}
|
||||
|
||||
@@ -226,6 +234,21 @@ int main(int argc, char *argv[]) {
|
||||
// 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
|
||||
|
||||
@@ -27,6 +27,14 @@ constexpr long long SERVER_DELAY = 0; //ms
|
||||
constexpr long long CLIENT_DELAY = 500; //ms
|
||||
constexpr long long CUTOFF_TIME = 5000; //ms
|
||||
|
||||
constexpr float PROJECTILE_VELOCITY = 600.f;
|
||||
constexpr float PROJECTILE_LIFE = 15000.f; //ms
|
||||
|
||||
const float projectileHitRadius = 1.5f * 5;
|
||||
const float boxCollisionRadius = 2.0f * 5;
|
||||
const float shipCollisionRadius = 15.0f * 5;
|
||||
const float npcCollisionRadius = 5.0f * 5;
|
||||
|
||||
uint32_t fnv1a_hash(const std::string& data);
|
||||
|
||||
struct ClientState {
|
||||
|
||||
@@ -21,8 +21,8 @@ namespace ZL {
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
|
||||
const float MIN_COORD = -100.0f;
|
||||
const float MAX_COORD = 100.0f;
|
||||
const float MIN_COORD = -1000.0f;
|
||||
const float MAX_COORD = 1000.0f;
|
||||
const float MIN_DISTANCE = 3.0f;
|
||||
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
|
||||
const int MAX_ATTEMPTS = 1000;
|
||||
@@ -68,7 +68,7 @@ namespace ZL {
|
||||
Eigen::Vector3f LocalClient::generateRandomPosition() {
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::uniform_real_distribution<> distrib(-500.0, 500.0);
|
||||
std::uniform_real_distribution<> distrib(-5000.0, 5000.0);
|
||||
|
||||
return Eigen::Vector3f(
|
||||
(float)distrib(gen),
|
||||
@@ -238,11 +238,6 @@ namespace ZL {
|
||||
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
|
||||
const float projectileHitRadius = 1.5f;
|
||||
const float boxCollisionRadius = 2.0f;
|
||||
const float shipCollisionRadius = 15.0f;
|
||||
const float npcCollisionRadius = 5.0f;
|
||||
|
||||
std::vector<std::pair<size_t, size_t>> boxProjectileCollisions;
|
||||
|
||||
for (size_t bi = 0; bi < serverBoxes.size(); ++bi) {
|
||||
|
||||
@@ -362,9 +362,10 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
|
||||
// 4. Рендеринг
|
||||
r->shaderManager.PushShader(shaderName);
|
||||
|
||||
// Матрица проекции (экрана)
|
||||
float W = (float)Environment::width;
|
||||
float H = (float)Environment::height;
|
||||
// Матрица проекции — используем виртуальные проекционные размеры,
|
||||
// чтобы координаты текста были независимы от физического разрешения экрана.
|
||||
float W = Environment::projectionWidth;
|
||||
float H = Environment::projectionHeight;
|
||||
Eigen::Matrix4f proj = Eigen::Matrix4f::Identity();
|
||||
proj(0, 0) = 2.0f / W;
|
||||
proj(1, 1) = 2.0f / H;
|
||||
|
||||
Reference in new issue
Block a user