完成动画组件与动画载入

This commit is contained in:
Ziyu
2025-06-16 14:44:47 +08:00
parent fc8a8cec5c
commit a31890fc20
20 changed files with 477 additions and 5 deletions
@@ -0,0 +1,95 @@
#include "animation_component.h"
#include "sprite_component.h"
#include "../object/game_object.h"
#include "../render/animation.h"
#include <spdlog/spdlog.h>
namespace engine::component {
AnimationComponent::~AnimationComponent() = default;
void AnimationComponent::init() {
if (!owner_) {
spdlog::error("AnimationComponent 没有所有者 GameObject!");
return;
}
sprite_component_ = owner_->getComponent<SpriteComponent>();
if (!sprite_component_) {
spdlog::error("GameObject '{}' 的 AnimationComponent 需要 SpriteComponent,但未找到。", owner_->getName());
return;
}
}
void AnimationComponent::update(float delta_time, engine::core::Context&) {
// 如果没有正在播放的动画,或者没有当前动画,或者没有精灵组件,或者当前动画没有帧,则直接返回
if (!is_playing_ || !current_animation_ || !sprite_component_ || current_animation_->isEmpty()) {
spdlog::trace("AnimationComponent 更新时没有正在播放的动画或精灵组件为空。");
return;
}
// 推进计时器
animation_timer_ += delta_time;
// 根据时间获取当前帧
const auto& current_frame = current_animation_->getFrame(animation_timer_);
// 更新精灵组件的源矩形 (使用 SpriteComponent 的新方法)
sprite_component_->setSourceRect(current_frame.source_rect);
// 检查非循环动画是否已结束
if (!current_animation_->isLooping() && animation_timer_ >= current_animation_->getTotalDuration()) {
is_playing_ = false;
animation_timer_ = current_animation_->getTotalDuration(); // 将时间限制在结束点
if (is_one_shot_removal_) { // 如果 is_one_shot_removal_ 为 true,则删除整个 GameObject
owner_->setNeedRemove(true);
}
}
}
void AnimationComponent::addAnimation(std::unique_ptr<engine::render::Animation> animation) {
if (!animation) return;
std::string name = animation->getName(); // 获取名称
animations_[name] = std::move(animation);
spdlog::debug("已将动画 '{}' 添加到 GameObject '{}'", name, owner_ ? owner_->getName() : "未知");
}
void AnimationComponent::playAnimation(const std::string& name) {
auto it = animations_.find(name);
if (it == animations_.end() || !it->second) {
spdlog::warn("未找到 GameObject '{}' 的动画 '{}'", name, owner_ ? owner_->getName() : "未知");
return;
}
// 如果已经在播放相同的动画,不重新开始(注释这一段则重新开始播放)
if (current_animation_ == it->second.get() && is_playing_) {
return;
}
current_animation_ = it->second.get();
animation_timer_ = 0.0f;
is_playing_ = true;
// 立即将精灵更新到第一帧
if (sprite_component_ && !current_animation_->isEmpty()) {
const auto& first_frame = current_animation_->getFrame(0.0f);
sprite_component_->setSourceRect(first_frame.source_rect);
spdlog::debug("GameObject '{}' 播放动画 '{}'", owner_ ? owner_->getName() : "未知", name);
}
}
std::string AnimationComponent::getCurrentAnimationName() const {
if (current_animation_) {
return current_animation_->getName();
}
return "";
}
bool AnimationComponent::isAnimationFinished() const {
// 如果没有当前动画(说明从未调用过playAnimation),或者当前动画是循环的,则返回 false
if (!current_animation_ || current_animation_->isLooping()) {
return false;
}
return animation_timer_ >= current_animation_->getTotalDuration();
}
} // namespace engine::component
@@ -0,0 +1,61 @@
#pragma once
#include "./component.h"
#include <string>
#include <unordered_map>
#include <memory>
namespace engine::render {
class Animation;
}
namespace engine::component {
class SpriteComponent;
}
namespace engine::component {
/**
* @brief GameObject的动画组件。
*
* 持有一组Animation对象并控制其播放,
* 根据当前帧更新关联的SpriteComponent。
*/
class AnimationComponent : public Component {
friend class engine::object::GameObject;
private:
/// @brief 动画名称到Animation对象的映射。
std::unordered_map<std::string, std::unique_ptr<engine::render::Animation>> animations_;
SpriteComponent* sprite_component_ = nullptr; ///< @brief 指向必需的SpriteComponent的指针
engine::render::Animation* current_animation_ = nullptr; ///< @brief 指向当前播放动画的原始指针
float animation_timer_ = 0.0f; ///< @brief 动画播放中的计时器
bool is_playing_ = false; ///< @brief 当前是否有动画正在播放
bool is_one_shot_removal_ = false; ///< @brief 是否在动画结束后删除整个GameObject
public:
AnimationComponent() = default;
~AnimationComponent() override;
// 删除复制/移动操作
AnimationComponent(const AnimationComponent&) = delete;
AnimationComponent& operator=(const AnimationComponent&) = delete;
AnimationComponent(AnimationComponent&&) = delete;
AnimationComponent& operator=(AnimationComponent&&) = delete;
void addAnimation(std::unique_ptr<engine::render::Animation> animation); ///< @brief 向 animations_ map容器中添加一个动画。
void playAnimation(const std::string& name); ///< @brief 播放指定名称的动画。
void stopAnimation() { is_playing_ = false; } ///< @brief 停止当前动画播放。
// --- Getters and Setters ---
std::string getCurrentAnimationName() const;
bool isPlaying() const { return is_playing_; }
bool isAnimationFinished() const;
bool isOneShotRemoval() const { return is_one_shot_removal_; }
void setOneShotRemoval(bool is_one_shot_removal) { is_one_shot_removal_ = is_one_shot_removal; }
protected:
// 核心循环方法
void init() override;
void update(float, engine::core::Context&) override;
};
} // namespace engine::component
+51
View File
@@ -0,0 +1,51 @@
#include "animation.h"
#include <glm/common.hpp>
#include <spdlog/spdlog.h>
namespace engine::render {
Animation::Animation(const std::string& name, bool loop)
: name_(name), loop_(loop) {}
void Animation::addFrame(const SDL_FRect& source_rect, float duration) {
if (duration <= 0.0f) {
spdlog::warn("尝试向动画 '{}' 添加无效持续时间的帧", name_);
return;
}
frames_.push_back({source_rect, duration});
total_duration_ += duration;
}
const AnimationFrame& Animation::getFrame(float time) const {
if (frames_.empty()) {
spdlog::error("动画 '{}' 没有帧,无法获取帧", name_);
return frames_.back(); // 返回最后一帧(空的)
}
float current_time = time;
if (loop_ && total_duration_ > 0.0f) {
// 对循环动画使用模运算获取有效时间
current_time = glm::mod(time, total_duration_);
} else {
// 对于非循环动画,如果时间超过总时长,则停留在最后一帧
if (current_time >= total_duration_) {
return frames_.back();
}
}
// 遍历帧以找到正确的帧
float accumulated_time = 0.0f;
for (const auto& frame : frames_) {
accumulated_time += frame.duration;
if (current_time < accumulated_time) {
return frame;
}
}
// 理论上在不应到达这里,但为了安全起见,返回最后一帧
spdlog::warn("动画 '{}' 在获取帧信息时出现错误。", name_);
return frames_.back();
}
} // namespace engine::render
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#include <SDL3/SDL_rect.h>
#include <vector>
#include <string>
namespace engine::render {
/**
* @brief 代表动画中的单个帧。
*
* 包含纹理图集上的源矩形和该帧的显示持续时间。
*/
struct AnimationFrame {
SDL_FRect source_rect; ///< @brief 纹理图集上此帧的区域
float duration; ///< @brief 此帧显示的持续时间(秒)
};
/**
* @brief 管理一系列动画帧。
*
* 存储动画的帧、总时长、名称和循环行为。
*/
class Animation final {
private:
std::string name_; ///< @brief 动画的名称 (例如, "walk", "idle")。
std::vector<AnimationFrame> frames_; ///< @brief 动画帧列表
float total_duration_ = 0.0f; ///< @brief 动画的总持续时间(秒)
bool loop_ = true; ///< @brief 默认动画是循环的
public:
/**
* @brief 构造函数
* @param name 动画的名称。
* @param loop 动画是否应该循环播放。
*/
Animation(const std::string& name = "default", bool loop = true);
~Animation() = default;
// 禁止拷贝和移动,因为 Animation 通常由管理器持有,不应随意拷贝
Animation(const Animation&) = delete;
Animation& operator=(const Animation&) = delete;
Animation(Animation&&) = delete;
Animation& operator=(Animation&&) = delete;
/**
* @brief 向动画添加一帧。
*
* @param source_rect 纹理图集上此帧的区域。
* @param duration 此帧应显示的持续时间(秒)。
*/
void addFrame(const SDL_FRect& source_rect, float duration);
/**
* @brief 获取在给定时间点应该显示的动画帧。
* @param time 当前时间(秒)。如果动画循环,则可以超过总持续时间。
* @return 对应时间点的动画帧。
*/
const AnimationFrame& getFrame(float time) const;
// --- Setters and Getters ---
const std::string& getName() const { return name_; } ///< @brief 获取动画名称。
const std::vector<AnimationFrame>& getFrames() const { return frames_; } ///< @brief 获取动画帧列表。
size_t getFrameCount() const { return frames_.size(); } ///< @brief 获取帧数量。
float getTotalDuration() const { return total_duration_; } ///< @brief 获取动画的总持续时间(秒)。
bool isLooping() const { return loop_; } ///< @brief 检查动画是否循环播放。
bool isEmpty() const { return frames_.empty(); } ///< @brief 检查动画是否没有帧。
void setName(const std::string& name) { name_ = name; } ///< @brief 设置动画名称。
void setLooping(bool loop) { loop_ = loop; } ///< @brief 设置动画是否循环播放。
};
} // namespace engine::render
+68
View File
@@ -5,11 +5,13 @@
#include "../component/sprite_component.h"
#include "../component/collider_component.h"
#include "../component/physics_component.h"
#include "../component/animation_component.h"
#include "../object/game_object.h"
#include "../scene/scene.h"
#include "../core/context.h"
#include "../resource/resource_manager.h"
#include "../render/sprite.h"
#include "../render/animation.h"
#include "../utils/math.h"
#include <nlohmann/json.hpp>
#include <fstream>
@@ -229,6 +231,23 @@ void LevelLoader::loadObjectLayer(const nlohmann::json& layer_json, Scene& scene
}
}
// 获取动画信息并设置
auto anim_string = getTileProperty<std::string>(tile_json, "animation");
if (anim_string) {
// 解析string为JSON对象
nlohmann::json anim_json;
try {
anim_json = nlohmann::json::parse(anim_string.value());
} catch (const nlohmann::json::parse_error& e) {
spdlog::error("解析动画 JSON 字符串失败: {}", e.what());
continue; // 跳过此对象
}
// 添加AnimationComponent
auto* ac = game_object->addComponent<engine::component::AnimationComponent>();
// 添加动画到 AnimationComponent
addAnimation(anim_json, ac, src_size);
}
// 添加到场景中
scene.addGameObject(std::move(game_object));
spdlog::info("加载对象: '{}' 完成", object_name);
@@ -236,6 +255,55 @@ void LevelLoader::loadObjectLayer(const nlohmann::json& layer_json, Scene& scene
}
}
void LevelLoader::addAnimation(const nlohmann::json& anim_json, engine::component::AnimationComponent *ac, const glm::vec2& sprite_size)
{
// 检查 anim_json 必须是一个对象,并且 ac 不能为 nullptr
if (!anim_json.is_object() || !ac) {
spdlog::error("无效的动画 JSON 或 AnimationComponent 指针。");
return;
}
// 遍历动画 JSON 对象中的每个键值对(动画名称 : 动画信息)
for (const auto& anim : anim_json.items()) {
const std::string& anim_name = anim.key();
const auto& anim_info = anim.value();
if (!anim_info.is_object()) {
spdlog::warn("动画 '{}' 的信息无效或为空。", anim_name);
continue;
}
// 获取可能存在的动画帧信息
auto duration_ms = anim_info.value("duration", 100); // 默认持续时间为100毫秒
auto duration = static_cast<float>(duration_ms) / 1000.0f; // 转换为秒
auto row = anim_info.value("row", 0); // 默认行数为0
// 帧信息(数组)是必须存在的
if (!anim_info.contains("frames") || !anim_info["frames"].is_array()) {
spdlog::warn("动画 '{}' 缺少 'frames' 数组。", anim_name);
continue;
}
// 创建一个Animation对象 (默认为循环播放)
auto animation = std::make_unique<engine::render::Animation>(anim_name);
// 遍历数组并进行添加帧信息到animation对象
for (const auto& frame : anim_info["frames"]) {
if (!frame.is_number_integer()) {
spdlog::warn("动画 {} 中 frames 数组格式错误!", anim_name);
continue;;
}
auto column = frame.get<int>();
// 计算源矩形
SDL_FRect src_rect = {
column * sprite_size.x,
row * sprite_size.y,
sprite_size.x,
sprite_size.y
};
// 添加动画帧到 Animation
animation->addFrame(src_rect, duration);
}
// 将 Animation 对象添加到 AnimationComponent 中
ac->addAnimation(std::move(animation));
}
}
std::optional<engine::utils::Rect> LevelLoader::getColliderRect(const nlohmann::json &tile_json)
{
if (!tile_json.contains("objectgroup")) return std::nullopt;
+9
View File
@@ -7,6 +7,7 @@
#include "../utils/math.h"
namespace engine::component {
class AnimationComponent;
struct TileInfo;
enum class TileType;
}
@@ -39,6 +40,14 @@ private:
void loadTileLayer(const nlohmann::json& layer_json, Scene& scene); ///< @brief 加载瓦片图层
void loadObjectLayer(const nlohmann::json& layer_json, Scene& scene); ///< @brief 加载对象图层
/**
* @brief 添加动画到指定的 AnimationComponent。
* @param anim_json 动画json数据(自定义)
* @param ac AnimationComponent 指针(动画添加到此组件)
* @param sprite_size 每一帧动画的尺寸
*/
void addAnimation(const nlohmann::json& anim_json, engine::component::AnimationComponent* ac, const glm::vec2& sprite_size);
/**
* @brief 获取瓦片属性
* @tparam T 属性类型
+3 -1
View File
@@ -3,6 +3,7 @@
#include "../../engine/component/transform_component.h"
#include "../../engine/component/physics_component.h"
#include "../../engine/component/sprite_component.h"
#include "../../engine/component/animation_component.h"
#include "../../engine/object/game_object.h"
#include "../../engine/input/input_manager.h"
#include <utility>
@@ -21,9 +22,10 @@ void PlayerComponent::init() {
transform_component_ = owner_->getComponent<engine::component::TransformComponent>();
physics_component_ = owner_->getComponent<engine::component::PhysicsComponent>();
sprite_component_ = owner_->getComponent<engine::component::SpriteComponent>();
animation_component_ = owner_->getComponent<engine::component::AnimationComponent>();
// 检查必要组件是否存在
if (!transform_component_ || !physics_component_ || !sprite_component_) {
if (!transform_component_ || !physics_component_ || !sprite_component_ || !animation_component_) {
spdlog::error("Player 对象缺少必要组件!");
}
+3
View File
@@ -10,6 +10,7 @@ namespace engine::component {
class TransformComponent;
class PhysicsComponent;
class SpriteComponent;
class AnimationComponent;
}
namespace game::component::state {
@@ -28,6 +29,7 @@ private:
engine::component::TransformComponent* transform_component_ = nullptr; // 指向 TransformComponent 的非拥有指针
engine::component::SpriteComponent* sprite_component_ = nullptr;
engine::component::PhysicsComponent* physics_component_ = nullptr;
engine::component::AnimationComponent* animation_component_ = nullptr;
std::unique_ptr<state::PlayerState> current_state_;
bool is_dead_ = false;
@@ -52,6 +54,7 @@ public:
engine::component::TransformComponent* getTransformComponent() const { return transform_component_; }
engine::component::SpriteComponent* getSpriteComponent() const { return sprite_component_; }
engine::component::PhysicsComponent* getPhysicsComponent() const { return physics_component_; }
engine::component::AnimationComponent* getAnimationComponent() const { return animation_component_; }
void setIsDead(bool is_dead) { is_dead_ = is_dead; } ///< @brief 设置玩家是否死亡
bool isDead() const { return is_dead_; } ///< @brief 获取玩家是否死亡
+1 -1
View File
@@ -11,7 +11,7 @@
namespace game::component::state {
void FallState::enter() {
playAnimation("fall"); // 播放下落动画
}
void FallState::exit() {
+1 -1
View File
@@ -11,7 +11,7 @@
namespace game::component::state {
void IdleState::enter() {
playAnimation("idle"); // 播放待机动画
}
void IdleState::exit() {
+1
View File
@@ -13,6 +13,7 @@
namespace game::component::state {
void JumpState::enter() {
playAnimation("jump"); // 播放跳跃动画
auto physics_component = player_component_->getPhysicsComponent();
physics_component->velocity_.y = -player_component_->getJumpForce(); // 向上跳跃
spdlog::debug("PlayerComponent 进入 JumpState,设置初始垂直速度为: {}", physics_component->velocity_.y);
+25
View File
@@ -0,0 +1,25 @@
#include "player_state.h"
#include "../player_component.h"
#include "../../../engine/component/animation_component.h"
#include "../../../engine/object/game_object.h"
#include <spdlog/spdlog.h>
namespace game::component::state {
void PlayerState::playAnimation(const std::string& animation_name) {
if (!player_component_) {
spdlog::error("PlayerState 没有关联的 PlayerComponent,无法播放动画 '{}'", animation_name);
return;
}
auto animation_component = player_component_->getAnimationComponent();
if (!animation_component) {
spdlog::error("PlayerComponent '{}' 没有 AnimationComponent,无法播放动画 '{}'",
player_component_->getOwner()->getName(), animation_name);
return;
}
animation_component->playAnimation(animation_name);
}
} // namespace game::component::state
+2
View File
@@ -30,6 +30,8 @@ public:
PlayerState(PlayerState&&) = delete;
PlayerState& operator=(PlayerState&&) = delete;
void playAnimation(const std::string& animation_name); ///< @brief 播放指定名称的动画,使用 AnimationComponent 的方法
protected:
// 核心状态方法
virtual void enter() = 0; ///< @brief 进入
+1 -1
View File
@@ -14,7 +14,7 @@
namespace game::component::state {
void WalkState::enter() {
playAnimation("walk"); // 播放步行动画
}
void WalkState::exit() {
+46
View File
@@ -7,6 +7,7 @@
#include "../../engine/component/physics_component.h"
#include "../../engine/component/collider_component.h"
#include "../../engine/component/tilelayer_component.h"
#include "../../engine/component/animation_component.h"
#include "../../engine/physics/physics_engine.h"
#include "../../engine/scene/level_loader.h"
#include "../../engine/input/input_manager.h"
@@ -39,6 +40,11 @@ void GameScene::init() {
context_.getInputManager().setShouldQuit(true);
return;
}
if (!initEnemyAndItem()) {
spdlog::error("敌人和道具初始化失败,无法继续。");
context_.getInputManager().setShouldQuit(true);
return;
}
Scene::init();
spdlog::trace("GameScene 初始化完成。");
@@ -121,4 +127,44 @@ bool GameScene::initPlayer()
return true;
}
bool GameScene::initEnemyAndItem()
{
bool success = true;
for (auto& game_object : game_objects_){
if (game_object->getName() == "eagle"){
if (auto* ac = game_object->getComponent<engine::component::AnimationComponent>(); ac){
ac->playAnimation("fly");
} else {
spdlog::error("Eagle对象缺少 AnimationComponent,无法播放动画。");
success = false;
}
}
if (game_object->getName() == "frog"){
if (auto* ac = game_object->getComponent<engine::component::AnimationComponent>(); ac){
ac->playAnimation("idle");
} else {
spdlog::error("Frog对象缺少 AnimationComponent,无法播放动画。");
success = false;
}
}
if (game_object->getName() == "opossum"){
if (auto* ac = game_object->getComponent<engine::component::AnimationComponent>(); ac){
ac->playAnimation("walk");
} else {
spdlog::error("Opossum对象缺少 AnimationComponent,无法播放动画。");
success = false;
}
}
if (game_object->getTag() == "item"){
if (auto* ac = game_object->getComponent<engine::component::AnimationComponent>(); ac){
ac->playAnimation("idle");
} else {
spdlog::error("Item对象缺少 AnimationComponent,无法播放动画。");
success = false;
}
}
}
return success;
}
} // namespace game::scene
+1
View File
@@ -28,6 +28,7 @@ public:
private:
[[nodiscard]] bool initLevel(); ///< @brief 初始化关卡
[[nodiscard]] bool initPlayer(); ///< @brief 初始化玩家
[[nodiscard]] bool initEnemyAndItem(); ///< @brief 初始化敌人和道具
};