refactor(actor): 重构Actor类以支持完整变换体系
重构Actor类,添加变换矩阵计算和父子关系处理 - 添加本地和世界变换矩阵计算 - 实现锚点、旋转、缩放等变换属性 - 添加父子关系变换继承 - 优化子节点管理,支持按ZOrder排序 - 添加透明度继承功能
This commit is contained in:
53
.opencode/plans/完善actor旋转功能.md
Normal file
53
.opencode/plans/完善actor旋转功能.md
Normal 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` 确保项目编译通过
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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_) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user