feat(creatures): animate creature walk cycles

Add a Gait component synchronized over the network and procedural animation for model nodes. Load animation parameters from assets/model/creature/pig/animation.json and apply leg swing, body bob, and head motion based on gait. Refactor Gait into its own header for reuse.
This commit is contained in:
2026-08-06 10:57:49 +08:00
parent b2187bf0ad
commit d6051486a8
18 changed files with 249 additions and 42 deletions

View File

@@ -1,5 +1,6 @@
#pragma once
#include "Cubed/gameplay/ecs/entity.hpp"
#include "Cubed/gameplay/gait.hpp"
#include "glm/ext/vector_float3.hpp"
#include "world/entity.pb.h"
@@ -13,7 +14,7 @@ public:
enum class Command { CREATE, DESTORY, UPDATE };
ClientEntityManager(ClientWorld& world);
void update();
void update(float dt);
void init();
void receive_entity_create(S2CEntityCreate& msg);
@@ -36,6 +37,7 @@ private:
EntityID id;
glm::vec3 pos;
glm::vec3 direction;
Gait gait;
};
using EntityMap = tbb::concurrent_hash_map<EntityID, entt::entity>;

View File

@@ -3,7 +3,6 @@
#include "Cubed/gameplay/ecs/animation.hpp"
#include "Cubed/gameplay/ecs/identity.hpp"
#include "Cubed/gameplay/ecs/transform.hpp"
#include "Cubed/gameplay/player.hpp"
namespace Cubed {
struct ClientPlayer {

View File

@@ -1,6 +1,6 @@
#pragma once
#include "Cubed/gameplay/player.hpp"
#include "Cubed/gameplay/gait.hpp"
namespace Cubed {
struct WalkPose {

View File

@@ -0,0 +1,21 @@
#pragma once
#include <stdexcept>
#include <utility>
namespace Cubed {
enum class Gait { STOP = 0, WALK = 1, RUN = 2 };
constexpr int get_gait_id(Gait gait) { return std::to_underlying(gait); }
inline Gait get_gait_from_id(int id) {
switch (id) {
case std::to_underlying(Gait::STOP):
return Gait::STOP;
case std::to_underlying(Gait::WALK):
return Gait::WALK;
case std::to_underlying(Gait::RUN):
return Gait::RUN;
default:
throw std::runtime_error("Unknown Gait");
}
}
} // namespace Cubed

View File

@@ -11,7 +11,6 @@
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/hitbox.hpp"
#include "Cubed/gameplay/item_stack.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/input/event.hpp"
#include "Cubed/input/input.hpp"

View File

@@ -1,23 +1,7 @@
#pragma once
#include "glm/ext/vector_float3.hpp"
#include <stdexcept>
#include <utility>
namespace Cubed {
enum class Gait { STOP = 0, WALK = 1, RUN = 2 };
constexpr int get_gait_id(Gait gait) { return std::to_underlying(gait); }
inline Gait get_gait_from_id(int id) {
switch (id) {
case std::to_underlying(Gait::STOP):
return Gait::STOP;
case std::to_underlying(Gait::WALK):
return Gait::WALK;
case std::to_underlying(Gait::RUN):
return Gait::RUN;
default:
throw std::runtime_error("Unknown Gait");
}
}
static constexpr glm::vec3 PLAYER_SIZE{0.6f, 1.8f, 0.6f};
} // namespace Cubed

View File

@@ -1,7 +1,7 @@
#pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/gait.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/player.hpp"
#include <absl/container/flat_hash_set.h>
#include <atomic>

View File

@@ -41,5 +41,6 @@ private:
IDMap m_id_map;
NameMap m_name_map;
Handle load_model(std::string_view model_name);
void load_anim_config(ModelNode& node, const std::string& path);
};
} // namespace Cubed

View File

@@ -9,6 +9,7 @@
#include <glm/glm.hpp>
#include <memory>
#include <string>
#include <string_view>
namespace Cubed {
struct Mesh {
@@ -32,11 +33,47 @@ struct Mesh {
(void*)offsetof(Vertex3D, nx));
};
};
struct NodeAnimRule {
enum class Role { NONE, LEG, HEAD };
std::string node;
Role role = Role::NONE;
float phase = 0.0f;
};
struct ModelAnimConfig {
float walk_speed = 6.0f;
float walk_amp = 25.0f;
float run_speed = 12.0f;
float run_amp = 40.0f;
float body_bob = 0.05f;
float head_amp = 4.0f;
std::vector<NodeAnimRule> nodes;
const NodeAnimRule* rule_for(std::string_view name) const {
for (const auto& r : nodes) {
if (r.node == name) {
return &r;
}
}
return nullptr;
}
bool has_role(NodeAnimRule::Role role) const {
for (const auto& r : nodes) {
if (r.role == role) {
return true;
}
}
return false;
}
};
struct ModelNode {
std::string name;
glm::mat4 transform{1.0f};
std::vector<Mesh> meshes;
std::vector<ModelNode> children;
ModelAnimConfig anim;
};
} // namespace Cubed

View File

@@ -1,5 +1,6 @@
#pragma once
#include "Cubed/gameplay/ecs/animation.hpp"
#include "Cubed/gameplay/model.hpp"
#include "Cubed/render/model_node.hpp"
#include "Cubed/shader.hpp"
@@ -12,15 +13,18 @@ class ModelRender {
public:
ModelRender(Renderer& renderer);
void render_model(ModelID id, const glm::vec3& pos, float yaw,
Camera& camera);
Camera& camera, const WalkPose& pose);
void shadow_pass(ModelID id, const glm::vec3& pos, float yaw,
Camera& camera);
Camera& camera, const WalkPose& pose);
private:
Renderer& m_renderer;
void render_node(const ModelNode& node, const glm::mat4& parent,
const glm::mat4& view, const Shader& shader, bool shadow);
const glm::mat4& view, const Shader& shader, bool shadow,
const WalkPose& pose, const ModelAnimConfig& cfg);
glm::mat4 pose_node(const ModelNode& node, const ModelAnimConfig& cfg,
const WalkPose& pose);
void render_mesh(const Mesh& mesh, bool shadow);
};
} // namespace Cubed