Merge branch 'spark' of gitea.fishrungames.com:salmon-engine-projects/space-game001 into spark
This commit is contained in:
commit
d610f1dea1
BIN
assets.zip
BIN
assets.zip
Binary file not shown.
@ -31,9 +31,25 @@
|
||||
"y": 300,
|
||||
"width": 200,
|
||||
"height": 50,
|
||||
"animations": {
|
||||
"buttonsExit": {
|
||||
"repeat": false,
|
||||
"steps": [
|
||||
{
|
||||
"type": "move",
|
||||
"to": [
|
||||
-400,
|
||||
0
|
||||
],
|
||||
"duration": 1.0,
|
||||
"easing": "easein"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"textures": {
|
||||
"normal": "./resources/button.png",
|
||||
"hover": "./resources/button.png",
|
||||
"hover": "./resources/sand.png",
|
||||
"pressed": "./resources/button.png"
|
||||
}
|
||||
},
|
||||
@ -44,9 +60,29 @@
|
||||
"y": 200,
|
||||
"width": 200,
|
||||
"height": 50,
|
||||
"animations": {
|
||||
"buttonsExit": {
|
||||
"repeat": false,
|
||||
"steps": [
|
||||
{
|
||||
"type": "wait",
|
||||
"duration": 0.5
|
||||
},
|
||||
{
|
||||
"type": "move",
|
||||
"to": [
|
||||
-400,
|
||||
0
|
||||
],
|
||||
"duration": 1.0,
|
||||
"easing": "easein"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"textures": {
|
||||
"normal": "./resources/sand.png",
|
||||
"hover": "./resources/sand.png",
|
||||
"hover": "./resources/button.png",
|
||||
"pressed": "./resources/sand.png"
|
||||
}
|
||||
},
|
||||
@ -57,9 +93,43 @@
|
||||
"y": 100,
|
||||
"width": 200,
|
||||
"height": 50,
|
||||
"animations": {
|
||||
"buttonsExit": {
|
||||
"repeat": false,
|
||||
"steps": [
|
||||
{
|
||||
"type": "wait",
|
||||
"duration": 1.0
|
||||
},
|
||||
{
|
||||
"type": "move",
|
||||
"to": [
|
||||
-400,
|
||||
0
|
||||
],
|
||||
"duration": 1.0,
|
||||
"easing": "easein"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bgScroll": {
|
||||
"repeat": true,
|
||||
"steps": [
|
||||
{
|
||||
"type": "move",
|
||||
"to": [
|
||||
1280,
|
||||
0
|
||||
],
|
||||
"duration": 5.0,
|
||||
"easing": "linear"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"textures": {
|
||||
"normal": "./resources/rock.png",
|
||||
"hover": "./resources/rock.png",
|
||||
"hover": "./resources/button.png",
|
||||
"pressed": "./resources/rock.png"
|
||||
}
|
||||
}
|
||||
|
||||
95
src/Game.cpp
95
src/Game.cpp
@ -18,6 +18,7 @@ namespace ZL
|
||||
const char* CONST_ZIP_FILE = "../assets.zip";
|
||||
#endif
|
||||
|
||||
static bool g_exitBgAnimating = false;
|
||||
|
||||
|
||||
Eigen::Quaternionf generateRandomQuaternion(std::mt19937& gen)
|
||||
@ -152,7 +153,7 @@ namespace ZL
|
||||
renderer.shaderManager.AddShaderFromFiles("default", "./shaders/default.vertex", "./shaders/default_desktop.fragment", CONST_ZIP_FILE);
|
||||
renderer.shaderManager.AddShaderFromFiles("env_sky", "./shaders/env_sky.vertex", "./shaders/env_sky_desktop.fragment", CONST_ZIP_FILE);
|
||||
renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "./shaders/defaultAtmosphere.vertex", "./shaders/defaultAtmosphere_desktop.fragment", CONST_ZIP_FILE);
|
||||
renderer.shaderManager.AddShaderFromFiles("planetBake", "./shaders/planet_bake.vertex", "./shaders/planet_bake_desktop.fragment", CONST_ZIP_FILE);
|
||||
renderer.shaderManager.AddShaderFromFiles("planetBake", "./shaders/planet_bake.vertex", "./shaders/planet_bake_desktop.fragment", CONST_ZIP_FILE);
|
||||
renderer.shaderManager.AddShaderFromFiles("planetStone", "./shaders/planet_stone.vertex", "./shaders/planet_stone_desktop.fragment", CONST_ZIP_FILE);
|
||||
renderer.shaderManager.AddShaderFromFiles("planetLand", "./shaders/planet_land.vertex", "./shaders/planet_land_desktop.fragment", CONST_ZIP_FILE);
|
||||
|
||||
@ -176,37 +177,73 @@ namespace ZL
|
||||
#endif
|
||||
|
||||
#endif
|
||||
//#ifndef SIMPLIFIED
|
||||
//#ifndef SIMPLIFIED
|
||||
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) {
|
||||
uiManager.loadFromFile("config/ui.json", renderer, CONST_ZIP_FILE);
|
||||
uiManager.startAnimationOnNode("backgroundNode", "bgScroll");
|
||||
static bool isExitButtonAnimating = false;
|
||||
uiManager.setAnimationCallback("settingsButton", "buttonsExit", [this]() {
|
||||
std::cerr << "Settings button animation finished -> переход в настройки" << std::endl;
|
||||
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.stopAllAnimations();
|
||||
uiManager.popMenu();
|
||||
});
|
||||
}
|
||||
else {
|
||||
std::cerr << "Failed to open settings menu" << std::endl;
|
||||
std::cerr << "Failed to open settings menu after animations" << std::endl;
|
||||
}
|
||||
});
|
||||
|
||||
uiManager.setAnimationCallback("exitButton", "bgScroll", []() {
|
||||
std::cerr << "Exit button bgScroll animation finished" << std::endl;
|
||||
g_exitBgAnimating = false;
|
||||
});
|
||||
|
||||
// Set UI button callbacks
|
||||
uiManager.setButtonCallback("playButton", [this](const std::string& name) {
|
||||
std::cerr << "Play button pressed: " << name << std::endl;
|
||||
|
||||
});
|
||||
|
||||
uiManager.setButtonCallback("settingsButton", [this](const std::string& name) {
|
||||
std::cerr << "Settings button pressed: " << name << std::endl;
|
||||
uiManager.startAnimationOnNode("playButton", "buttonsExit");
|
||||
uiManager.startAnimationOnNode("settingsButton", "buttonsExit");
|
||||
uiManager.startAnimationOnNode("exitButton", "buttonsExit");
|
||||
});
|
||||
|
||||
uiManager.setButtonCallback("exitButton", [this](const std::string& name) {
|
||||
std::cerr << "Exit button pressed: " << name << std::endl;
|
||||
|
||||
if (!g_exitBgAnimating) {
|
||||
std::cerr << "start repeat anim bgScroll on exitButton" << std::endl;
|
||||
g_exitBgAnimating = true;
|
||||
uiManager.startAnimationOnNode("exitButton", "bgScroll");
|
||||
}
|
||||
else {
|
||||
std::cerr << "stop repeat anim bgScroll on exitButton" << std::endl;
|
||||
g_exitBgAnimating = false;
|
||||
uiManager.stopAnimationOnNode("exitButton", "bgScroll");
|
||||
|
||||
auto exitButton = uiManager.findButton("exitButton");
|
||||
if (exitButton) {
|
||||
exitButton->animOffsetX = 0.0f;
|
||||
exitButton->animOffsetY = 0.0f;
|
||||
exitButton->animScaleX = 1.0f;
|
||||
exitButton->animScaleY = 1.0f;
|
||||
exitButton->buildMesh();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -215,7 +252,7 @@ namespace ZL
|
||||
musicVolume = value;
|
||||
Environment::shipVelocity = musicVolume * 20.0f;
|
||||
});
|
||||
//#endif
|
||||
//#endif
|
||||
|
||||
cubemapTexture = std::make_shared<Texture>(
|
||||
std::array<TextureDataStruct, 6>{
|
||||
@ -234,7 +271,7 @@ namespace ZL
|
||||
//Load texture
|
||||
spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/DefaultMaterial_BaseColor_shine.png", CONST_ZIP_FILE));
|
||||
spaceshipBase = LoadFromTextFile02("./resources/spaceship006.txt", CONST_ZIP_FILE);
|
||||
spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());
|
||||
spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix();
|
||||
|
||||
//spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/cap_D.png", CONST_ZIP_FILE));
|
||||
//spaceshipBase = LoadFromTextFile02("./resources/spaceship006x.txt", CONST_ZIP_FILE);
|
||||
@ -448,11 +485,11 @@ namespace ZL
|
||||
}
|
||||
}
|
||||
|
||||
//#ifndef SIMPLIFIED
|
||||
//#ifndef SIMPLIFIED
|
||||
|
||||
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||
projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||
//#endif
|
||||
//#endif
|
||||
renderer.PopMatrix();
|
||||
renderer.PopProjectionMatrix();
|
||||
renderer.DisableVertexAttribArray(vPositionName);
|
||||
@ -615,10 +652,10 @@ namespace ZL
|
||||
drawShip();
|
||||
drawBoxes();
|
||||
|
||||
//#ifndef SIMPLIFIED
|
||||
//#ifndef SIMPLIFIED
|
||||
|
||||
drawUI();
|
||||
//#endif
|
||||
//#endif
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
@ -634,10 +671,10 @@ namespace ZL
|
||||
size_t delta = (newTickCount - lastTickCount > CONST_MAX_TIME_INTERVAL) ?
|
||||
CONST_MAX_TIME_INTERVAL : newTickCount - lastTickCount;
|
||||
|
||||
//#ifndef SIMPLIFIED
|
||||
//gameObjects.updateScene(delta);
|
||||
//#ifndef SIMPLIFIED
|
||||
//gameObjects.updateScene(delta);
|
||||
sparkEmitter.update(static_cast<float>(delta));
|
||||
//#endif
|
||||
//#endif
|
||||
planetObject.update(static_cast<float>(delta));
|
||||
if (Environment::tapDownHold) {
|
||||
|
||||
@ -672,7 +709,7 @@ namespace ZL
|
||||
Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted;
|
||||
}
|
||||
|
||||
//#ifndef SIMPLIFIED
|
||||
//#ifndef SIMPLIFIED
|
||||
for (auto& p : projectiles) {
|
||||
if (p && p->isActive()) {
|
||||
p->update(static_cast<float>(delta), renderer);
|
||||
@ -707,7 +744,9 @@ namespace ZL
|
||||
|
||||
sparkEmitter.update(static_cast<float>(delta));
|
||||
projectileEmitter.update(static_cast<float>(delta));
|
||||
//#endif
|
||||
|
||||
uiManager.update(static_cast<float>(delta));
|
||||
//#endif
|
||||
lastTickCount = newTickCount;
|
||||
}
|
||||
}
|
||||
@ -765,7 +804,7 @@ namespace ZL
|
||||
uiManager.onMouseDown(uiX, uiY);
|
||||
|
||||
bool uiHandled = false;
|
||||
//#ifndef SIMPLIFIED
|
||||
//#ifndef SIMPLIFIED
|
||||
if (event.button.button == SDL_BUTTON_LEFT && !uiManager.isUiInteraction()) {
|
||||
uint64_t now = SDL_GetTicks64();
|
||||
if (now - lastProjectileFireTime >= static_cast<uint64_t>(projectileCooldownMs)) {
|
||||
@ -773,7 +812,7 @@ namespace ZL
|
||||
fireProjectiles();
|
||||
}
|
||||
}
|
||||
//#endif
|
||||
//#endif
|
||||
for (const auto& button : uiManager.findButton("") ? std::vector<std::shared_ptr<UiButton>>{} : std::vector<std::shared_ptr<UiButton>>{}) {
|
||||
(void)button;
|
||||
}
|
||||
|
||||
@ -8,6 +8,17 @@ namespace ZL {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
static float applyEasing(const std::string& easing, float t) {
|
||||
if (easing == "easein") {
|
||||
return t * t;
|
||||
}
|
||||
else if (easing == "easeout") {
|
||||
float inv = 1.0f - t;
|
||||
return 1.0f - inv * inv;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
void UiButton::buildMesh() {
|
||||
mesh.data.PositionData.clear();
|
||||
mesh.data.TexCoordData.clear();
|
||||
@ -52,6 +63,10 @@ namespace ZL {
|
||||
static const std::string vTexCoordName = "vTexCoord";
|
||||
static const std::string textureUniformName = "Texture";
|
||||
|
||||
renderer.PushMatrix();
|
||||
renderer.TranslateMatrix({ animOffsetX, animOffsetY, 0.0f });
|
||||
renderer.ScaleMatrix({ animScaleX, animScaleY, 1.0f });
|
||||
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
renderer.EnableVertexAttribArray(vPositionName);
|
||||
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||
@ -61,6 +76,7 @@ namespace ZL {
|
||||
|
||||
renderer.DisableVertexAttribArray(vPositionName);
|
||||
renderer.DisableVertexAttribArray(vTexCoordName);
|
||||
renderer.PopMatrix();
|
||||
}
|
||||
|
||||
void UiSlider::buildTrackMesh() {
|
||||
@ -191,6 +207,8 @@ namespace ZL {
|
||||
sliders.clear();
|
||||
collectButtonsAndSliders(root);
|
||||
|
||||
nodeActiveAnims.clear();
|
||||
|
||||
for (auto& b : buttons) {
|
||||
b->buildMesh();
|
||||
}
|
||||
@ -278,6 +296,37 @@ namespace ZL {
|
||||
node->slider = s;
|
||||
}
|
||||
|
||||
if (j.contains("animations") && j["animations"].is_object()) {
|
||||
for (auto it = j["animations"].begin(); it != j["animations"].end(); ++it) {
|
||||
std::string animName = it.key();
|
||||
const auto& animDef = it.value();
|
||||
UiNode::AnimSequence seq;
|
||||
if (animDef.contains("repeat") && animDef["repeat"].is_boolean()) seq.repeat = animDef["repeat"].get<bool>();
|
||||
if (animDef.contains("steps") && animDef["steps"].is_array()) {
|
||||
for (const auto& step : animDef["steps"]) {
|
||||
UiNode::AnimStep s;
|
||||
if (step.contains("type") && step["type"].is_string()) {
|
||||
s.type = step["type"].get<std::string>();
|
||||
std::transform(s.type.begin(), s.type.end(), s.type.begin(), ::tolower);
|
||||
}
|
||||
if (step.contains("to") && step["to"].is_array() && step["to"].size() >= 2) {
|
||||
s.toX = step["to"][0].get<float>();
|
||||
s.toY = step["to"][1].get<float>();
|
||||
}
|
||||
if (step.contains("duration")) {
|
||||
s.durationMs = step["duration"].get<float>() * 1000.0f;
|
||||
}
|
||||
if (step.contains("easing") && step["easing"].is_string()) {
|
||||
s.easing = step["easing"].get<std::string>();
|
||||
std::transform(s.easing.begin(), s.easing.end(), s.easing.begin(), ::tolower);
|
||||
}
|
||||
seq.steps.push_back(s);
|
||||
}
|
||||
}
|
||||
node->animations[animName] = std::move(seq);
|
||||
}
|
||||
}
|
||||
|
||||
if (j.contains("children") && j["children"].is_array()) {
|
||||
for (const auto& ch : j["children"]) {
|
||||
node->children.push_back(parseNode(ch, renderer, zipFile));
|
||||
@ -406,13 +455,27 @@ namespace ZL {
|
||||
prev.pressedSlider = pressedSlider;
|
||||
prev.path = "";
|
||||
|
||||
prev.animCallbacks = animCallbacks;
|
||||
|
||||
try {
|
||||
nodeActiveAnims.clear();
|
||||
animCallbacks.clear();
|
||||
for (auto& b : buttons) {
|
||||
if (b) {
|
||||
b->animOffsetX = 0.0f;
|
||||
b->animOffsetY = 0.0f;
|
||||
b->animScaleX = 1.0f;
|
||||
b->animScaleY = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
animCallbacks = prev.animCallbacks;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -425,19 +488,32 @@ namespace ZL {
|
||||
auto s = menuStack.back();
|
||||
menuStack.pop_back();
|
||||
|
||||
nodeActiveAnims.clear();
|
||||
|
||||
root = s.root;
|
||||
buttons = s.buttons;
|
||||
sliders = s.sliders;
|
||||
pressedButton = s.pressedButton;
|
||||
pressedSlider = s.pressedSlider;
|
||||
|
||||
animCallbacks = s.animCallbacks;
|
||||
|
||||
for (auto& b : buttons) {
|
||||
if (b) b->buildMesh();
|
||||
}
|
||||
for (auto& sl : sliders) {
|
||||
if (sl) { sl->buildTrackMesh(); sl->buildKnobMesh(); }
|
||||
if (b) {
|
||||
b->animOffsetX = 0.0f;
|
||||
b->animOffsetY = 0.0f;
|
||||
b->animScaleX = 1.0f;
|
||||
b->animScaleY = 1.0f;
|
||||
b->buildMesh();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& sl : sliders) {
|
||||
if (sl) {
|
||||
sl->buildTrackMesh();
|
||||
sl->buildKnobMesh();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -461,6 +537,168 @@ namespace ZL {
|
||||
renderer.PopProjectionMatrix();
|
||||
}
|
||||
|
||||
static std::shared_ptr<UiNode> findNodeByName(const std::shared_ptr<UiNode>& node, const std::string& name) {
|
||||
if (!node) return nullptr;
|
||||
if (!name.empty() && node->name == name) return node;
|
||||
for (auto& c : node->children) {
|
||||
auto r = findNodeByName(c, name);
|
||||
if (r) return r;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UiManager::update(float deltaMs) {
|
||||
if (!root) return;
|
||||
|
||||
std::vector<std::pair<std::shared_ptr<UiNode>, size_t>> animationsToRemove;
|
||||
std::vector<std::function<void()>> pendingCallbacks;
|
||||
|
||||
for (auto& kv : nodeActiveAnims) {
|
||||
auto node = kv.first;
|
||||
auto& activeList = kv.second;
|
||||
|
||||
for (size_t i = 0; i < activeList.size(); ++i) {
|
||||
auto& act = activeList[i];
|
||||
if (!act.seq) {
|
||||
animationsToRemove.push_back({ node, i });
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& steps = act.seq->steps;
|
||||
|
||||
if (act.stepIndex >= steps.size()) {
|
||||
if (act.repeat) {
|
||||
if (node->button) {
|
||||
node->button->animOffsetX = act.origOffsetX;
|
||||
node->button->animOffsetY = act.origOffsetY;
|
||||
node->button->animScaleX = act.origScaleX;
|
||||
node->button->animScaleY = act.origScaleY;
|
||||
}
|
||||
act.stepIndex = 0;
|
||||
act.elapsedMs = 0.0f;
|
||||
act.stepStarted = false;
|
||||
}
|
||||
else {
|
||||
if (act.onComplete) {
|
||||
pendingCallbacks.push_back(act.onComplete);
|
||||
}
|
||||
animationsToRemove.push_back({ node, i });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& step = steps[act.stepIndex];
|
||||
|
||||
if (step.durationMs <= 0.0f) {
|
||||
if (step.type == "move") {
|
||||
if (node->button) {
|
||||
node->button->animOffsetX = step.toX;
|
||||
node->button->animOffsetY = step.toY;
|
||||
}
|
||||
}
|
||||
else if (step.type == "scale") {
|
||||
if (node->button) {
|
||||
node->button->animScaleX = step.toX;
|
||||
node->button->animScaleY = step.toY;
|
||||
}
|
||||
}
|
||||
act.stepIndex++;
|
||||
act.elapsedMs = 0.0f;
|
||||
act.stepStarted = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!act.stepStarted && act.stepIndex == 0 && act.elapsedMs == 0.0f) {
|
||||
if (node->button) {
|
||||
act.origOffsetX = node->button->animOffsetX;
|
||||
act.origOffsetY = node->button->animOffsetY;
|
||||
act.origScaleX = node->button->animScaleX;
|
||||
act.origScaleY = node->button->animScaleY;
|
||||
}
|
||||
else {
|
||||
act.origOffsetX = act.origOffsetY = 0.0f;
|
||||
act.origScaleX = act.origScaleY = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
float prevElapsed = act.elapsedMs;
|
||||
act.elapsedMs += deltaMs;
|
||||
|
||||
if (!act.stepStarted && prevElapsed == 0.0f) {
|
||||
if (node->button) {
|
||||
act.startOffsetX = node->button->animOffsetX;
|
||||
act.startOffsetY = node->button->animOffsetY;
|
||||
act.startScaleX = node->button->animScaleX;
|
||||
act.startScaleY = node->button->animScaleY;
|
||||
}
|
||||
else {
|
||||
act.startOffsetX = act.startOffsetY = 0.0f;
|
||||
act.startScaleX = act.startScaleY = 1.0f;
|
||||
}
|
||||
if (step.type == "move") {
|
||||
act.endOffsetX = step.toX;
|
||||
act.endOffsetY = step.toY;
|
||||
}
|
||||
else if (step.type == "scale") {
|
||||
act.endScaleX = step.toX;
|
||||
act.endScaleY = step.toY;
|
||||
}
|
||||
act.stepStarted = true;
|
||||
}
|
||||
float t = (step.durationMs > 0.0f) ? (act.elapsedMs / step.durationMs) : 1.0f;
|
||||
if (t > 1.0f) t = 1.0f;
|
||||
float te = applyEasing(step.easing, t);
|
||||
|
||||
if (step.type == "move") {
|
||||
float nx = act.startOffsetX + (act.endOffsetX - act.startOffsetX) * te;
|
||||
float ny = act.startOffsetY + (act.endOffsetY - act.startOffsetY) * te;
|
||||
if (node->button) {
|
||||
node->button->animOffsetX = nx;
|
||||
node->button->animOffsetY = ny;
|
||||
}
|
||||
}
|
||||
else if (step.type == "scale") {
|
||||
float sx = act.startScaleX + (act.endScaleX - act.startScaleX) * te;
|
||||
float sy = act.startScaleY + (act.endScaleY - act.startScaleY) * te;
|
||||
if (node->button) {
|
||||
node->button->animScaleX = sx;
|
||||
node->button->animScaleY = sy;
|
||||
}
|
||||
}
|
||||
else if (step.type == "wait") {
|
||||
//wait
|
||||
}
|
||||
if (act.elapsedMs >= step.durationMs) {
|
||||
act.stepIndex++;
|
||||
act.elapsedMs = 0.0f;
|
||||
act.stepStarted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = animationsToRemove.rbegin(); it != animationsToRemove.rend(); ++it) {
|
||||
auto& [node, index] = *it;
|
||||
if (nodeActiveAnims.find(node) != nodeActiveAnims.end()) {
|
||||
auto& animList = nodeActiveAnims[node];
|
||||
if (index < animList.size()) {
|
||||
animList.erase(animList.begin() + index);
|
||||
}
|
||||
if (animList.empty()) {
|
||||
nodeActiveAnims.erase(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& cb : pendingCallbacks) {
|
||||
try {
|
||||
cb();
|
||||
}
|
||||
catch (...) {
|
||||
std::cerr << "UiManager: animation onComplete callback threw exception" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UiManager::onMouseMove(int x, int y) {
|
||||
for (auto& b : buttons) {
|
||||
if (b->rect.contains((float)x, (float)y)) {
|
||||
@ -540,4 +778,88 @@ namespace ZL {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UiManager::startAnimationOnNode(const std::string& nodeName, const std::string& animName) {
|
||||
if (!root) return false;
|
||||
auto node = findNodeByName(root, nodeName);
|
||||
if (!node) return false;
|
||||
auto it = node->animations.find(animName);
|
||||
if (it == node->animations.end()) return false;
|
||||
|
||||
ActiveAnim aa;
|
||||
aa.name = animName;
|
||||
aa.seq = &it->second;
|
||||
aa.stepIndex = 0;
|
||||
aa.elapsedMs = 0.0f;
|
||||
aa.repeat = it->second.repeat;
|
||||
aa.stepStarted = false;
|
||||
if (node->button) {
|
||||
aa.origOffsetX = node->button->animOffsetX;
|
||||
aa.origOffsetY = node->button->animOffsetY;
|
||||
aa.origScaleX = node->button->animScaleX;
|
||||
aa.origScaleY = node->button->animScaleY;
|
||||
}
|
||||
auto cbIt = animCallbacks.find({ nodeName, animName });
|
||||
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
|
||||
nodeActiveAnims[node].push_back(std::move(aa));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UiManager::stopAnimationOnNode(const std::string& nodeName, const std::string& animName) {
|
||||
if (!root) return false;
|
||||
auto node = findNodeByName(root, nodeName);
|
||||
if (!node) return false;
|
||||
auto it = nodeActiveAnims.find(node);
|
||||
if (it != nodeActiveAnims.end()) {
|
||||
auto& animList = it->second;
|
||||
for (auto animIt = animList.begin(); animIt != animList.end(); ) {
|
||||
if (animIt->name == animName) {
|
||||
animIt = animList.erase(animIt);
|
||||
}
|
||||
else {
|
||||
++animIt;
|
||||
}
|
||||
}
|
||||
if (animList.empty()) {
|
||||
nodeActiveAnims.erase(it);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UiManager::startAnimation(const std::string& animName) {
|
||||
if (!root) return;
|
||||
std::function<void(const std::shared_ptr<UiNode>&)> traverse = [&](const std::shared_ptr<UiNode>& n) {
|
||||
if (!n) return;
|
||||
auto it = n->animations.find(animName);
|
||||
if (it != n->animations.end()) {
|
||||
ActiveAnim aa;
|
||||
aa.name = animName;
|
||||
aa.seq = &it->second;
|
||||
aa.stepIndex = 0;
|
||||
aa.elapsedMs = 0.0f;
|
||||
aa.repeat = it->second.repeat;
|
||||
aa.stepStarted = false;
|
||||
if (n->button) {
|
||||
aa.origOffsetX = n->button->animOffsetX;
|
||||
aa.origOffsetY = n->button->animOffsetY;
|
||||
aa.origScaleX = n->button->animScaleX;
|
||||
aa.origScaleY = n->button->animScaleY;
|
||||
}
|
||||
auto cbIt = animCallbacks.find({ n->name, animName });
|
||||
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
|
||||
nodeActiveAnims[n].push_back(std::move(aa));
|
||||
}
|
||||
for (auto& c : n->children) traverse(c);
|
||||
};
|
||||
traverse(root);
|
||||
}
|
||||
|
||||
bool UiManager::setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function<void()> cb) {
|
||||
animCallbacks[{nodeName, animName}] = std::move(cb);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
@ -41,6 +42,12 @@ namespace ZL {
|
||||
|
||||
std::function<void(const std::string&)> onClick;
|
||||
|
||||
// animation runtime
|
||||
float animOffsetX = 0.0f;
|
||||
float animOffsetY = 0.0f;
|
||||
float animScaleX = 1.0f;
|
||||
float animScaleY = 1.0f;
|
||||
|
||||
void buildMesh();
|
||||
void draw(Renderer& renderer) const;
|
||||
};
|
||||
@ -73,6 +80,19 @@ namespace ZL {
|
||||
std::shared_ptr<UiSlider> slider;
|
||||
std::string orientation = "vertical";
|
||||
float spacing = 0.0f;
|
||||
|
||||
struct AnimStep {
|
||||
std::string type;
|
||||
float toX = 0.0f;
|
||||
float toY = 0.0f;
|
||||
float durationMs = 0.0f;
|
||||
std::string easing = "linear";
|
||||
};
|
||||
struct AnimSequence {
|
||||
std::vector<AnimStep> steps;
|
||||
bool repeat = false;
|
||||
};
|
||||
std::map<std::string, AnimSequence> animations;
|
||||
};
|
||||
|
||||
class UiManager {
|
||||
@ -91,6 +111,19 @@ namespace ZL {
|
||||
return pressedButton != nullptr || pressedSlider != nullptr;
|
||||
}
|
||||
|
||||
void stopAllAnimations() {
|
||||
nodeActiveAnims.clear();
|
||||
|
||||
for (auto& b : buttons) {
|
||||
if (b) {
|
||||
b->animOffsetX = 0.0f;
|
||||
b->animOffsetY = 0.0f;
|
||||
b->animScaleX = 1.0f;
|
||||
b->animScaleY = 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<UiButton> findButton(const std::string& name);
|
||||
|
||||
bool setButtonCallback(const std::string& name, std::function<void(const std::string&)> cb);
|
||||
@ -106,15 +139,47 @@ namespace ZL {
|
||||
bool popMenu();
|
||||
void clearMenuStack();
|
||||
|
||||
void update(float deltaMs);
|
||||
void startAnimation(const std::string& animName);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
struct ActiveAnim {
|
||||
std::string name;
|
||||
const UiNode::AnimSequence* seq = nullptr;
|
||||
size_t stepIndex = 0;
|
||||
float elapsedMs = 0.0f;
|
||||
bool repeat = false;
|
||||
float startOffsetX = 0.0f;
|
||||
float startOffsetY = 0.0f;
|
||||
float endOffsetX = 0.0f;
|
||||
float endOffsetY = 0.0f;
|
||||
float startScaleX = 1.0f;
|
||||
float startScaleY = 1.0f;
|
||||
float endScaleX = 1.0f;
|
||||
float endScaleY = 1.0f;
|
||||
std::function<void()> onComplete;
|
||||
|
||||
float origOffsetX = 0.0f;
|
||||
float origOffsetY = 0.0f;
|
||||
float origScaleX = 1.0f;
|
||||
float origScaleY = 1.0f;
|
||||
bool stepStarted = false;
|
||||
};
|
||||
|
||||
std::shared_ptr<UiNode> root;
|
||||
std::vector<std::shared_ptr<UiButton>> buttons;
|
||||
std::vector<std::shared_ptr<UiSlider>> sliders;
|
||||
|
||||
std::map<std::shared_ptr<UiNode>, std::vector<ActiveAnim>> nodeActiveAnims;
|
||||
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks; // key: (nodeName, animName)
|
||||
|
||||
std::shared_ptr<UiButton> pressedButton;
|
||||
std::shared_ptr<UiSlider> pressedSlider;
|
||||
|
||||
@ -125,6 +190,7 @@ namespace ZL {
|
||||
std::shared_ptr<UiButton> pressedButton;
|
||||
std::shared_ptr<UiSlider> pressedSlider;
|
||||
std::string path;
|
||||
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks;
|
||||
};
|
||||
|
||||
std::vector<MenuState> menuStack;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user