merge
This commit is contained in:
commit
0a5a3b653d
@ -448,6 +448,10 @@ add_executable(space-game001
|
||||
StoneObject.h
|
||||
FrameBuffer.cpp
|
||||
FrameBuffer.h
|
||||
UiManager.cpp
|
||||
UiManager.h
|
||||
Projectile.h
|
||||
Projectile.cpp
|
||||
)
|
||||
|
||||
# Установка проекта по умолчанию для Visual Studio
|
||||
|
||||
322
Game.cpp
322
Game.cpp
@ -107,11 +107,10 @@ namespace ZL
|
||||
, newTickCount(0)
|
||||
, lastTickCount(0)
|
||||
{
|
||||
std::vector<Vector3f> emissionPoints = {
|
||||
Vector3f{-2.1f, 0.9f, 5.0f},
|
||||
Vector3f{2.1f, 0.9f, 5.0f}
|
||||
};
|
||||
sparkEmitter = SparkEmitter(emissionPoints, 100.0f);
|
||||
projectiles.reserve(maxProjectiles);
|
||||
for (int i = 0; i < maxProjectiles; ++i) {
|
||||
projectiles.emplace_back(std::make_unique<Projectile>());
|
||||
}
|
||||
}
|
||||
|
||||
Game::~Game() {
|
||||
@ -152,6 +151,45 @@ namespace ZL
|
||||
|
||||
#endif
|
||||
|
||||
bool cfgLoaded = sparkEmitter.loadFromJsonFile("../config/spark_config.json", renderer, CONST_ZIP_FILE);
|
||||
bool projCfgLoaded = projectileEmitter.loadFromJsonFile("../config/spark_projectile_config.json", renderer, CONST_ZIP_FILE);
|
||||
projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||||
uiManager.loadFromFile("../config/ui.json", renderer, CONST_ZIP_FILE);
|
||||
|
||||
uiManager.setButtonCallback("playButton", [this](const std::string& name) {
|
||||
std::cerr << "Play button pressed: " << name << std::endl;
|
||||
});
|
||||
|
||||
uiManager.setButtonCallback("exitButton", [](const std::string& name) {
|
||||
Environment::exitGameLoop = true;
|
||||
});
|
||||
|
||||
uiManager.setButtonCallback("settingsButton", [this](const std::string& name) {
|
||||
if (uiManager.pushMenuFromFile("../config/settings.json", this->renderer, CONST_ZIP_FILE)) {
|
||||
|
||||
uiManager.setButtonCallback("Opt1", [this](const std::string& n) {
|
||||
std::cerr << "Opt1 pressed: " << n << std::endl;
|
||||
});
|
||||
|
||||
uiManager.setButtonCallback("Opt2", [this](const std::string& n) {
|
||||
std::cerr << "Opt2 pressed: " << n << std::endl;
|
||||
});
|
||||
|
||||
uiManager.setButtonCallback("backButton", [this](const std::string& n) {
|
||||
uiManager.popMenu();
|
||||
});
|
||||
}
|
||||
else {
|
||||
std::cerr << "Failed to open settings menu" << std::endl;
|
||||
}
|
||||
});
|
||||
|
||||
uiManager.setSliderCallback("musicVolumeSlider", [this](const std::string& name, float value) {
|
||||
std::cerr << "Music volume slider changed to: " << value << std::endl;
|
||||
musicVolume = value;
|
||||
Environment::shipVelocity = musicVolume * 20.0f;
|
||||
});
|
||||
|
||||
cubemapTexture = std::make_shared<Texture>(
|
||||
std::array<TextureDataStruct, 6>{
|
||||
CreateTextureDataFromBmp24("./resources/sky/space_rt.bmp", CONST_ZIP_FILE),
|
||||
@ -192,67 +230,68 @@ namespace ZL
|
||||
boxRenderArr[i].RefreshVBO();
|
||||
}
|
||||
|
||||
sparkTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/spark.png", CONST_ZIP_FILE));
|
||||
|
||||
sparkEmitter.setTexture(sparkTexture);
|
||||
if (!cfgLoaded)
|
||||
{
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
|
||||
buttonTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/button.png", CONST_ZIP_FILE));
|
||||
/* buttonTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/button.png", CONST_ZIP_FILE));
|
||||
|
||||
button.data.PositionData.push_back({ 100, 100, 0 });
|
||||
button.data.PositionData.push_back({ 100, 150, 0 });
|
||||
button.data.PositionData.push_back({ 300, 150, 0 });
|
||||
button.data.PositionData.push_back({ 100, 100, 0 });
|
||||
button.data.PositionData.push_back({ 300, 150, 0 });
|
||||
button.data.PositionData.push_back({ 300, 100, 0 });
|
||||
button.data.PositionData.push_back({ 100, 100, 0 });
|
||||
button.data.PositionData.push_back({ 100, 150, 0 });
|
||||
button.data.PositionData.push_back({ 300, 150, 0 });
|
||||
button.data.PositionData.push_back({ 100, 100, 0 });
|
||||
button.data.PositionData.push_back({ 300, 150, 0 });
|
||||
button.data.PositionData.push_back({ 300, 100, 0 });
|
||||
|
||||
button.data.TexCoordData.push_back({ 0,0 });
|
||||
button.data.TexCoordData.push_back({ 0,1 });
|
||||
button.data.TexCoordData.push_back({ 1,1 });
|
||||
button.data.TexCoordData.push_back({ 0,0 });
|
||||
button.data.TexCoordData.push_back({ 1,1 });
|
||||
button.data.TexCoordData.push_back({ 1,0 });
|
||||
button.data.TexCoordData.push_back({ 0,0 });
|
||||
button.data.TexCoordData.push_back({ 0,1 });
|
||||
button.data.TexCoordData.push_back({ 1,1 });
|
||||
button.data.TexCoordData.push_back({ 0,0 });
|
||||
button.data.TexCoordData.push_back({ 1,1 });
|
||||
button.data.TexCoordData.push_back({ 1,0 });
|
||||
|
||||
button.RefreshVBO();
|
||||
button.RefreshVBO();*/
|
||||
/*
|
||||
musicVolumeBarTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/musicVolumeBarTexture.png", CONST_ZIP_FILE));
|
||||
|
||||
musicVolumeBarTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/musicVolumeBarTexture.png", CONST_ZIP_FILE));
|
||||
musicVolumeBar.data.PositionData.push_back({ 1190, 100, 0 });
|
||||
musicVolumeBar.data.PositionData.push_back({ 1190, 600, 0 });
|
||||
musicVolumeBar.data.PositionData.push_back({ 1200, 600, 0 });
|
||||
musicVolumeBar.data.PositionData.push_back({ 1190, 100, 0 });
|
||||
musicVolumeBar.data.PositionData.push_back({ 1200, 600, 0 });
|
||||
musicVolumeBar.data.PositionData.push_back({ 1200, 100, 0 });
|
||||
|
||||
musicVolumeBar.data.PositionData.push_back({ 1190, 100, 0 });
|
||||
musicVolumeBar.data.PositionData.push_back({ 1190, 600, 0 });
|
||||
musicVolumeBar.data.PositionData.push_back({ 1200, 600, 0 });
|
||||
musicVolumeBar.data.PositionData.push_back({ 1190, 100, 0 });
|
||||
musicVolumeBar.data.PositionData.push_back({ 1200, 600, 0 });
|
||||
musicVolumeBar.data.PositionData.push_back({ 1200, 100, 0 });
|
||||
musicVolumeBar.data.TexCoordData.push_back({ 0,0 });
|
||||
musicVolumeBar.data.TexCoordData.push_back({ 0,1 });
|
||||
musicVolumeBar.data.TexCoordData.push_back({ 1,1 });
|
||||
musicVolumeBar.data.TexCoordData.push_back({ 0,0 });
|
||||
musicVolumeBar.data.TexCoordData.push_back({ 1,1 });
|
||||
musicVolumeBar.data.TexCoordData.push_back({ 1,0 });
|
||||
|
||||
musicVolumeBar.data.TexCoordData.push_back({ 0,0 });
|
||||
musicVolumeBar.data.TexCoordData.push_back({ 0,1 });
|
||||
musicVolumeBar.data.TexCoordData.push_back({ 1,1 });
|
||||
musicVolumeBar.data.TexCoordData.push_back({ 0,0 });
|
||||
musicVolumeBar.data.TexCoordData.push_back({ 1,1 });
|
||||
musicVolumeBar.data.TexCoordData.push_back({ 1,0 });
|
||||
|
||||
musicVolumeBar.RefreshVBO();
|
||||
musicVolumeBar.RefreshVBO();
|
||||
|
||||
|
||||
musicVolumeBarButtonTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/musicVolumeBarButton.png", CONST_ZIP_FILE));
|
||||
musicVolumeBarButtonTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/musicVolumeBarButton.png", CONST_ZIP_FILE));
|
||||
|
||||
float musicVolumeBarButtonButtonCenterY = 350.0f;
|
||||
float musicVolumeBarButtonButtonCenterY = 350.0f;
|
||||
|
||||
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
|
||||
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
|
||||
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
|
||||
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
|
||||
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
|
||||
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
|
||||
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
|
||||
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
|
||||
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
|
||||
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
|
||||
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
|
||||
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
|
||||
|
||||
musicVolumeBarButton.data.TexCoordData.push_back({ 0,0 });
|
||||
musicVolumeBarButton.data.TexCoordData.push_back({ 0,1 });
|
||||
musicVolumeBarButton.data.TexCoordData.push_back({ 1,1 });
|
||||
musicVolumeBarButton.data.TexCoordData.push_back({ 0,0 });
|
||||
musicVolumeBarButton.data.TexCoordData.push_back({ 1,1 });
|
||||
musicVolumeBarButton.data.TexCoordData.push_back({ 1,0 });
|
||||
musicVolumeBarButton.data.TexCoordData.push_back({ 0,0 });
|
||||
musicVolumeBarButton.data.TexCoordData.push_back({ 0,1 });
|
||||
musicVolumeBarButton.data.TexCoordData.push_back({ 1,1 });
|
||||
musicVolumeBarButton.data.TexCoordData.push_back({ 0,0 });
|
||||
musicVolumeBarButton.data.TexCoordData.push_back({ 1,1 });
|
||||
musicVolumeBarButton.data.TexCoordData.push_back({ 1,0 });
|
||||
|
||||
musicVolumeBarButton.RefreshVBO();
|
||||
musicVolumeBarButton.RefreshVBO();*/
|
||||
renderer.InitOpenGL();
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
@ -320,10 +359,18 @@ namespace ZL
|
||||
renderer.LoadIdentity();
|
||||
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
||||
|
||||
|
||||
renderer.TranslateMatrix({ 0, -Environment::zoom * 0.03f, 0 });
|
||||
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
|
||||
renderer.DrawVertexRenderStruct(spaceship);
|
||||
|
||||
for (const auto& p : projectiles) {
|
||||
if (p && p->isActive()) {
|
||||
p->draw(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||
projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||
|
||||
renderer.PopMatrix();
|
||||
renderer.PopProjectionMatrix();
|
||||
@ -376,7 +423,7 @@ namespace ZL
|
||||
renderer.shaderManager.PopShader();
|
||||
CheckGlError();
|
||||
}
|
||||
void Game::UpdateVolumeKnob() {
|
||||
/*void Game::UpdateVolumeKnob() {
|
||||
float musicVolumeBarButtonButtonCenterY = volumeBarMinY + musicVolume * (volumeBarMaxY - volumeBarMinY);
|
||||
|
||||
auto& pos = musicVolumeBarButton.data.PositionData;
|
||||
@ -391,7 +438,6 @@ namespace ZL
|
||||
musicVolumeBarButton.RefreshVBO();
|
||||
|
||||
}
|
||||
|
||||
void Game::UpdateVolumeFromMouse(int mouseX, int mouseY) {
|
||||
|
||||
int uiX = mouseX;
|
||||
@ -407,7 +453,8 @@ namespace ZL
|
||||
if (t > 1.0f) t = 1.0f;
|
||||
musicVolume = t;
|
||||
UpdateVolumeKnob();
|
||||
}
|
||||
}*/
|
||||
|
||||
void Game::drawUI()
|
||||
{
|
||||
static const std::string defaultShaderName = "default";
|
||||
@ -416,7 +463,6 @@ namespace ZL
|
||||
static const std::string vTexCoordName = "vTexCoord";
|
||||
static const std::string textureUniformName = "Texture";
|
||||
|
||||
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
renderer.shaderManager.PushShader(defaultShaderName);
|
||||
@ -424,26 +470,27 @@ namespace ZL
|
||||
renderer.EnableVertexAttribArray(vPositionName);
|
||||
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||
|
||||
renderer.PushProjectionMatrix(Environment::width, Environment::height, -1, 1);
|
||||
renderer.PushMatrix();
|
||||
//renderer.PushProjectionMatrix(Environment::width, Environment::height, -1, 1);
|
||||
//renderer.PushMatrix();
|
||||
|
||||
renderer.LoadIdentity();
|
||||
//renderer.LoadIdentity();
|
||||
|
||||
//glBindTexture(GL_TEXTURE_2D, buttonTexture->getTexID());
|
||||
//renderer.DrawVertexRenderStruct(button);
|
||||
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, buttonTexture->getTexID());
|
||||
renderer.DrawVertexRenderStruct(button);
|
||||
//glBindTexture(GL_TEXTURE_2D, musicVolumeBarTexture->getTexID());
|
||||
//renderer.DrawVertexRenderStruct(musicVolumeBar);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, musicVolumeBarTexture->getTexID());
|
||||
renderer.DrawVertexRenderStruct(musicVolumeBar);
|
||||
//glBindTexture(GL_TEXTURE_2D, musicVolumeBarButtonTexture->getTexID());
|
||||
//renderer.DrawVertexRenderStruct(musicVolumeBarButton);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, musicVolumeBarButtonTexture->getTexID());
|
||||
renderer.DrawVertexRenderStruct(musicVolumeBarButton);
|
||||
//renderer.PopMatrix();
|
||||
//renderer.PopProjectionMatrix();
|
||||
|
||||
renderer.PopMatrix();
|
||||
renderer.PopProjectionMatrix();
|
||||
renderer.DisableVertexAttribArray(vPositionName);
|
||||
renderer.DisableVertexAttribArray(vTexCoordName);
|
||||
|
||||
uiManager.draw(renderer);
|
||||
renderer.shaderManager.PopShader();
|
||||
CheckGlError();
|
||||
}
|
||||
@ -513,7 +560,6 @@ namespace ZL
|
||||
float diffx = Environment::tapDownCurrentPos.v[0] - Environment::tapDownStartPos.v[0];
|
||||
float diffy = Environment::tapDownCurrentPos.v[1] - Environment::tapDownStartPos.v[1];
|
||||
|
||||
|
||||
if (abs(diffy) > 5.0 || abs(diffx) > 5.0) //threshold
|
||||
{
|
||||
|
||||
@ -545,14 +591,74 @@ namespace ZL
|
||||
{
|
||||
Vector3f velocityDirection = { 0,0, -Environment::shipVelocity * delta / 1000.f };
|
||||
Vector3f velocityDirectionAdjusted = MultMatrixVector(Environment::shipMatrix, velocityDirection);
|
||||
|
||||
Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted;
|
||||
}
|
||||
|
||||
for (auto& p : projectiles) {
|
||||
if (p && p->isActive()) {
|
||||
p->update(static_cast<float>(delta), renderer);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Vector3f> projCameraPoints;
|
||||
for (const auto& p : projectiles) {
|
||||
if (p && p->isActive()) {
|
||||
Vector3f worldPos = p->getPosition();
|
||||
Vector3f rel = worldPos - Environment::shipPosition;
|
||||
Vector3f camPos = MultMatrixVector(Environment::inverseShipMatrix, rel);
|
||||
projCameraPoints.push_back(camPos);
|
||||
}
|
||||
}
|
||||
if (!projCameraPoints.empty()) {
|
||||
projectileEmitter.setEmissionPoints(projCameraPoints);
|
||||
projectileEmitter.emit();
|
||||
}
|
||||
else {
|
||||
projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||||
}
|
||||
|
||||
std::vector<Vector3f> shipCameraPoints;
|
||||
for (const auto& lp : shipLocalEmissionPoints) {
|
||||
Vector3f adjusted = lp + Vector3f{ 0.0f, -Environment::zoom * 0.03f, 0.0f };
|
||||
shipCameraPoints.push_back(adjusted);
|
||||
}
|
||||
if (!shipCameraPoints.empty()) {
|
||||
sparkEmitter.setEmissionPoints(shipCameraPoints);
|
||||
}
|
||||
|
||||
sparkEmitter.update(static_cast<float>(delta));
|
||||
projectileEmitter.update(static_cast<float>(delta));
|
||||
|
||||
lastTickCount = newTickCount;
|
||||
}
|
||||
}
|
||||
|
||||
void Game::fireProjectiles() {
|
||||
std::vector<Vector3f> localOffsets = {
|
||||
Vector3f{ -1.5f, 0.9f, 5.0f },
|
||||
Vector3f{ 1.5f, 0.9f, 5.0f }
|
||||
};
|
||||
|
||||
const float projectileSpeed = 60.0f;
|
||||
const float lifeMs = 5000.0f;
|
||||
const float size = 0.5f;
|
||||
|
||||
Vector3f localForward = { 0,0,-1 };
|
||||
Vector3f worldForward = MultMatrixVector(Environment::shipMatrix, localForward).normalized();
|
||||
|
||||
for (const auto& lo : localOffsets) {
|
||||
Vector3f worldPos = Environment::shipPosition + MultMatrixVector(Environment::shipMatrix, lo);
|
||||
Vector3f worldVel = worldForward * projectileSpeed;
|
||||
|
||||
for (auto& p : projectiles) {
|
||||
if (!p->isActive()) {
|
||||
p->init(worldPos, worldVel, lifeMs, size, projectileTexture, renderer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Game::render() {
|
||||
SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
|
||||
ZL::CheckGlError();
|
||||
@ -570,51 +676,73 @@ namespace ZL
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SDL_QUIT) {
|
||||
Environment::exitGameLoop = true;
|
||||
|
||||
}
|
||||
else if (event.type == SDL_MOUSEBUTTONDOWN) {
|
||||
// 1. Îáðàáîòêà íàæàòèÿ êíîïêè ìûøè
|
||||
|
||||
int mx = event.button.x;
|
||||
int my = event.button.y;
|
||||
|
||||
std::cout << mx << " " << my << '\n';
|
||||
int uiX = mx;
|
||||
int uiY = Environment::height - my;
|
||||
if (uiX >= volumeBarMinX - 40 && uiX <= volumeBarMaxX + 40 &&
|
||||
uiY >= volumeBarMinY - 40 && uiY <= volumeBarMaxY + 40) {
|
||||
isDraggingVolume = true;
|
||||
UpdateVolumeFromMouse(mx, my);
|
||||
}
|
||||
else {
|
||||
Environment::tapDownHold = true;
|
||||
// Êîîðäèíàòû íà÷àëüíîãî íàæàòèÿ
|
||||
Environment::tapDownStartPos.v[0] = event.button.x;
|
||||
Environment::tapDownStartPos.v[1] = event.button.y;
|
||||
// Íà÷àëüíàÿ ïîçèöèÿ òàêæå ñòàíîâèòñÿ òåêóùåé
|
||||
Environment::tapDownCurrentPos.v[0] = event.button.x;
|
||||
Environment::tapDownCurrentPos.v[1] = event.button.y;
|
||||
|
||||
uiManager.onMouseDown(uiX, uiY);
|
||||
|
||||
bool uiHandled = false;
|
||||
|
||||
if (event.button.button == SDL_BUTTON_LEFT && !uiManager.isUiInteraction()) {
|
||||
uint64_t now = SDL_GetTicks64();
|
||||
if (now - lastProjectileFireTime >= static_cast<uint64_t>(projectileCooldownMs)) {
|
||||
lastProjectileFireTime = now;
|
||||
fireProjectiles();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& button : uiManager.findButton("") ? std::vector<std::shared_ptr<UiButton>>{} : std::vector<std::shared_ptr<UiButton>>{}) {
|
||||
(void)button;
|
||||
}
|
||||
|
||||
auto pressedSlider = [&]() -> std::shared_ptr<UiSlider> {
|
||||
for (const auto& slider : uiManager.findSlider("") ? std::vector<std::shared_ptr<UiSlider>>{} : std::vector<std::shared_ptr<UiSlider>>{}) {
|
||||
(void)slider;
|
||||
}
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
if (!uiManager.isUiInteraction()) {
|
||||
Environment::tapDownHold = true;
|
||||
// Êîîðäèíàòû íà÷àëüíîãî íàæàòèÿ
|
||||
Environment::tapDownStartPos.v[0] = mx;
|
||||
Environment::tapDownStartPos.v[1] = my;
|
||||
// Íà÷àëüíàÿ ïîçèöèÿ òàêæå ñòàíîâèòñÿ òåêóùåé
|
||||
Environment::tapDownCurrentPos.v[0] = mx;
|
||||
Environment::tapDownCurrentPos.v[1] = my;
|
||||
}
|
||||
}
|
||||
else if (event.type == SDL_MOUSEBUTTONUP) {
|
||||
// 2. Îáðàáîòêà îòïóñêàíèÿ êíîïêè ìûøè
|
||||
isDraggingVolume = false;
|
||||
Environment::tapDownHold = false;
|
||||
int mx = event.button.x;
|
||||
int my = event.button.y;
|
||||
int uiX = mx;
|
||||
int uiY = Environment::height - my;
|
||||
|
||||
uiManager.onMouseUp(uiX, uiY);
|
||||
|
||||
if (!uiManager.isUiInteraction()) {
|
||||
Environment::tapDownHold = false;
|
||||
}
|
||||
}
|
||||
else if (event.type == SDL_MOUSEMOTION) {
|
||||
// 3. Îáðàáîòêà ïåðåìåùåíèÿ ìûøè
|
||||
int mx = event.motion.x;
|
||||
int my = event.motion.y;
|
||||
int uiX = mx;
|
||||
int uiY = Environment::height - my;
|
||||
|
||||
if (isDraggingVolume) {
|
||||
// Äâèãàåì ìûøü ïî ñëàéäåðó — ìåíÿåì ãðîìêîñòü è ïîçèöèþ êðóæêà
|
||||
UpdateVolumeFromMouse(mx, my);
|
||||
}
|
||||
if (Environment::tapDownHold) {
|
||||
// Îáíîâëåíèå òåêóùåé ïîçèöèè, åñëè êíîïêà óäåðæèâàåòñÿ
|
||||
Environment::tapDownCurrentPos.v[0] = event.motion.x;
|
||||
Environment::tapDownCurrentPos.v[1] = event.motion.y;
|
||||
uiManager.onMouseMove(uiX, uiY);
|
||||
|
||||
if (Environment::tapDownHold && !uiManager.isUiInteraction()) {
|
||||
Environment::tapDownCurrentPos.v[0] = mx;
|
||||
Environment::tapDownCurrentPos.v[1] = my;
|
||||
}
|
||||
}
|
||||
else if (event.type == SDL_MOUSEWHEEL) {
|
||||
|
||||
37
Game.h
37
Game.h
@ -6,6 +6,8 @@
|
||||
#include "TextureManager.h"
|
||||
#include "SparkEmitter.h"
|
||||
#include "PlanetObject.h"
|
||||
#include "UiManager.h"
|
||||
#include "Projectile.h"
|
||||
|
||||
namespace ZL {
|
||||
|
||||
@ -37,6 +39,8 @@ namespace ZL {
|
||||
void drawBoxes();
|
||||
void drawUI();
|
||||
|
||||
void fireProjectiles();
|
||||
|
||||
SDL_Window* window;
|
||||
SDL_GLContext glContext;
|
||||
Renderer renderer;
|
||||
@ -52,26 +56,28 @@ namespace ZL {
|
||||
std::vector<VertexRenderStruct> boxRenderArr;
|
||||
|
||||
|
||||
std::shared_ptr<Texture> buttonTexture;
|
||||
VertexRenderStruct button;
|
||||
//std::shared_ptr<Texture> buttonTexture;
|
||||
//VertexRenderStruct button;
|
||||
|
||||
std::shared_ptr<Texture> musicVolumeBarTexture;
|
||||
VertexRenderStruct musicVolumeBar;
|
||||
//std::shared_ptr<Texture> musicVolumeBarTexture;
|
||||
//VertexRenderStruct musicVolumeBar;
|
||||
|
||||
std::shared_ptr<Texture> musicVolumeBarButtonTexture;
|
||||
VertexRenderStruct musicVolumeBarButton;
|
||||
//std::shared_ptr<Texture> musicVolumeBarButtonTexture;
|
||||
//VertexRenderStruct musicVolumeBarButton;
|
||||
|
||||
|
||||
bool isDraggingVolume = false;
|
||||
//bool isDraggingVolume = false;
|
||||
|
||||
float musicVolume = 0.0f;
|
||||
float volumeBarMinX = 1190.0f;
|
||||
float volumeBarMaxX = 1200.0f;
|
||||
float volumeBarMinY = 100.0f;
|
||||
float volumeBarMaxY = 600.0f;
|
||||
float musicVolumeBarButtonButtonCenterX = 1195.0f;
|
||||
float musicVolumeBarButtonButtonRadius = 25.0f;
|
||||
void UpdateVolumeFromMouse(int mouseX, int mouseY);
|
||||
void UpdateVolumeKnob();
|
||||
//float musicVolumeBarButtonButtonCenterX = 1195.0f;
|
||||
//float musicVolumeBarButtonButtonRadius = 25.0f;
|
||||
//void UpdateVolumeFromMouse(int mouseX, int mouseY);
|
||||
//void UpdateVolumeKnob();
|
||||
|
||||
|
||||
static const size_t CONST_TIMER_INTERVAL = 10;
|
||||
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
|
||||
@ -88,7 +94,16 @@ namespace ZL {
|
||||
VertexDataStruct boxBase;
|
||||
|
||||
SparkEmitter sparkEmitter;
|
||||
SparkEmitter projectileEmitter;
|
||||
PlanetObject planetObject;
|
||||
UiManager uiManager;
|
||||
|
||||
std::vector<std::unique_ptr<Projectile>> projectiles;
|
||||
std::shared_ptr<Texture> projectileTexture;
|
||||
float projectileCooldownMs = 500.0f;
|
||||
uint64_t lastProjectileFireTime = 0;
|
||||
int maxProjectiles = 32;
|
||||
std::vector<Vector3f> shipLocalEmissionPoints;
|
||||
};
|
||||
|
||||
|
||||
|
||||
71
Projectile.cpp
Normal file
71
Projectile.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
#include "Projectile.h"
|
||||
|
||||
namespace ZL {
|
||||
|
||||
Projectile::Projectile()
|
||||
: pos({ 0,0,0 })
|
||||
, vel({ 0,0,0 })
|
||||
, life(0.0f)
|
||||
, maxLife(0.0f)
|
||||
, active(false)
|
||||
, size(0.5f)
|
||||
, texture(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void Projectile::init(const Vector3f& startPos, const Vector3f& startVel, float lifeMs, float s, std::shared_ptr<Texture> tex, Renderer& renderer) {
|
||||
pos = startPos;
|
||||
vel = startVel;
|
||||
life = 0.0f;
|
||||
maxLife = lifeMs;
|
||||
size = s;
|
||||
texture = tex;
|
||||
active = true;
|
||||
|
||||
rebuildMesh(renderer);
|
||||
mesh.RefreshVBO();
|
||||
}
|
||||
|
||||
void Projectile::update(float deltaMs, Renderer& renderer) {
|
||||
if (!active) return;
|
||||
|
||||
pos = pos + vel * (deltaMs / 1000.0f);
|
||||
life += deltaMs;
|
||||
if (life >= maxLife) {
|
||||
active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
rebuildMesh(renderer);
|
||||
mesh.RefreshVBO();
|
||||
}
|
||||
|
||||
void Projectile::rebuildMesh(Renderer&) {
|
||||
float half = size * 0.5f;
|
||||
|
||||
mesh.data.PositionData.clear();
|
||||
mesh.data.TexCoordData.clear();
|
||||
|
||||
mesh.data.PositionData.push_back({ pos.v[0] - half, pos.v[1] - half, pos.v[2] });
|
||||
mesh.data.PositionData.push_back({ pos.v[0] - half, pos.v[1] + half, pos.v[2] });
|
||||
mesh.data.PositionData.push_back({ pos.v[0] + half, pos.v[1] + half, pos.v[2] });
|
||||
|
||||
mesh.data.PositionData.push_back({ pos.v[0] - half, pos.v[1] - half, pos.v[2] });
|
||||
mesh.data.PositionData.push_back({ pos.v[0] + half, pos.v[1] + half, pos.v[2] });
|
||||
mesh.data.PositionData.push_back({ pos.v[0] + half, pos.v[1] - half, pos.v[2] });
|
||||
|
||||
mesh.data.TexCoordData.push_back({ 0.0f, 0.0f });
|
||||
mesh.data.TexCoordData.push_back({ 0.0f, 1.0f });
|
||||
mesh.data.TexCoordData.push_back({ 1.0f, 1.0f });
|
||||
mesh.data.TexCoordData.push_back({ 0.0f, 0.0f });
|
||||
mesh.data.TexCoordData.push_back({ 1.0f, 1.0f });
|
||||
mesh.data.TexCoordData.push_back({ 1.0f, 0.0f });
|
||||
}
|
||||
|
||||
void Projectile::draw(Renderer& renderer) const {
|
||||
if (!active || !texture) return;
|
||||
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
||||
renderer.DrawVertexRenderStruct(mesh);
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
36
Projectile.h
Normal file
36
Projectile.h
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "ZLMath.h"
|
||||
#include "Renderer.h"
|
||||
#include "TextureManager.h"
|
||||
#include <memory>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
class Projectile {
|
||||
public:
|
||||
Projectile();
|
||||
~Projectile() = default;
|
||||
|
||||
void init(const Vector3f& startPos, const Vector3f& startVel, float lifeMs, float size, std::shared_ptr<Texture> tex, Renderer& renderer);
|
||||
void update(float deltaMs, Renderer& renderer);
|
||||
void draw(Renderer& renderer) const;
|
||||
|
||||
bool isActive() const { return active; }
|
||||
|
||||
Vector3f getPosition() const { return pos; }
|
||||
|
||||
private:
|
||||
Vector3f pos;
|
||||
Vector3f vel;
|
||||
float life;
|
||||
float maxLife;
|
||||
bool active;
|
||||
float size;
|
||||
VertexRenderStruct mesh;
|
||||
std::shared_ptr<Texture> texture;
|
||||
|
||||
void rebuildMesh(Renderer& renderer);
|
||||
};
|
||||
|
||||
} // namespace ZL
|
||||
260
SparkEmitter.cpp
260
SparkEmitter.cpp
@ -2,13 +2,19 @@
|
||||
#include <random>
|
||||
#include <cmath>
|
||||
#include "OpenGlExtensions.h"
|
||||
#include <fstream>
|
||||
#include "external/nlohmann/json.hpp"
|
||||
#include <iostream>
|
||||
#include "Environment.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
SparkEmitter::SparkEmitter()
|
||||
: emissionRate(100.0f), isActive(true), drawDataDirty(true), maxParticles(200) {
|
||||
: emissionRate(100.0f), isActive(true), drawDataDirty(true), maxParticles(200),
|
||||
shaderProgramName("default"), particleSize(0.04f), biasX(0.3f), configured(false) {
|
||||
particles.resize(maxParticles);
|
||||
drawPositions.reserve(maxParticles * 6);
|
||||
drawTexCoords.reserve(maxParticles * 6);
|
||||
@ -19,7 +25,8 @@ namespace ZL {
|
||||
|
||||
SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions, float rate)
|
||||
: emissionPoints(positions), emissionRate(rate), isActive(true),
|
||||
drawDataDirty(true), maxParticles(positions.size() * 100) {
|
||||
drawDataDirty(true), maxParticles(positions.size() * 100),
|
||||
shaderProgramName("default"), particleSize(0.04f), biasX(0.3f), configured(false) {
|
||||
particles.resize(maxParticles);
|
||||
drawPositions.reserve(maxParticles * 6);
|
||||
drawTexCoords.reserve(maxParticles * 6);
|
||||
@ -32,7 +39,8 @@ namespace ZL {
|
||||
std::shared_ptr<Texture> tex,
|
||||
float rate)
|
||||
: emissionPoints(positions), texture(tex), emissionRate(rate),
|
||||
isActive(true), drawDataDirty(true), maxParticles(positions.size() * 100) {
|
||||
isActive(true), drawDataDirty(true), maxParticles(positions.size() * 100),
|
||||
shaderProgramName("default"), particleSize(0.04f), biasX(0.3f), configured(false) {
|
||||
particles.resize(maxParticles);
|
||||
drawPositions.reserve(maxParticles * 6);
|
||||
drawTexCoords.reserve(maxParticles * 6);
|
||||
@ -73,7 +81,7 @@ namespace ZL {
|
||||
for (const auto& [particlePtr, depth] : sortedParticles) {
|
||||
const auto& particle = *particlePtr;
|
||||
Vector3f pos = particle.position;
|
||||
float size = 0.04f * particle.scale;
|
||||
float size = particleSize * particle.scale;
|
||||
|
||||
drawPositions.push_back({ pos.v[0] - size, pos.v[1] - size, pos.v[2] });
|
||||
drawTexCoords.push_back({ 0.0f, 0.0f });
|
||||
@ -98,12 +106,16 @@ namespace ZL {
|
||||
}
|
||||
|
||||
void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight) {
|
||||
if (!configured) {
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
if (getActiveParticleCount() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!texture) {
|
||||
return;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
prepareDrawData();
|
||||
@ -116,12 +128,11 @@ namespace ZL {
|
||||
sparkQuad.data.TexCoordData = drawTexCoords;
|
||||
sparkQuad.RefreshVBO();
|
||||
|
||||
static const std::string defaultShaderName = "default";
|
||||
static const std::string vPositionName = "vPosition";
|
||||
static const std::string vTexCoordName = "vTexCoord";
|
||||
static const std::string textureUniformName = "Texture";
|
||||
|
||||
renderer.shaderManager.PushShader(defaultShaderName);
|
||||
renderer.shaderManager.PushShader(shaderProgramName);
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
renderer.EnableVertexAttribArray(vPositionName);
|
||||
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||
@ -132,7 +143,7 @@ namespace ZL {
|
||||
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
||||
|
||||
renderer.PushMatrix();
|
||||
renderer.LoadIdentity();
|
||||
@ -151,6 +162,10 @@ namespace ZL {
|
||||
}
|
||||
|
||||
void SparkEmitter::update(float deltaTimeMs) {
|
||||
if (!configured) {
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
auto currentTime = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
currentTime - lastEmissionTime).count();
|
||||
@ -197,6 +212,10 @@ namespace ZL {
|
||||
}
|
||||
|
||||
void SparkEmitter::emit() {
|
||||
if (!configured) {
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
if (emissionPoints.empty()) return;
|
||||
bool emitted = false;
|
||||
|
||||
@ -243,39 +262,51 @@ namespace ZL {
|
||||
|
||||
void SparkEmitter::setEmissionPoints(const std::vector<Vector3f>& positions) {
|
||||
emissionPoints = positions;
|
||||
maxParticles = positions.size() * 100;
|
||||
particles.resize(maxParticles);
|
||||
drawDataDirty = true;
|
||||
}
|
||||
|
||||
void SparkEmitter::initParticle(SparkParticle& particle, int emitterIndex) {
|
||||
if (!configured) {
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
particle.velocity = getRandomVelocity(emitterIndex);
|
||||
particle.scale = 1.0f;
|
||||
particle.maxLifeTime = 800.0f + (rand() % 400);
|
||||
static std::random_device rd;
|
||||
static std::mt19937 gen(rd());
|
||||
std::uniform_real_distribution<float> scaleDist(scaleRange.min, scaleRange.max);
|
||||
particle.scale = scaleDist(gen);
|
||||
|
||||
std::uniform_real_distribution<float> lifeDist(lifeTimeRange.min, lifeTimeRange.max);
|
||||
particle.maxLifeTime = lifeDist(gen);
|
||||
particle.emitterIndex = emitterIndex;
|
||||
}
|
||||
|
||||
Vector3f SparkEmitter::getRandomVelocity(int emitterIndex) {
|
||||
if (!configured) {
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
static std::random_device rd;
|
||||
static std::mt19937 gen(rd());
|
||||
static std::uniform_real_distribution<> angleDist(0, 2 * M_PI);
|
||||
static std::uniform_real_distribution<> speedDist(0.5f, 2.0f);
|
||||
static std::uniform_real_distribution<> zSpeedDist(1.0f, 3.0f);
|
||||
std::uniform_real_distribution<float> angleDist(0.0f, 2.0f * static_cast<float>(M_PI));
|
||||
std::uniform_real_distribution<float> speedDist(speedRange.min, speedRange.max);
|
||||
std::uniform_real_distribution<float> zSpeedDist(zSpeedRange.min, zSpeedRange.max);
|
||||
|
||||
float angle = angleDist(gen);
|
||||
float speed = speedDist(gen);
|
||||
float zSpeed = zSpeedDist(gen);
|
||||
|
||||
// Теперь biasX берется из JSON
|
||||
if (emitterIndex == 0) {
|
||||
return Vector3f{
|
||||
cosf(angle) * speed - 0.3f,
|
||||
cosf(angle) * speed - biasX,
|
||||
sinf(angle) * speed,
|
||||
zSpeed
|
||||
};
|
||||
}
|
||||
else {
|
||||
return Vector3f{
|
||||
cosf(angle) * speed + 0.3f,
|
||||
cosf(angle) * speed + biasX,
|
||||
sinf(angle) * speed,
|
||||
zSpeed
|
||||
};
|
||||
@ -296,4 +327,197 @@ namespace ZL {
|
||||
return count;
|
||||
}
|
||||
|
||||
bool SparkEmitter::loadFromJsonFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
|
||||
std::cout << "Loading spark config from: " << path << std::endl;
|
||||
std::ifstream in(path);
|
||||
if (!in.is_open()) {
|
||||
std::cerr << "Failed to open JSON file: " << path << std::endl;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
json j;
|
||||
try {
|
||||
in >> j;
|
||||
std::cout << "JSON parsed successfully" << std::endl;
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "JSON parse error: " << e.what() << std::endl;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
std::cout << "JSON content: " << j.dump(2) << std::endl;
|
||||
|
||||
auto requireKey = [&](const std::string& key) {
|
||||
if (!j.contains(key)) {
|
||||
std::cerr << "Missing required key in spark JSON: " << key << std::endl;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
};
|
||||
|
||||
requireKey("emissionPoints");
|
||||
requireKey("texture");
|
||||
requireKey("speedRange");
|
||||
requireKey("zSpeedRange");
|
||||
requireKey("scaleRange");
|
||||
requireKey("lifeTimeRange");
|
||||
requireKey("emissionRate");
|
||||
requireKey("maxParticles");
|
||||
requireKey("particleSize");
|
||||
requireKey("biasX");
|
||||
requireKey("shaderProgramName");
|
||||
|
||||
emissionRate = j["emissionRate"].get<float>();
|
||||
|
||||
maxParticles = j["maxParticles"].get<int>();
|
||||
particles.resize(maxParticles);
|
||||
drawPositions.reserve(maxParticles * 6);
|
||||
drawTexCoords.reserve(maxParticles * 6);
|
||||
std::cout << "Max particles: " << maxParticles << std::endl;
|
||||
|
||||
particleSize = j["particleSize"].get<float>();
|
||||
std::cout << "Particle size: " << particleSize << std::endl;
|
||||
|
||||
biasX = j["biasX"].get<float>();
|
||||
std::cout << "Bias X: " << biasX << std::endl;
|
||||
|
||||
// emissionPoints
|
||||
std::vector<Vector3f> points;
|
||||
if (j.contains("emissionPoints") && j["emissionPoints"].is_array()) {
|
||||
for (const auto& el : j["emissionPoints"]) {
|
||||
if (el.contains("position") && el["position"].is_array()) {
|
||||
auto arr = el["position"];
|
||||
points.push_back(Vector3f{ arr[0].get<float>(), arr[1].get<float>(), arr[2].get<float>() });
|
||||
std::cout << "Fixed point: [" << arr[0] << ", " << arr[1] << ", " << arr[2] << "]" << std::endl;
|
||||
}
|
||||
else if (el.contains("positionRange") && el["positionRange"].is_object()) {
|
||||
auto pr = el["positionRange"];
|
||||
auto minArr = pr["min"];
|
||||
auto maxArr = pr["max"];
|
||||
int count = 1;
|
||||
if (el.contains("count")) count = el["count"].get<int>();
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::uniform_real_distribution<float> dx(minArr[0].get<float>(), maxArr[0].get<float>());
|
||||
std::uniform_real_distribution<float> dy(minArr[1].get<float>(), maxArr[1].get<float>());
|
||||
std::uniform_real_distribution<float> dz(minArr[2].get<float>(), maxArr[2].get<float>());
|
||||
|
||||
for (int k = 0; k < count; ++k) {
|
||||
Vector3f randomPoint{ dx(gen), dy(gen), dz(gen) };
|
||||
points.push_back(randomPoint);
|
||||
std::cout << "Random point " << k + 1 << ": [" << randomPoint.v[0]
|
||||
<< ", " << randomPoint.v[1] << ", " << randomPoint.v[2] << "]" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!points.empty()) {
|
||||
setEmissionPoints(points);
|
||||
std::cout << "Total emission points: " << emissionPoints.size() << std::endl;
|
||||
}
|
||||
else {
|
||||
std::cerr << "Emission points parsed but empty" << std::endl;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::cerr << "emissionPoints is not array" << std::endl;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
// Ranges
|
||||
if (j.contains("speedRange") && j["speedRange"].is_array()) {
|
||||
auto a = j["speedRange"];
|
||||
speedRange.min = a[0].get<float>();
|
||||
speedRange.max = a[1].get<float>();
|
||||
std::cout << "Speed range: [" << speedRange.min << ", " << speedRange.max << "]" << std::endl;
|
||||
}
|
||||
else {
|
||||
std::cerr << "speedRange missing or invalid" << std::endl;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
if (j.contains("zSpeedRange") && j["zSpeedRange"].is_array()) {
|
||||
auto a = j["zSpeedRange"];
|
||||
zSpeedRange.min = a[0].get<float>();
|
||||
zSpeedRange.max = a[1].get<float>();
|
||||
std::cout << "Z speed range: [" << zSpeedRange.min << ", " << zSpeedRange.max << "]" << std::endl;
|
||||
}
|
||||
else {
|
||||
std::cerr << "zSpeedRange missing or invalid" << std::endl;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
if (j.contains("scaleRange") && j["scaleRange"].is_array()) {
|
||||
auto a = j["scaleRange"];
|
||||
scaleRange.min = a[0].get<float>();
|
||||
scaleRange.max = a[1].get<float>();
|
||||
std::cout << "Scale range: [" << scaleRange.min << ", " << scaleRange.max << "]" << std::endl;
|
||||
}
|
||||
else {
|
||||
std::cerr << "scaleRange missing or invalid" << std::endl;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
if (j.contains("lifeTimeRange") && j["lifeTimeRange"].is_array()) {
|
||||
auto a = j["lifeTimeRange"];
|
||||
lifeTimeRange.min = a[0].get<float>();
|
||||
lifeTimeRange.max = a[1].get<float>();
|
||||
std::cout << "Life time range: [" << lifeTimeRange.min << ", " << lifeTimeRange.max << "]" << std::endl;
|
||||
}
|
||||
else {
|
||||
std::cerr << "lifeTimeRange missing or invalid" << std::endl;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
// texture
|
||||
if (j.contains("texture") && j["texture"].is_string()) {
|
||||
std::string texPath = j["texture"].get<std::string>();
|
||||
std::cout << "Loading texture: " << texPath << std::endl;
|
||||
|
||||
try {
|
||||
auto texData = CreateTextureDataFromPng(texPath.c_str(), zipFile.c_str());
|
||||
texture = std::make_shared<Texture>(texData);
|
||||
std::cout << "Texture loaded successfully, ID: " << texture->getTexID() << std::endl;
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "Texture load error: " << e.what() << std::endl;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::cerr << "No texture specified or invalid type in JSON" << std::endl;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
// shaders
|
||||
if (j.contains("shaderProgramName") && j["shaderProgramName"].is_string()) {
|
||||
shaderProgramName = j["shaderProgramName"].get<std::string>();
|
||||
std::cout << "Shader program name: " << shaderProgramName << std::endl;
|
||||
}
|
||||
else {
|
||||
std::cerr << "shaderProgramName missing or invalid" << std::endl;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
|
||||
if (j.contains("vertexShader") && j.contains("fragmentShader")
|
||||
&& j["vertexShader"].is_string() && j["fragmentShader"].is_string()) {
|
||||
std::string v = j["vertexShader"].get<std::string>();
|
||||
std::string f = j["fragmentShader"].get<std::string>();
|
||||
std::cout << "Loading shaders - vertex: " << v << ", fragment: " << f << std::endl;
|
||||
|
||||
try {
|
||||
renderer.shaderManager.AddShaderFromFiles(shaderProgramName, v, f, zipFile.c_str());
|
||||
std::cout << "Shaders loaded successfully" << std::endl;
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "Shader load error: " << e.what() << std::endl;
|
||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||
}
|
||||
}
|
||||
|
||||
drawDataDirty = true;
|
||||
configured = true;
|
||||
std::cout << "SparkEmitter configuration loaded successfully!" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
@ -5,6 +5,7 @@
|
||||
#include "TextureManager.h"
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
@ -37,7 +38,19 @@ namespace ZL {
|
||||
std::shared_ptr<Texture> texture;
|
||||
|
||||
int maxParticles;
|
||||
float particleSize;
|
||||
float biasX;
|
||||
|
||||
// Ranges (used when config supplies intervals)
|
||||
struct FloatRange { float min; float max; };
|
||||
FloatRange speedRange; // XY speed
|
||||
FloatRange zSpeedRange; // Z speed
|
||||
FloatRange scaleRange;
|
||||
FloatRange lifeTimeRange;
|
||||
|
||||
std::string shaderProgramName;
|
||||
|
||||
bool configured;
|
||||
void prepareDrawData();
|
||||
|
||||
public:
|
||||
@ -49,6 +62,13 @@ namespace ZL {
|
||||
|
||||
void setEmissionPoints(const std::vector<Vector3f>& positions);
|
||||
void setTexture(std::shared_ptr<Texture> tex);
|
||||
void setEmissionRate(float rate) { emissionRate = rate; }
|
||||
void setShaderProgramName(const std::string& name) { shaderProgramName = name; }
|
||||
void setMaxParticles(int max) { maxParticles = max; particles.resize(maxParticles); }
|
||||
void setParticleSize(float size) { particleSize = size; }
|
||||
void setBiasX(float bias) { biasX = bias; }
|
||||
|
||||
bool loadFromJsonFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
|
||||
|
||||
void update(float deltaTimeMs);
|
||||
void emit();
|
||||
@ -57,6 +77,7 @@ namespace ZL {
|
||||
|
||||
const std::vector<SparkParticle>& getParticles() const;
|
||||
size_t getActiveParticleCount() const;
|
||||
std::shared_ptr<Texture> getTexture() const { return texture; }
|
||||
|
||||
private:
|
||||
void initParticle(SparkParticle& particle, int emitterIndex);
|
||||
|
||||
530
UiManager.cpp
Normal file
530
UiManager.cpp
Normal file
@ -0,0 +1,530 @@
|
||||
#include "UiManager.h"
|
||||
#include "Utils.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
void UiButton::buildMesh() {
|
||||
mesh.data.PositionData.clear();
|
||||
mesh.data.TexCoordData.clear();
|
||||
|
||||
float x0 = rect.x;
|
||||
float y0 = rect.y;
|
||||
float x1 = rect.x + rect.w;
|
||||
float y1 = rect.y + rect.h;
|
||||
|
||||
mesh.data.PositionData.push_back({ x0, y0, 0 });
|
||||
mesh.data.TexCoordData.push_back({ 0, 0 });
|
||||
|
||||
mesh.data.PositionData.push_back({ x0, y1, 0 });
|
||||
mesh.data.TexCoordData.push_back({ 0, 1 });
|
||||
|
||||
mesh.data.PositionData.push_back({ x1, y1, 0 });
|
||||
mesh.data.TexCoordData.push_back({ 1, 1 });
|
||||
|
||||
mesh.data.PositionData.push_back({ x0, y0, 0 });
|
||||
mesh.data.TexCoordData.push_back({ 0, 0 });
|
||||
|
||||
mesh.data.PositionData.push_back({ x1, y1, 0 });
|
||||
mesh.data.TexCoordData.push_back({ 1, 1 });
|
||||
|
||||
mesh.data.PositionData.push_back({ x1, y0, 0 });
|
||||
mesh.data.TexCoordData.push_back({ 1, 0 });
|
||||
|
||||
mesh.RefreshVBO();
|
||||
}
|
||||
|
||||
void UiButton::draw(Renderer& renderer) const {
|
||||
if (!texNormal) return;
|
||||
const std::shared_ptr<Texture>* tex = &texNormal;
|
||||
switch (state) {
|
||||
case ButtonState::Normal: tex = &texNormal; break;
|
||||
case ButtonState::Hover: tex = &texHover; break;
|
||||
case ButtonState::Pressed: tex = &texPressed; break;
|
||||
}
|
||||
if (!(*tex)) return;
|
||||
|
||||
static const std::string vPositionName = "vPosition";
|
||||
static const std::string vTexCoordName = "vTexCoord";
|
||||
static const std::string textureUniformName = "Texture";
|
||||
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
renderer.EnableVertexAttribArray(vPositionName);
|
||||
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, (*tex)->getTexID());
|
||||
renderer.DrawVertexRenderStruct(mesh);
|
||||
|
||||
renderer.DisableVertexAttribArray(vPositionName);
|
||||
renderer.DisableVertexAttribArray(vTexCoordName);
|
||||
}
|
||||
|
||||
void UiSlider::buildTrackMesh() {
|
||||
trackMesh.data.PositionData.clear();
|
||||
trackMesh.data.TexCoordData.clear();
|
||||
|
||||
float x0 = rect.x;
|
||||
float y0 = rect.y;
|
||||
float x1 = rect.x + rect.w;
|
||||
float y1 = rect.y + rect.h;
|
||||
|
||||
trackMesh.data.PositionData.push_back({ x0, y0, 0 });
|
||||
trackMesh.data.TexCoordData.push_back({ 0, 0 });
|
||||
|
||||
trackMesh.data.PositionData.push_back({ x0, y1, 0 });
|
||||
trackMesh.data.TexCoordData.push_back({ 0, 1 });
|
||||
|
||||
trackMesh.data.PositionData.push_back({ x1, y1, 0 });
|
||||
trackMesh.data.TexCoordData.push_back({ 1, 1 });
|
||||
|
||||
trackMesh.data.PositionData.push_back({ x0, y0, 0 });
|
||||
trackMesh.data.TexCoordData.push_back({ 0, 0 });
|
||||
|
||||
trackMesh.data.PositionData.push_back({ x1, y1, 0 });
|
||||
trackMesh.data.TexCoordData.push_back({ 1, 1 });
|
||||
|
||||
trackMesh.data.PositionData.push_back({ x1, y0, 0 });
|
||||
trackMesh.data.TexCoordData.push_back({ 1, 0 });
|
||||
|
||||
trackMesh.RefreshVBO();
|
||||
}
|
||||
|
||||
void UiSlider::buildKnobMesh() {
|
||||
knobMesh.data.PositionData.clear();
|
||||
knobMesh.data.TexCoordData.clear();
|
||||
|
||||
float kw = vertical ? rect.w * 4.0f : rect.w * 0.5f;
|
||||
float kh = vertical ? rect.w * 4.0f : rect.h * 0.5f;
|
||||
|
||||
float cx = rect.x + rect.w * 0.5f;
|
||||
float cy = rect.y + (vertical ? (value * rect.h) : (rect.h * 0.5f));
|
||||
|
||||
float x0 = cx - kw * 0.5f;
|
||||
float y0 = cy - kh * 0.5f;
|
||||
float x1 = cx + kw * 0.5f;
|
||||
float y1 = cy + kh * 0.5f;
|
||||
|
||||
knobMesh.data.PositionData.push_back({ x0, y0, 0 });
|
||||
knobMesh.data.TexCoordData.push_back({ 0, 0 });
|
||||
|
||||
knobMesh.data.PositionData.push_back({ x0, y1, 0 });
|
||||
knobMesh.data.TexCoordData.push_back({ 0, 1 });
|
||||
|
||||
knobMesh.data.PositionData.push_back({ x1, y1, 0 });
|
||||
knobMesh.data.TexCoordData.push_back({ 1, 1 });
|
||||
|
||||
knobMesh.data.PositionData.push_back({ x0, y0, 0 });
|
||||
knobMesh.data.TexCoordData.push_back({ 0, 0 });
|
||||
|
||||
knobMesh.data.PositionData.push_back({ x1, y1, 0 });
|
||||
knobMesh.data.TexCoordData.push_back({ 1, 1 });
|
||||
|
||||
knobMesh.data.PositionData.push_back({ x1, y0, 0 });
|
||||
knobMesh.data.TexCoordData.push_back({ 1, 0 });
|
||||
|
||||
knobMesh.RefreshVBO();
|
||||
}
|
||||
|
||||
void UiSlider::draw(Renderer& renderer) const {
|
||||
static const std::string vPositionName = "vPosition";
|
||||
static const std::string vTexCoordName = "vTexCoord";
|
||||
static const std::string textureUniformName = "Texture";
|
||||
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
renderer.EnableVertexAttribArray(vPositionName);
|
||||
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||
|
||||
if (texTrack) {
|
||||
glBindTexture(GL_TEXTURE_2D, texTrack->getTexID());
|
||||
renderer.DrawVertexRenderStruct(trackMesh);
|
||||
}
|
||||
if (texKnob) {
|
||||
glBindTexture(GL_TEXTURE_2D, texKnob->getTexID());
|
||||
renderer.DrawVertexRenderStruct(knobMesh);
|
||||
}
|
||||
|
||||
renderer.DisableVertexAttribArray(vPositionName);
|
||||
renderer.DisableVertexAttribArray(vTexCoordName);
|
||||
}
|
||||
|
||||
void UiManager::loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
|
||||
std::ifstream in(path);
|
||||
if (!in.is_open()) {
|
||||
std::cerr << "UiManager: failed to open " << path << std::endl;
|
||||
throw std::runtime_error("Failed to load UI file: " + path);
|
||||
}
|
||||
|
||||
json j;
|
||||
try {
|
||||
in >> j;
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "UiManager: json parse error: " << e.what() << std::endl;
|
||||
throw std::runtime_error("Failed to load UI file: " + path);
|
||||
}
|
||||
|
||||
if (!j.contains("root") || !j["root"].is_object()) {
|
||||
std::cerr << "UiManager: root node missing or invalid" << std::endl;
|
||||
throw std::runtime_error("Failed to load UI file: " + path);
|
||||
}
|
||||
|
||||
root = parseNode(j["root"], renderer, zipFile);
|
||||
layoutNode(root);
|
||||
buttons.clear();
|
||||
sliders.clear();
|
||||
collectButtonsAndSliders(root);
|
||||
|
||||
for (auto& b : buttons) {
|
||||
b->buildMesh();
|
||||
}
|
||||
for (auto& s : sliders) {
|
||||
s->buildTrackMesh();
|
||||
s->buildKnobMesh();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<UiNode> UiManager::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>();
|
||||
|
||||
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 (node->type == "Button") {
|
||||
auto btn = std::make_shared<UiButton>();
|
||||
btn->name = node->name;
|
||||
btn->rect = node->rect;
|
||||
|
||||
if (!j.contains("textures") || !j["textures"].is_object()) {
|
||||
std::cerr << "UiManager: Button '" << btn->name << "' missing textures" << std::endl;
|
||||
throw std::runtime_error("UI button textures missing");
|
||||
}
|
||||
auto t = j["textures"];
|
||||
auto loadTex = [&](const std::string& key)->std::shared_ptr<Texture> {
|
||||
if (!t.contains(key) || !t[key].is_string()) return nullptr;
|
||||
std::string path = t[key].get<std::string>();
|
||||
try {
|
||||
auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str());
|
||||
return std::make_shared<Texture>(data);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "UiManager: failed load texture " << path << " : " << e.what() << std::endl;
|
||||
throw std::runtime_error("UI texture load failed: " + path);
|
||||
}
|
||||
};
|
||||
|
||||
btn->texNormal = loadTex("normal");
|
||||
btn->texHover = loadTex("hover");
|
||||
btn->texPressed = loadTex("pressed");
|
||||
|
||||
node->button = btn;
|
||||
}
|
||||
else if (node->type == "Slider") {
|
||||
auto s = std::make_shared<UiSlider>();
|
||||
s->name = node->name;
|
||||
s->rect = node->rect;
|
||||
|
||||
if (!j.contains("textures") || !j["textures"].is_object()) {
|
||||
std::cerr << "UiManager: Slider '" << s->name << "' missing textures" << std::endl;
|
||||
throw std::runtime_error("UI slider textures missing");
|
||||
}
|
||||
auto t = j["textures"];
|
||||
auto loadTex = [&](const std::string& key)->std::shared_ptr<Texture> {
|
||||
if (!t.contains(key) || !t[key].is_string()) return nullptr;
|
||||
std::string path = t[key].get<std::string>();
|
||||
try {
|
||||
auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str());
|
||||
return std::make_shared<Texture>(data);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "UiManager: failed load texture " << path << " : " << e.what() << std::endl;
|
||||
throw std::runtime_error("UI texture load failed: " + path);
|
||||
}
|
||||
};
|
||||
|
||||
s->texTrack = loadTex("track");
|
||||
s->texKnob = loadTex("knob");
|
||||
|
||||
if (j.contains("value")) s->value = j["value"].get<float>();
|
||||
if (j.contains("orientation")) {
|
||||
std::string orient = j["orientation"].get<std::string>();
|
||||
std::transform(orient.begin(), orient.end(), orient.begin(), ::tolower);
|
||||
s->vertical = (orient != "horizontal");
|
||||
}
|
||||
|
||||
node->slider = s;
|
||||
}
|
||||
|
||||
if (j.contains("children") && j["children"].is_array()) {
|
||||
for (const auto& ch : j["children"]) {
|
||||
node->children.push_back(parseNode(ch, renderer, zipFile));
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (node->type == "LinearLayout") {
|
||||
std::string orient = node->orientation;
|
||||
std::transform(orient.begin(), orient.end(), orient.begin(), ::tolower);
|
||||
|
||||
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;
|
||||
}
|
||||
else {
|
||||
child->rect.x = node->rect.x;
|
||||
child->rect.y = cursorY;
|
||||
cursorY += child->rect.h + node->spacing;
|
||||
}
|
||||
layoutNode(child);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (auto& child : node->children) {
|
||||
layoutNode(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UiManager::collectButtonsAndSliders(const std::shared_ptr<UiNode>& node) {
|
||||
if (node->button) {
|
||||
buttons.push_back(node->button);
|
||||
}
|
||||
if (node->slider) {
|
||||
sliders.push_back(node->slider);
|
||||
}
|
||||
for (auto& c : node->children) collectButtonsAndSliders(c);
|
||||
}
|
||||
|
||||
bool UiManager::setButtonCallback(const std::string& name, std::function<void(const std::string&)> cb) {
|
||||
auto b = findButton(name);
|
||||
if (!b) {
|
||||
std::cerr << "UiManager: setButtonCallback failed, button not found: " << name << std::endl;
|
||||
return false;
|
||||
}
|
||||
b->onClick = std::move(cb);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UiManager::addSlider(const std::string& name, const UiRect& rect, Renderer& renderer, const std::string& zipFile,
|
||||
const std::string& trackPath, const std::string& knobPath, float initialValue, bool vertical) {
|
||||
|
||||
auto s = std::make_shared<UiSlider>();
|
||||
s->name = name;
|
||||
s->rect = rect;
|
||||
s->value = std::clamp(initialValue, 0.0f, 1.0f);
|
||||
s->vertical = vertical;
|
||||
|
||||
try {
|
||||
if (!trackPath.empty()) {
|
||||
auto data = CreateTextureDataFromPng(trackPath.c_str(), zipFile.c_str());
|
||||
s->texTrack = std::make_shared<Texture>(data);
|
||||
}
|
||||
if (!knobPath.empty()) {
|
||||
auto data = CreateTextureDataFromPng(knobPath.c_str(), zipFile.c_str());
|
||||
s->texKnob = std::make_shared<Texture>(data);
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "UiManager: addSlider failed to load textures: " << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
s->buildTrackMesh();
|
||||
s->buildKnobMesh();
|
||||
|
||||
sliders.push_back(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<UiSlider> UiManager::findSlider(const std::string& name) {
|
||||
for (auto& s : sliders) if (s->name == name) return s;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UiManager::setSliderCallback(const std::string& name, std::function<void(const std::string&, float)> cb) {
|
||||
auto s = findSlider(name);
|
||||
if (!s) {
|
||||
std::cerr << "UiManager: setSliderCallback failed, slider not found: " << name << std::endl;
|
||||
return false;
|
||||
}
|
||||
s->onValueChanged = std::move(cb);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UiManager::setSliderValue(const std::string& name, float value) {
|
||||
auto s = findSlider(name);
|
||||
if (!s) return false;
|
||||
value = std::clamp(value, 0.0f, 1.0f);
|
||||
if (fabs(s->value - value) < 1e-6f) return true;
|
||||
s->value = value;
|
||||
s->buildKnobMesh();
|
||||
if (s->onValueChanged) s->onValueChanged(s->name, s->value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UiManager::pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
|
||||
MenuState prev;
|
||||
prev.root = root;
|
||||
prev.buttons = buttons;
|
||||
prev.sliders = sliders;
|
||||
prev.pressedButton = pressedButton;
|
||||
prev.pressedSlider = pressedSlider;
|
||||
prev.path = "";
|
||||
|
||||
try {
|
||||
loadFromFile(path, renderer, zipFile);
|
||||
menuStack.push_back(std::move(prev));
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "UiManager: pushMenuFromFile failed to load " << path << " : " << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool UiManager::popMenu() {
|
||||
if (menuStack.empty()) {
|
||||
std::cerr << "UiManager: popMenu called but menu stack is empty" << std::endl;
|
||||
return false;
|
||||
}
|
||||
auto s = menuStack.back();
|
||||
menuStack.pop_back();
|
||||
|
||||
root = s.root;
|
||||
buttons = s.buttons;
|
||||
sliders = s.sliders;
|
||||
pressedButton = s.pressedButton;
|
||||
pressedSlider = s.pressedSlider;
|
||||
|
||||
for (auto& b : buttons) {
|
||||
if (b) b->buildMesh();
|
||||
}
|
||||
for (auto& sl : sliders) {
|
||||
if (sl) { sl->buildTrackMesh(); sl->buildKnobMesh(); }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UiManager::clearMenuStack() {
|
||||
menuStack.clear();
|
||||
}
|
||||
|
||||
void UiManager::draw(Renderer& renderer) {
|
||||
renderer.PushProjectionMatrix(Environment::width, Environment::height, -1, 1);
|
||||
renderer.PushMatrix();
|
||||
renderer.LoadIdentity();
|
||||
|
||||
for (const auto& b : buttons) {
|
||||
b->draw(renderer);
|
||||
}
|
||||
for (const auto& s : sliders) {
|
||||
s->draw(renderer);
|
||||
}
|
||||
|
||||
renderer.PopMatrix();
|
||||
renderer.PopProjectionMatrix();
|
||||
}
|
||||
|
||||
void UiManager::onMouseMove(int x, int y) {
|
||||
for (auto& b : buttons) {
|
||||
if (b->rect.contains((float)x, (float)y)) {
|
||||
if (b->state != ButtonState::Pressed) b->state = ButtonState::Hover;
|
||||
}
|
||||
else {
|
||||
if (b->state != ButtonState::Pressed) b->state = ButtonState::Normal;
|
||||
}
|
||||
}
|
||||
|
||||
if (pressedSlider) {
|
||||
auto s = pressedSlider;
|
||||
float t;
|
||||
if (s->vertical) {
|
||||
t = (y - s->rect.y) / s->rect.h;
|
||||
}
|
||||
else {
|
||||
t = (x - s->rect.x) / s->rect.w;
|
||||
}
|
||||
if (t < 0.0f) t = 0.0f;
|
||||
if (t > 1.0f) t = 1.0f;
|
||||
s->value = t;
|
||||
s->buildKnobMesh();
|
||||
if (s->onValueChanged) s->onValueChanged(s->name, s->value);
|
||||
}
|
||||
}
|
||||
|
||||
void UiManager::onMouseDown(int x, int y) {
|
||||
for (auto& b : buttons) {
|
||||
if (b->rect.contains((float)x, (float)y)) {
|
||||
b->state = ButtonState::Pressed;
|
||||
pressedButton = b;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& s : sliders) {
|
||||
if (s->rect.contains((float)x, (float)y)) {
|
||||
pressedSlider = s;
|
||||
float t;
|
||||
if (s->vertical) {
|
||||
t = (y - s->rect.y) / s->rect.h;
|
||||
}
|
||||
else {
|
||||
t = (x - s->rect.x) / s->rect.w;
|
||||
}
|
||||
if (t < 0.0f) t = 0.0f;
|
||||
if (t > 1.0f) t = 1.0f;
|
||||
s->value = t;
|
||||
s->buildKnobMesh();
|
||||
if (s->onValueChanged) s->onValueChanged(s->name, s->value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UiManager::onMouseUp(int x, int y) {
|
||||
for (auto& b : buttons) {
|
||||
bool contains = b->rect.contains((float)x, (float)y);
|
||||
if (b->state == ButtonState::Pressed) {
|
||||
if (contains && pressedButton == b) {
|
||||
if (b->onClick) {
|
||||
b->onClick(b->name);
|
||||
}
|
||||
}
|
||||
b->state = contains ? ButtonState::Hover : ButtonState::Normal;
|
||||
}
|
||||
}
|
||||
pressedButton.reset();
|
||||
|
||||
if (pressedSlider) {
|
||||
pressedSlider.reset();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<UiButton> UiManager::findButton(const std::string& name) {
|
||||
for (auto& b : buttons) if (b->name == name) return b;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
133
UiManager.h
Normal file
133
UiManager.h
Normal file
@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include "Renderer.h"
|
||||
#include "TextureManager.h"
|
||||
#include "Environment.h"
|
||||
#include "external/nlohmann/json.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
struct UiRect {
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
float w = 0;
|
||||
float h = 0;
|
||||
bool contains(float px, float py) const {
|
||||
return px >= x && px <= x + w && py >= y && py <= y + h;
|
||||
}
|
||||
};
|
||||
|
||||
enum class ButtonState {
|
||||
Normal,
|
||||
Hover,
|
||||
Pressed
|
||||
};
|
||||
|
||||
struct UiButton {
|
||||
std::string name;
|
||||
UiRect rect;
|
||||
std::shared_ptr<Texture> texNormal;
|
||||
std::shared_ptr<Texture> texHover;
|
||||
std::shared_ptr<Texture> texPressed;
|
||||
ButtonState state = ButtonState::Normal;
|
||||
|
||||
VertexRenderStruct mesh;
|
||||
|
||||
std::function<void(const std::string&)> onClick;
|
||||
|
||||
void buildMesh();
|
||||
void draw(Renderer& renderer) const;
|
||||
};
|
||||
|
||||
struct UiSlider {
|
||||
std::string name;
|
||||
UiRect rect;
|
||||
std::shared_ptr<Texture> texTrack;
|
||||
std::shared_ptr<Texture> texKnob;
|
||||
|
||||
VertexRenderStruct trackMesh;
|
||||
VertexRenderStruct knobMesh;
|
||||
|
||||
float value = 0.0f;
|
||||
bool vertical = true;
|
||||
|
||||
std::function<void(const std::string&, float)> onValueChanged;
|
||||
|
||||
void buildTrackMesh();
|
||||
void buildKnobMesh();
|
||||
void draw(Renderer& renderer) const;
|
||||
};
|
||||
|
||||
struct UiNode {
|
||||
std::string type;
|
||||
UiRect rect;
|
||||
std::string name;
|
||||
std::vector<std::shared_ptr<UiNode>> children;
|
||||
std::shared_ptr<UiButton> button;
|
||||
std::shared_ptr<UiSlider> slider;
|
||||
std::string orientation = "vertical";
|
||||
float spacing = 0.0f;
|
||||
};
|
||||
|
||||
class UiManager {
|
||||
public:
|
||||
UiManager() = default;
|
||||
|
||||
void loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
|
||||
|
||||
void draw(Renderer& renderer);
|
||||
|
||||
void onMouseMove(int x, int y);
|
||||
void onMouseDown(int x, int y);
|
||||
void onMouseUp(int x, int y);
|
||||
|
||||
bool isUiInteraction() const {
|
||||
return pressedButton != nullptr || pressedSlider != nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<UiButton> findButton(const std::string& name);
|
||||
|
||||
bool setButtonCallback(const std::string& name, std::function<void(const std::string&)> cb);
|
||||
|
||||
bool addSlider(const std::string& name, const UiRect& rect, Renderer& renderer, const std::string& zipFile,
|
||||
const std::string& trackPath, const std::string& knobPath, float initialValue = 0.0f, bool vertical = true);
|
||||
|
||||
std::shared_ptr<UiSlider> findSlider(const std::string& name);
|
||||
bool setSliderCallback(const std::string& name, std::function<void(const std::string&, float)> cb);
|
||||
bool setSliderValue(const std::string& name, float value); // programmatic set (clamped 0..1)
|
||||
|
||||
bool pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
|
||||
bool popMenu();
|
||||
void clearMenuStack();
|
||||
|
||||
private:
|
||||
std::shared_ptr<UiNode> parseNode(const json& j, Renderer& renderer, const std::string& zipFile);
|
||||
void layoutNode(const std::shared_ptr<UiNode>& node);
|
||||
void collectButtonsAndSliders(const std::shared_ptr<UiNode>& node);
|
||||
|
||||
std::shared_ptr<UiNode> root;
|
||||
std::vector<std::shared_ptr<UiButton>> buttons;
|
||||
std::vector<std::shared_ptr<UiSlider>> sliders;
|
||||
|
||||
std::shared_ptr<UiButton> pressedButton;
|
||||
std::shared_ptr<UiSlider> pressedSlider;
|
||||
|
||||
struct MenuState {
|
||||
std::shared_ptr<UiNode> root;
|
||||
std::vector<std::shared_ptr<UiButton>> buttons;
|
||||
std::vector<std::shared_ptr<UiSlider>> sliders;
|
||||
std::shared_ptr<UiButton> pressedButton;
|
||||
std::shared_ptr<UiSlider> pressedSlider;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
std::vector<MenuState> menuStack;
|
||||
};
|
||||
|
||||
} // namespace ZL
|
||||
72
config/settings.json
Normal file
72
config/settings.json
Normal file
@ -0,0 +1,72 @@
|
||||
{
|
||||
"root": {
|
||||
"type": "FrameLayout",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"children": [
|
||||
{
|
||||
"type": "FrameLayout",
|
||||
"name": "centerPanel",
|
||||
"x": 480,
|
||||
"y": 160,
|
||||
"width": 320,
|
||||
"height": 400,
|
||||
"children": [
|
||||
{
|
||||
"type": "LinearLayout",
|
||||
"name": "settingsButtons",
|
||||
"orientation": "vertical",
|
||||
"spacing": 10,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 300,
|
||||
"children": [
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "Opt1",
|
||||
"x": 100,
|
||||
"y": 300,
|
||||
"width": 200,
|
||||
"height": 50,
|
||||
"textures": {
|
||||
"normal": "./resources/button.png",
|
||||
"hover": "./resources/button.png",
|
||||
"pressed": "./resources/button.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "Opt2",
|
||||
"x": 100,
|
||||
"y": 200,
|
||||
"width": 200,
|
||||
"height": 50,
|
||||
"textures": {
|
||||
"normal": "./resources/button.png",
|
||||
"hover": "./resources/button.png",
|
||||
"pressed": "./resources/button.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "backButton",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 200,
|
||||
"height": 50,
|
||||
"textures": {
|
||||
"normal": "./resources/button.png",
|
||||
"hover": "./resources/button.png",
|
||||
"pressed": "./resources/button.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
22
config/spark_config.json
Normal file
22
config/spark_config.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"emissionRate": 100.0,
|
||||
"maxParticles": 200,
|
||||
"particleSize": 0.04,
|
||||
"biasX": 0.3,
|
||||
"emissionPoints": [
|
||||
{
|
||||
"position": [-2.1, 0.4, 5.0]
|
||||
},
|
||||
{
|
||||
"position": [2.1, 0.4, 5.0]
|
||||
}
|
||||
],
|
||||
"speedRange": [0.5, 2.0],
|
||||
"zSpeedRange": [1.0, 3.0],
|
||||
"scaleRange": [0.8, 1.2],
|
||||
"lifeTimeRange": [600.0, 1400.0],
|
||||
"texture": "./resources/spark.png",
|
||||
"shaderProgramName": "default",
|
||||
"vertexShader": "./shaders/spark.vertex",
|
||||
"fragmentShader": "./shaders/spark.fragment"
|
||||
}
|
||||
15
config/spark_projectile_config.json
Normal file
15
config/spark_projectile_config.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"emissionPoints": [
|
||||
{ "position": [0.0, 0.0, 0.0] }
|
||||
],
|
||||
"texture": "./resources/sand.png",
|
||||
"speedRange": [10.0, 30.0],
|
||||
"zSpeedRange": [-1.0, 1.0],
|
||||
"scaleRange": [0.5, 1.0],
|
||||
"lifeTimeRange": [200.0, 800.0],
|
||||
"emissionRate": 50.0,
|
||||
"maxParticles": 10,
|
||||
"particleSize": 0.09,
|
||||
"biasX": 0.1,
|
||||
"shaderProgramName": "default"
|
||||
}
|
||||
86
config/ui.json
Normal file
86
config/ui.json
Normal file
@ -0,0 +1,86 @@
|
||||
{
|
||||
"root": {
|
||||
"type": "FrameLayout",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"children": [
|
||||
{
|
||||
"type": "FrameLayout",
|
||||
"name": "leftPanel",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 320,
|
||||
"height": 400,
|
||||
"children": [
|
||||
{
|
||||
"type": "LinearLayout",
|
||||
"name": "mainButtons",
|
||||
"orientation": "vertical",
|
||||
"spacing": 10,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 300,
|
||||
"children": [
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "playButton",
|
||||
"x": 100,
|
||||
"y": 300,
|
||||
"width": 200,
|
||||
"height": 50,
|
||||
"textures": {
|
||||
"normal": "./resources/button.png",
|
||||
"hover": "./resources/button.png",
|
||||
"pressed": "./resources/button.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "settingsButton",
|
||||
"x": 100,
|
||||
"y": 200,
|
||||
"width": 200,
|
||||
"height": 50,
|
||||
"textures": {
|
||||
"normal": "./resources/sand.png",
|
||||
"hover": "./resources/sand.png",
|
||||
"pressed": "./resources/sand.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "exitButton",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 200,
|
||||
"height": 50,
|
||||
"textures": {
|
||||
"normal": "./resources/rock.png",
|
||||
"hover": "./resources/rock.png",
|
||||
"pressed": "./resources/rock.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Slider",
|
||||
"name": "musicVolumeSlider",
|
||||
"x": 1140,
|
||||
"y": 100,
|
||||
"width": 10,
|
||||
"height": 500,
|
||||
"value": 0.5,
|
||||
"orientation": "vertical",
|
||||
"textures": {
|
||||
"track": "./resources/musicVolumeBarTexture.png",
|
||||
"knob": "./resources/musicVolumeBarButton.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
25526
external/nlohmann/json.hpp
vendored
Normal file
25526
external/nlohmann/json.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
12
shaders/spark.fragment
Normal file
12
shaders/spark.fragment
Normal file
@ -0,0 +1,12 @@
|
||||
//precision mediump float;
|
||||
uniform sampler2D Texture;
|
||||
varying vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture2D(Texture,texCoord).rgba;
|
||||
//gl_FragColor = vec4(color.rgb*0.9 + vec3(0.1, 0.1, 0.1), 1.0);
|
||||
|
||||
gl_FragColor = color;
|
||||
|
||||
}
|
||||
11
shaders/spark.vertex
Normal file
11
shaders/spark.vertex
Normal file
@ -0,0 +1,11 @@
|
||||
attribute vec3 vPosition;
|
||||
attribute vec2 vTexCoord;
|
||||
varying vec2 texCoord;
|
||||
|
||||
uniform mat4 ProjectionModelViewMatrix;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = ProjectionModelViewMatrix * vec4(vPosition.xyz, 1.0);
|
||||
texCoord = vTexCoord;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user