refactor(actor): 重构Actor类以支持完整变换体系

重构Actor类,添加变换矩阵计算和父子关系处理
- 添加本地和世界变换矩阵计算
- 实现锚点、旋转、缩放等变换属性
- 添加父子关系变换继承
- 优化子节点管理,支持按ZOrder排序
- 添加透明度继承功能
This commit is contained in:
2026-03-21 03:34:56 +08:00
parent 4870627b4d
commit 9a5b36392c
7 changed files with 267 additions and 99 deletions

View File

@@ -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` 确保项目编译通过

View File

@@ -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<Actor> 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;
};

View File

@@ -1,16 +1,10 @@
#pragma once
#include <frostbite2D/base/RefObject.h>
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/utils/intrusive_list.hpp>
#include <frostbite2D/2d/actor.h>
#include <vector>
namespace frostbite2D {
using ActorList = IntrusiveList<RefPtr<Actor>>;
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> actor);
void RemoveActor(RefPtr<Actor> 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;
};

View File

@@ -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<Actor> 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<Actor> child) {
if (!child) {
return;
@@ -39,7 +161,20 @@ void Actor::AddChild(Ptr<Actor> 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<Actor> child) {

View File

@@ -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_) {

View File

@@ -1,5 +1,4 @@
#include <frostbite2D/scene/scene.h>
#include <frostbite2D/2d/actor.h>
#include <SDL2/SDL.h>
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> actor) {
if (!actor) {
return;
}
if (actor->GetParent()) {
return;
}
actor->SetScene(this);
actors_.PushBack(actor);
}
void Scene::RemoveActor(Ptr<Actor> actor) {
if (!actor || actor->GetScene() != this) {
return;
}
actor->SetScene(nullptr);
actors_.Remove(actor);
}
void Scene::RemoveAllActors() {
while (!actors_.IsEmpty()) {
Ptr<Actor> 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();
}
}

View File

@@ -1,4 +1,5 @@
#include "SDL_log.h"
#include "frostbite2D/types/type_math.h"
#include <SDL2/SDL.h>
#include <frostbite2D/core/application.h>
#include <frostbite2D/core/window.h>
@@ -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");