Swim animations

This commit is contained in:
2026-04-22 21:38:33 +03:00
parent 30814ea35a
commit d55bf970e0
13 changed files with 241 additions and 2 deletions

View File

@@ -47,6 +47,7 @@ set(EDITSCENE_SOURCES
ui/TriangleBufferEditor.cpp
ui/CharacterSlotsEditor.cpp
ui/AnimationTreeEditor.cpp
ui/AnimationTreeTemplateEditor.cpp
ui/CharacterEditor.cpp
ui/CellGridEditor.cpp
ui/LotEditor.cpp
@@ -72,6 +73,7 @@ set(EDITSCENE_SOURCES
components/TriangleBufferModule.cpp
components/CharacterSlotsModule.cpp
components/AnimationTreeModule.cpp
components/AnimationTreeTemplateModule.cpp
components/AnimationTree.cpp
components/CharacterModule.cpp
components/CellGridModule.cpp
@@ -152,6 +154,7 @@ set(EDITSCENE_HEADERS
ui/TriangleBufferEditor.hpp
ui/CharacterSlotsEditor.hpp
ui/AnimationTreeEditor.hpp
ui/AnimationTreeTemplateEditor.hpp
ui/CharacterEditor.hpp
ui/CellGridEditor.hpp
ui/LotEditor.hpp

View File

@@ -42,6 +42,7 @@
#include "components/TriangleBuffer.hpp"
#include "components/CharacterSlots.hpp"
#include "components/AnimationTree.hpp"
#include "components/AnimationTreeTemplate.hpp"
#include "components/Character.hpp"
#include "components/StartupMenu.hpp"
#include "components/PlayerController.hpp"
@@ -488,6 +489,9 @@ void EditorApp::setupECS()
// Register AnimationTree component
m_world.component<AnimationTreeComponent>();
// Register AnimationTreeTemplate component
m_world.component<AnimationTreeTemplate>();
// Register Character component
m_world.component<CharacterComponent>();

View File

@@ -150,6 +150,12 @@ struct AnimationTreeComponent {
bool useRootMotion = false;
bool dirty = true;
/* If set, the tree root is copied from the named template */
Ogre::String templateName;
/* Runtime: last copied template version (not serialized) */
uint64_t templateVersion = 0;
/* Runtime: current state of each state machine (not serialized) */
std::unordered_map<Ogre::String, Ogre::String> currentStates;

View File

@@ -0,0 +1,20 @@
#ifndef EDITSCENE_ANIMATIONTREETEMPLATE_HPP
#define EDITSCENE_ANIMATIONTREETEMPLATE_HPP
#pragma once
#include <Ogre.h>
/**
* Template marker for reusable animation trees.
*
* Entities with this component serve as shared animation tree templates.
* They should also have an AnimationTreeComponent for editing the tree.
* Other entities reference the template by name via
* AnimationTreeComponent::templateName.
*/
struct AnimationTreeTemplate {
Ogre::String name;
uint64_t version = 1;
};
#endif // EDITSCENE_ANIMATIONTREETEMPLATE_HPP

View File

@@ -0,0 +1,23 @@
#include "AnimationTreeTemplate.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/AnimationTreeTemplateEditor.hpp"
REGISTER_COMPONENT_GROUP("Animation Tree Template", "Animation",
AnimationTreeTemplate, AnimationTreeTemplateEditor)
{
registry.registerComponent<AnimationTreeTemplate>(
AnimationTreeTemplate_name, AnimationTreeTemplate_group,
std::make_unique<AnimationTreeTemplateEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<AnimationTreeTemplate>()) {
e.set<AnimationTreeTemplate>({});
}
},
// Remover
[](flecs::entity e) {
if (e.has<AnimationTreeTemplate>()) {
e.remove<AnimationTreeTemplate>();
}
});
}

View File

@@ -228,6 +228,39 @@ void AnimationTreeSystem::initializeTreeStates(
initializeTreeStates(child, at);
}
void AnimationTreeSystem::resolveTemplate(AnimationTreeComponent &at)
{
if (at.templateName.empty())
return;
AnimationTreeTemplate *templ = nullptr;
AnimationTreeComponent *templAt = nullptr;
m_world.query<AnimationTreeTemplate, AnimationTreeComponent>()
.each([&](flecs::entity, AnimationTreeTemplate &t,
AnimationTreeComponent &ta) {
if (t.name == at.templateName) {
templ = &t;
templAt = &ta;
}
});
if (!templ) {
m_world.query<AnimationTreeTemplate>()
.each([&](flecs::entity, AnimationTreeTemplate &t) {
if (t.name == at.templateName)
templ = &t;
});
}
if (templ && at.templateVersion != templ->version) {
if (templAt)
at.root = templAt->root;
at.templateVersion = templ->version;
at.dirty = true;
}
}
void AnimationTreeSystem::update(float deltaTime)
{
if (!m_initialized)
@@ -236,6 +269,8 @@ void AnimationTreeSystem::update(float deltaTime)
m_world.query<AnimationTreeComponent>().each(
[this, deltaTime](flecs::entity e,
AnimationTreeComponent &at) {
resolveTemplate(at);
if (at.dirty) {
if (setupEntity(e, at))
at.dirty = false;

View File

@@ -9,6 +9,7 @@
#include <set>
#include "../components/AnimationTree.hpp"
#include "../components/AnimationTreeTemplate.hpp"
/**
* System that evaluates an AnimationTreeComponent each frame.
@@ -100,6 +101,9 @@ private:
const Ogre::String &stateMachineName,
const Ogre::String &stateName, bool reset);
/* Resolve template reference and copy tree if template changed */
void resolveTemplate(AnimationTreeComponent &at);
const AnimationTreeNode *findStateMachineNode(
const AnimationTreeNode &root,
const Ogre::String &name) const;

View File

@@ -21,6 +21,7 @@
#include "../components/CharacterSlots.hpp"
#include "../components/Character.hpp"
#include "../components/AnimationTree.hpp"
#include "../components/AnimationTreeTemplate.hpp"
#include "../components/StartupMenu.hpp"
#include "../components/PlayerController.hpp"
#include "../components/CellGrid.hpp"
@@ -701,6 +702,13 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
componentCount++;
}
// Render AnimationTreeTemplate if present
if (entity.has<AnimationTreeTemplate>()) {
auto &templ = entity.get_mut<AnimationTreeTemplate>();
m_componentRegistry.render<AnimationTreeTemplate>(entity, templ);
componentCount++;
}
// Render StartupMenu if present
if (entity.has<StartupMenuComponent>()) {
auto &sm = entity.get_mut<StartupMenuComponent>();

View File

@@ -18,6 +18,7 @@
#include "../components/Character.hpp"
#include "../components/CharacterSlots.hpp"
#include "../components/AnimationTree.hpp"
#include "../components/AnimationTreeTemplate.hpp"
#include "../components/StartupMenu.hpp"
#include "../components/PlayerController.hpp"
#include "../components/CellGrid.hpp"
@@ -211,6 +212,10 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
json["animationTree"] = serializeAnimationTree(entity);
}
if (entity.has<AnimationTreeTemplate>()) {
json["animationTreeTemplate"] = serializeAnimationTreeTemplate(entity);
}
if (entity.has<StartupMenuComponent>()) {
json["startupMenu"] = serializeStartupMenu(entity);
}
@@ -364,6 +369,10 @@ void SceneSerializer::deserializeEntity(const nlohmann::json &json,
deserializeAnimationTree(entity, json["animationTree"]);
}
if (json.contains("animationTreeTemplate")) {
deserializeAnimationTreeTemplate(entity, json["animationTreeTemplate"]);
}
if (json.contains("startupMenu")) {
deserializeStartupMenu(entity, json["startupMenu"]);
}
@@ -1549,6 +1558,8 @@ nlohmann::json SceneSerializer::serializeAnimationTree(flecs::entity entity)
nlohmann::json json;
json["enabled"] = at.enabled;
json["useRootMotion"] = at.useRootMotion;
if (!at.templateName.empty())
json["templateName"] = at.templateName;
json["root"] = serializeAnimationTreeNode(at.root);
return json;
}
@@ -1559,12 +1570,31 @@ void SceneSerializer::deserializeAnimationTree(flecs::entity entity,
AnimationTreeComponent at;
at.enabled = json.value("enabled", true);
at.useRootMotion = json.value("useRootMotion", false);
at.templateName = json.value("templateName", "");
if (json.contains("root"))
deserializeAnimationTreeNode(at.root, json["root"]);
at.dirty = true;
entity.set<AnimationTreeComponent>(at);
}
nlohmann::json SceneSerializer::serializeAnimationTreeTemplate(flecs::entity entity)
{
auto &templ = entity.get<AnimationTreeTemplate>();
nlohmann::json json;
json["name"] = templ.name;
json["version"] = templ.version;
return json;
}
void SceneSerializer::deserializeAnimationTreeTemplate(flecs::entity entity,
const nlohmann::json &json)
{
AnimationTreeTemplate templ;
templ.name = json.value("name", "");
templ.version = json.value("version", 1);
entity.set<AnimationTreeTemplate>(templ);
}
// ============================================================================
// CellGrid/Town Component Serialization
// ============================================================================

View File

@@ -62,6 +62,7 @@ private:
nlohmann::json serializeCharacter(flecs::entity entity);
nlohmann::json serializeCharacterSlots(flecs::entity entity);
nlohmann::json serializeAnimationTree(flecs::entity entity);
nlohmann::json serializeAnimationTreeTemplate(flecs::entity entity);
nlohmann::json serializeStartupMenu(flecs::entity entity);
nlohmann::json serializePlayerController(flecs::entity entity);
@@ -111,6 +112,8 @@ private:
const nlohmann::json &json);
void deserializeAnimationTree(flecs::entity entity,
const nlohmann::json &json);
void deserializeAnimationTreeTemplate(flecs::entity entity,
const nlohmann::json &json);
void deserializeStartupMenu(flecs::entity entity,
const nlohmann::json &json);
void deserializePlayerController(flecs::entity entity,

View File

@@ -1,5 +1,6 @@
#include "AnimationTreeEditor.hpp"
#include "../systems/AnimationTreeSystem.hpp"
#include "../components/AnimationTreeTemplate.hpp"
#include <imgui.h>
AnimationTreeEditor::AnimationTreeEditor(Ogre::SceneManager *sceneMgr)
@@ -211,7 +212,8 @@ void AnimationTreeEditor::renderTree(AnimationTreeNode &node,
if (ImGui::BeginPopupContextItem()) {
if (canHaveChildren(node.type)) {
if (node.type == "output" ||
node.type == "stateMachine") {
node.type == "stateMachine" ||
node.type == "state") {
if (ImGui::MenuItem("Add State Machine"))
queueAddChild(&node, "stateMachine");
}
@@ -259,7 +261,8 @@ void AnimationTreeEditor::renderTree(AnimationTreeNode &node,
}
if (ImGui::BeginPopup("AddChild")) {
if (node.type == "output" ||
node.type == "stateMachine") {
node.type == "stateMachine" ||
node.type == "state") {
if (ImGui::MenuItem("State Machine"))
queueAddChild(&node, "stateMachine");
}
@@ -473,12 +476,44 @@ void AnimationTreeEditor::renderStatePreview(
bool AnimationTreeEditor::renderComponent(
flecs::entity entity, AnimationTreeComponent &at)
{
ImGui::PushID("AnimTree");
bool modified = false;
if (ImGui::CollapsingHeader("Animation Tree",
ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
/* Template reference */
char templBuf[256];
snprintf(templBuf, sizeof(templBuf), "%s",
at.templateName.c_str());
if (ImGui::InputText("Template Name", templBuf,
sizeof(templBuf))) {
at.templateName = templBuf;
modified = true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Leave empty for local tree. Set to template name to copy from a template entity.");
}
bool isTemplateEntity = entity.has<AnimationTreeTemplate>();
if (isTemplateEntity) {
auto &templ = entity.get_mut<AnimationTreeTemplate>();
char nameBuf[256];
snprintf(nameBuf, sizeof(nameBuf), "%s",
templ.name.c_str());
if (ImGui::InputText("Template Publish Name", nameBuf,
sizeof(nameBuf))) {
templ.name = nameBuf;
modified = true;
}
ImGui::TextDisabled("Template version: %llu",
(unsigned long long)templ.version);
}
ImGui::Separator();
/* Global toggles */
if (ImGui::Checkbox("Enabled", &at.enabled))
modified = true;
@@ -515,5 +550,12 @@ bool AnimationTreeEditor::renderComponent(
ImGui::Unindent();
}
/* Auto-publish template changes */
if (modified && entity.has<AnimationTreeTemplate>()) {
auto &templ = entity.get_mut<AnimationTreeTemplate>();
templ.version++;
}
ImGui::PopID();
return modified;
}

View File

@@ -0,0 +1,37 @@
#include "AnimationTreeTemplateEditor.hpp"
#include <imgui.h>
bool AnimationTreeTemplateEditor::renderComponent(
flecs::entity entity, AnimationTreeTemplate &templ)
{
ImGui::PushID("AnimTreeTempl");
(void)entity;
bool modified = false;
if (ImGui::CollapsingHeader("Animation Tree Template",
ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
char nameBuf[256];
snprintf(nameBuf, sizeof(nameBuf), "%s", templ.name.c_str());
if (ImGui::InputText("Template Name", nameBuf,
sizeof(nameBuf))) {
templ.name = nameBuf;
modified = true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Other entities reference this template by name in their Animation Tree component.");
}
ImGui::TextDisabled("Version: %llu",
(unsigned long long)templ.version);
ImGui::TextDisabled(
"Edit the Animation Tree component on this entity to modify the template.");
ImGui::Unindent();
}
ImGui::PopID();
return modified;
}

View File

@@ -0,0 +1,24 @@
#ifndef EDITSCENE_ANIMATIONTREETEMPLATEEDITOR_HPP
#define EDITSCENE_ANIMATIONTREETEMPLATEEDITOR_HPP
#pragma once
#include "ComponentEditor.hpp"
#include "../components/AnimationTreeTemplate.hpp"
/**
* Editor for AnimationTreeTemplate component.
* The actual tree editing is done via the co-located AnimationTreeComponent.
* This editor only exposes the template name and version.
*/
class AnimationTreeTemplateEditor
: public ComponentEditor<AnimationTreeTemplate> {
public:
bool renderComponent(flecs::entity entity,
AnimationTreeTemplate &templ) override;
const char *getName() const override
{
return "Animation Tree Template";
}
};
#endif // EDITSCENE_ANIMATIONTREETEMPLATEEDITOR_HPP