From 9a5b36392c0dae7ae826d2510c1706917d7f61a9 Mon Sep 17 00:00:00 2001 From: Lenheart <947330670@qq.com> Date: Sat, 21 Mar 2026 03:34:56 +0800 Subject: [PATCH] =?UTF-8?q?refactor(actor):=20=E9=87=8D=E6=9E=84Actor?= =?UTF-8?q?=E7=B1=BB=E4=BB=A5=E6=94=AF=E6=8C=81=E5=AE=8C=E6=95=B4=E5=8F=98?= =?UTF-8?q?=E6=8D=A2=E4=BD=93=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构Actor类,添加变换矩阵计算和父子关系处理 - 添加本地和世界变换矩阵计算 - 实现锚点、旋转、缩放等变换属性 - 添加父子关系变换继承 - 优化子节点管理,支持按ZOrder排序 - 添加透明度继承功能 --- .opencode/plans/完善actor旋转功能.md | 53 +++++++ Frostbite2D/include/frostbite2D/2d/actor.h | 40 +++-- Frostbite2D/include/frostbite2D/scene/scene.h | 23 +-- Frostbite2D/src/frostbite2D/2d/actor.cpp | 139 +++++++++++++++++- Frostbite2D/src/frostbite2D/2d/sprite.cpp | 37 ++++- Frostbite2D/src/frostbite2D/scene/scene.cpp | 51 +------ Game/src/main.cpp | 23 ++- 7 files changed, 267 insertions(+), 99 deletions(-) create mode 100644 .opencode/plans/完善actor旋转功能.md diff --git a/.opencode/plans/完善actor旋转功能.md b/.opencode/plans/完善actor旋转功能.md new file mode 100644 index 0000000..41505fc --- /dev/null +++ b/.opencode/plans/完善actor旋转功能.md @@ -0,0 +1,53 @@ +# 完善 Actor 旋转功能 + +## 问题描述 +Actor 的 SetRotation 方法需要改进,增加角度规范化和相对旋转功能。 + +## 解决方案 + +### 1. 改进 SetRotation 方法 +- 添加角度规范化到 [0, 360) 范围 +- 使用已有的 `normalizeAngle360` 函数 + +### 2. 添加 RotateBy 方法 +- 新增相对旋转方法,用于在当前角度基础上增加/减少角度 +- 同样会进行角度规范化 + +## 实现步骤 + +### 文件修改 + +1. **Frostbite2D/include/frostbite2D/2d/actor.h** + - 在 `SetRotation` 方法声明后添加 `RotateBy` 方法声明 + +2. **Frostbite2D/src/frostbite2D/2d/actor.cpp** + - 改进 `SetRotation` 方法,添加角度规范化 + - 实现 `RotateBy` 方法 + +## 代码变更 + +### actor.h 修改 +在第 43 行后添加: +```cpp +void RotateBy(float deltaRotation); +``` + +### actor.cpp 修改 +改进 SetRotation 方法(第 37-40 行): +```cpp +void Actor::SetRotation(float rotation) { + rotation_ = normalizeAngle360(rotation); + markTransformDirty(); +} +``` + +添加 RotateBy 方法: +```cpp +void Actor::RotateBy(float deltaRotation) { + rotation_ = normalizeAngle360(rotation_ + deltaRotation); + markTransformDirty(); +} +``` + +## 验证 +- 运行 `xmake build` 确保项目编译通过 diff --git a/Frostbite2D/include/frostbite2D/2d/actor.h b/Frostbite2D/include/frostbite2D/2d/actor.h index 8bcc59e..a2bda6b 100644 --- a/Frostbite2D/include/frostbite2D/2d/actor.h +++ b/Frostbite2D/include/frostbite2D/2d/actor.h @@ -36,28 +36,36 @@ public: void SetName(const std::string& name) { name_ = name; } const Vec2& GetPosition() const { return position_; } - void SetPosition(const Vec2& pos) { position_ = pos; } - void SetPosition(float x, float y) { position_ = Vec2(x, y); } + void SetPosition(const Vec2& pos); + void SetPosition(float x, float y); float GetRotation() const { return rotation_; } - void SetRotation(float rotation) { rotation_ = rotation; } + void SetRotation(float rotation); const Vec2& GetScale() const { return scale_; } - void SetScale(const Vec2& scale) { scale_ = scale; } - void SetScale(float scale) { scale_ = Vec2(scale, scale); } + void SetScale(const Vec2& scale); + void SetScale(float scale); + + const Transform2D& GetLocalTransform() const; + const Transform2D& GetWorldTransform() const; const Vec2& GetSize() const { return size_; } - void SetSize(const Vec2& size) { size_ = size; } - void SetSize(float width, float height) { size_ = Vec2(width, height); } + void SetSize(const Vec2& size); + void SetSize(float width, float height); + + const Vec2& GetAnchor() const { return anchor_; } + void SetAnchor(const Vec2& anchor); + void SetAnchor(float x, float y); bool IsVisible() const { return visible_; } void SetVisible(bool visible) { visible_ = visible; } int GetZOrder() const { return zOrder_; } - void SetZOrder(int zOrder) { zOrder_ = zOrder; } + void SetZOrder(int zOrder); float GetOpacity() const { return opacity_; } - void SetOpacity(float opacity) { opacity_ = opacity; } + void SetOpacity(float opacity); + float GetWorldOpacity() const; ActorList& GetChildren() { return children_; } const ActorList& GetChildren() const { return children_; } @@ -66,9 +74,12 @@ protected: void SetParent(Actor* parent) { parent_ = parent; } void SetScene(Scene* scene) { scene_ = scene; } + void insertChildByZOrder(RefPtr child); void UpdateChildren(float deltaTime); void RenderChildren(); + virtual void OnTransformChanged() {}; + private: Actor* parent_; Scene* scene_; @@ -77,12 +88,21 @@ private: std::string name_; Vec2 position_; float rotation_; - Vec2 scale_; + Vec2 scale_ = Vec2(1.0f, 1.0f); Vec2 size_; + Vec2 anchor_; bool visible_; int zOrder_; float opacity_; + mutable Transform2D localTransform_; + mutable Transform2D worldTransform_; + mutable bool transformDirty_ = true; + + void markTransformDirty(); + void updateLocalTransform() const; + void updateWorldTransform() const; + friend class Scene; }; diff --git a/Frostbite2D/include/frostbite2D/scene/scene.h b/Frostbite2D/include/frostbite2D/scene/scene.h index d0c5b12..6031dbb 100644 --- a/Frostbite2D/include/frostbite2D/scene/scene.h +++ b/Frostbite2D/include/frostbite2D/scene/scene.h @@ -1,16 +1,10 @@ #pragma once -#include -#include -#include #include -#include namespace frostbite2D { -using ActorList = IntrusiveList>; - -class Scene : public RefObject { +class Scene : public Actor { public: Scene(); virtual ~Scene(); @@ -18,21 +12,10 @@ public: virtual void onEnter(); virtual void onExit(); - void Update(float deltaTime); - void Render(); - - void AddActor(RefPtr actor); - void RemoveActor(RefPtr actor); - void RemoveAllActors(); - -protected: - void UpdateActors(float deltaTime); - void RenderActors(); + void Update(float deltaTime) override; + void Render() override; private: - ActorList actors_; - - friend class Actor; friend class SceneManager; }; diff --git a/Frostbite2D/src/frostbite2D/2d/actor.cpp b/Frostbite2D/src/frostbite2D/2d/actor.cpp index 7a8408f..79298f5 100644 --- a/Frostbite2D/src/frostbite2D/2d/actor.cpp +++ b/Frostbite2D/src/frostbite2D/2d/actor.cpp @@ -9,7 +9,107 @@ Actor::Actor() , rotation_(0.0f) , visible_(true) , zOrder_(0) - , opacity_(1.0f) { + , opacity_(1.0f) + , anchor_(Vec2(0.0f, 0.0f)) { +} + +void Actor::SetOpacity(float opacity) { + opacity_ = std::clamp(opacity, 0.0f, 1.0f); +} + +float Actor::GetWorldOpacity() const { + float worldOpacity = opacity_; + if (parent_) { + worldOpacity *= parent_->GetWorldOpacity(); + } + return worldOpacity; +} + +void Actor::SetPosition(const Vec2& pos) { + position_ = pos; + markTransformDirty(); +} + +void Actor::SetPosition(float x, float y) { + position_ = Vec2(x, y); + markTransformDirty(); +} + +void Actor::SetRotation(float rotation) { + rotation_ = rotation; + markTransformDirty(); +} + +void Actor::SetScale(const Vec2& scale) { + scale_ = scale; + markTransformDirty(); +} + +void Actor::SetScale(float scale) { + scale_ = Vec2(scale, scale); + markTransformDirty(); +} + +void Actor::SetSize(const Vec2& size) { + size_ = size; + markTransformDirty(); +} + +void Actor::SetSize(float width, float height) { + size_ = Vec2(width, height); + markTransformDirty(); +} + +void Actor::SetAnchor(const Vec2& anchor) { + anchor_ = anchor; + markTransformDirty(); +} + +void Actor::SetAnchor(float x, float y) { + anchor_ = Vec2(x, y); + markTransformDirty(); +} + +const Transform2D& Actor::GetLocalTransform() const { + if (transformDirty_) { + updateLocalTransform(); + } + return localTransform_; +} + +const Transform2D& Actor::GetWorldTransform() const { + if (transformDirty_) { + updateLocalTransform(); + updateWorldTransform(); + transformDirty_ = false; + } + return worldTransform_; +} + +void Actor::markTransformDirty() { + transformDirty_ = true; + OnTransformChanged(); + for (auto it = children_.begin(); it != children_.end(); ++it) { + if (*it) { + (*it)->markTransformDirty(); + } + } +} + +void Actor::updateLocalTransform() const { + Vec2 anchorOffset = Vec2(anchor_.x * size_.x, anchor_.y * size_.y); + localTransform_ = Transform2D::translation(position_.x, position_.y) * + Transform2D::rotation(rotation_) * + Transform2D::scaling(scale_.x, scale_.y) * + Transform2D::translation(-anchorOffset.x, -anchorOffset.y); +} + +void Actor::updateWorldTransform() const { + if (parent_) { + worldTransform_ = parent_->GetWorldTransform() * localTransform_; + } else { + worldTransform_ = localTransform_; + } } Actor::~Actor() { @@ -24,6 +124,28 @@ void Actor::Render() { RenderChildren(); } +void Actor::insertChildByZOrder(Ptr child) { + if (!child) { + return; + } + + if (children_.IsEmpty()) { + children_.PushBack(child); + return; + } + + auto it = children_.begin(); + while (it != children_.end()) { + if (*it && (*it)->GetZOrder() > child->GetZOrder()) { + children_.InsertBefore(child, *it); + return; + } + ++it; + } + + children_.PushBack(child); +} + void Actor::AddChild(Ptr child) { if (!child) { return; @@ -39,7 +161,20 @@ void Actor::AddChild(Ptr child) { child->SetParent(this); child->SetScene(scene_); - children_.PushBack(child); + insertChildByZOrder(child); +} + +void Actor::SetZOrder(int zOrder) { + if (zOrder_ == zOrder) { + return; + } + + zOrder_ = zOrder; + + if (parent_) { + parent_->RemoveChild(this); + parent_->AddChild(this); + } } void Actor::RemoveChild(Ptr child) { diff --git a/Frostbite2D/src/frostbite2D/2d/sprite.cpp b/Frostbite2D/src/frostbite2D/2d/sprite.cpp index da99782..7d0987b 100644 --- a/Frostbite2D/src/frostbite2D/2d/sprite.cpp +++ b/Frostbite2D/src/frostbite2D/2d/sprite.cpp @@ -57,16 +57,40 @@ void Sprite::Render() { Renderer& renderer = Renderer::get(); - Vec2 pos = GetPosition(); Vec2 size = GetSize(); if (size.x == 0 || size.y == 0) { size = Vec2((float)texture_->getWidth(), (float)texture_->getHeight()); } - // 直接使用 Renderer 的 drawSprite 函数,确保和直接渲染一致 - renderer.drawSprite(pos, Rect(0, 0, (float)texture_->getWidth(), (float)texture_->getHeight()), - Vec2((float)texture_->getWidth(), (float)texture_->getHeight()), - texture_, color_); + // 使用完整的变换矩阵进行渲染 + Rect destRect(0, 0, size.x, size.y); + Rect srcRect = srcRect_; + if (srcRect.empty()) { + srcRect = Rect(0, 0, (float)texture_->getWidth(), (float)texture_->getHeight()); + } + + float worldOpacity = GetWorldOpacity(); + Quad quad = Quad::createTextured( + destRect, srcRect, + Vec2((float)texture_->getWidth(), (float)texture_->getHeight()), + color_.r, color_.g, color_.b, color_.a * worldOpacity + ); + + if (flippedX_) { + std::swap(quad.vertices[0].texCoord, quad.vertices[1].texCoord); + std::swap(quad.vertices[2].texCoord, quad.vertices[3].texCoord); + } + if (flippedY_) { + std::swap(quad.vertices[0].texCoord, quad.vertices[2].texCoord); + std::swap(quad.vertices[1].texCoord, quad.vertices[3].texCoord); + } + + auto* shader = renderer.getShaderManager().getShader("sprite"); + if (shader) { + shader->use(); + } + + renderer.getBatch().submitQuad(quad, GetWorldTransform(), texture_, shader, blendMode_); RenderChildren(); } @@ -160,9 +184,10 @@ Quad Sprite::createQuad() const { Vec2 texSize(texture_->getWidth(), texture_->getHeight()); + float worldOpacity = GetWorldOpacity(); Quad quad = Quad::createTextured( destRect, srcRect, texSize, - color_.r, color_.g, color_.b, color_.a + color_.r, color_.g, color_.b, color_.a * worldOpacity ); if (flippedX_) { diff --git a/Frostbite2D/src/frostbite2D/scene/scene.cpp b/Frostbite2D/src/frostbite2D/scene/scene.cpp index a560d57..898af48 100644 --- a/Frostbite2D/src/frostbite2D/scene/scene.cpp +++ b/Frostbite2D/src/frostbite2D/scene/scene.cpp @@ -1,5 +1,4 @@ #include -#include #include namespace frostbite2D { @@ -8,7 +7,6 @@ Scene::Scene() { } Scene::~Scene() { - RemoveAllActors(); } void Scene::onEnter() { @@ -20,56 +18,11 @@ void Scene::onExit() { } void Scene::Update(float deltaTime) { - UpdateActors(deltaTime); + UpdateChildren(deltaTime); } void Scene::Render() { - RenderActors(); -} - -void Scene::AddActor(Ptr actor) { - if (!actor) { - return; - } - - if (actor->GetParent()) { - return; - } - - actor->SetScene(this); - actors_.PushBack(actor); -} - -void Scene::RemoveActor(Ptr actor) { - if (!actor || actor->GetScene() != this) { - return; - } - - actor->SetScene(nullptr); - actors_.Remove(actor); -} - -void Scene::RemoveAllActors() { - while (!actors_.IsEmpty()) { - Ptr actor = actors_.GetFirst(); - RemoveActor(actor); - } -} - -void Scene::UpdateActors(float deltaTime) { - for (auto it = actors_.begin(); it != actors_.end(); ++it) { - if (*it && (*it)->IsVisible()) { - (*it)->Update(deltaTime); - } - } -} - -void Scene::RenderActors() { - for (auto it = actors_.begin(); it != actors_.end(); ++it) { - if (*it && (*it)->IsVisible()) { - (*it)->Render(); - } - } + RenderChildren(); } } diff --git a/Game/src/main.cpp b/Game/src/main.cpp index ecec632..6be8e04 100644 --- a/Game/src/main.cpp +++ b/Game/src/main.cpp @@ -1,4 +1,5 @@ #include "SDL_log.h" +#include "frostbite2D/types/type_math.h" #include #include #include @@ -46,8 +47,13 @@ int main(int argc, char **argv) { // 尝试加载精灵 auto sprite = Sprite::createFromFile("assets/player.png"); if (sprite) { - sprite->SetPosition(0, 0); - menuScene->AddActor(sprite); + sprite->SetPosition(320, 300); + sprite->SetOpacity(0.8f); + // sprite->SetAnchor(Vec2(0.5f, 0.5f)); + // sprite->SetRotation(30.f); + sprite->SetZOrder(2000); + // sprite->SetScale(Vec2(-1.0f, 1.0f)); + menuScene->AddChild(sprite); SDL_Log("Sprite created and added to scene"); } else { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite from file!"); @@ -119,9 +125,9 @@ int main(int argc, char **argv) { auto sprite1 = Sprite::createFromNpk("sprite/newtitle/nangua.img", 0); if (sprite1) { - sprite1->SetPosition(0, 0); - sprite1->SetScale(5.0f); - menuScene->AddActor(sprite1); + sprite1->SetPosition(220, 10); + // sprite1->SetScale(2.0f); + sprite->AddChild(sprite1); } else { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite from NPK!"); } @@ -137,13 +143,6 @@ int main(int argc, char **argv) { // SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load sound!"); // } - auto music = Music::loadFromNpk("sounds/ui/amazing_box.ogg"); - if (music) { - music->play(); - } else { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load music!"); - } - auto &audioDB = AudioDatabase::get(); audioDB.loadFromFile("assets/audio.xml");