2026-02-11 19:40:26 +08:00
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
#include <cmath>
|
2026-02-25 21:22:35 +08:00
|
|
|
|
#include <animation/tween.h>
|
2026-02-25 06:23:53 +08:00
|
|
|
|
#include <graphics/render_command.h>
|
|
|
|
|
|
#include <scene/node.h>
|
|
|
|
|
|
#include <scene/scene.h>
|
|
|
|
|
|
#include <utils/logger.h>
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
|
|
|
|
|
namespace extra2d {
|
|
|
|
|
|
|
|
|
|
|
|
Node::Node() = default;
|
|
|
|
|
|
|
|
|
|
|
|
Node::~Node() {
|
|
|
|
|
|
removeAllChildren();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::addChild(Ptr<Node> child) {
|
|
|
|
|
|
if (!child || child.get() == this) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
child->removeFromParent();
|
|
|
|
|
|
child->parent_ = weak_from_this();
|
|
|
|
|
|
children_.push_back(child);
|
|
|
|
|
|
childrenOrderDirty_ = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新索引
|
|
|
|
|
|
if (!child->getName().empty()) {
|
|
|
|
|
|
nameIndex_[child->getName()] = child;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (child->getTag() != -1) {
|
|
|
|
|
|
tagIndex_[child->getTag()] = child;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (running_) {
|
|
|
|
|
|
child->onEnter();
|
|
|
|
|
|
if (scene_) {
|
|
|
|
|
|
child->onAttachToScene(scene_);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::addChildren(std::vector<Ptr<Node>> &&children) {
|
|
|
|
|
|
// 预留空间,避免多次扩容
|
|
|
|
|
|
size_t newSize = children_.size() + children.size();
|
|
|
|
|
|
if (newSize > children_.capacity()) {
|
|
|
|
|
|
children_.reserve(newSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (auto &child : children) {
|
|
|
|
|
|
if (!child || child.get() == this) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
child->removeFromParent();
|
|
|
|
|
|
child->parent_ = weak_from_this();
|
|
|
|
|
|
children_.push_back(child);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新索引
|
|
|
|
|
|
if (!child->getName().empty()) {
|
|
|
|
|
|
nameIndex_[child->getName()] = child;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (child->getTag() != -1) {
|
|
|
|
|
|
tagIndex_[child->getTag()] = child;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (running_) {
|
|
|
|
|
|
child->onEnter();
|
|
|
|
|
|
if (scene_) {
|
|
|
|
|
|
child->onAttachToScene(scene_);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!children.empty()) {
|
|
|
|
|
|
childrenOrderDirty_ = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::removeChild(Ptr<Node> child) {
|
|
|
|
|
|
if (!child)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
auto it = std::find(children_.begin(), children_.end(), child);
|
|
|
|
|
|
if (it != children_.end()) {
|
|
|
|
|
|
// 始终从空间索引中移除(无论 running_ 状态)
|
|
|
|
|
|
// 这确保节点被正确清理
|
|
|
|
|
|
(*it)->onDetachFromScene();
|
|
|
|
|
|
|
|
|
|
|
|
if (running_) {
|
|
|
|
|
|
(*it)->onExit();
|
|
|
|
|
|
}
|
|
|
|
|
|
// 从索引中移除
|
|
|
|
|
|
if (!(*it)->getName().empty()) {
|
|
|
|
|
|
nameIndex_.erase((*it)->getName());
|
|
|
|
|
|
}
|
|
|
|
|
|
if ((*it)->getTag() != -1) {
|
|
|
|
|
|
tagIndex_.erase((*it)->getTag());
|
|
|
|
|
|
}
|
|
|
|
|
|
(*it)->parent_.reset();
|
|
|
|
|
|
children_.erase(it);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::removeChildByName(const std::string &name) {
|
|
|
|
|
|
auto child = getChildByName(name);
|
|
|
|
|
|
if (child) {
|
|
|
|
|
|
removeChild(child);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::removeFromParent() {
|
|
|
|
|
|
auto p = parent_.lock();
|
|
|
|
|
|
if (p) {
|
|
|
|
|
|
// 安全获取 shared_ptr,避免在对象未由 shared_ptr 管理时崩溃
|
|
|
|
|
|
Ptr<Node> self;
|
|
|
|
|
|
try {
|
|
|
|
|
|
self = shared_from_this();
|
|
|
|
|
|
} catch (const std::bad_weak_ptr &) {
|
|
|
|
|
|
// 对象不是由 shared_ptr 管理的,直接重置父节点引用
|
|
|
|
|
|
parent_.reset();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
p->removeChild(self);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::removeAllChildren() {
|
|
|
|
|
|
for (auto &child : children_) {
|
|
|
|
|
|
if (running_) {
|
|
|
|
|
|
child->onDetachFromScene();
|
|
|
|
|
|
child->onExit();
|
|
|
|
|
|
}
|
|
|
|
|
|
child->parent_.reset();
|
|
|
|
|
|
}
|
|
|
|
|
|
children_.clear();
|
|
|
|
|
|
nameIndex_.clear();
|
|
|
|
|
|
tagIndex_.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Ptr<Node> Node::getChildByName(const std::string &name) const {
|
|
|
|
|
|
// 使用哈希索引,O(1) 查找
|
|
|
|
|
|
auto it = nameIndex_.find(name);
|
|
|
|
|
|
if (it != nameIndex_.end()) {
|
|
|
|
|
|
return it->second.lock();
|
|
|
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Ptr<Node> Node::getChildByTag(int tag) const {
|
|
|
|
|
|
// 使用哈希索引,O(1) 查找
|
|
|
|
|
|
auto it = tagIndex_.find(tag);
|
|
|
|
|
|
if (it != tagIndex_.end()) {
|
|
|
|
|
|
return it->second.lock();
|
|
|
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setPosition(const Vec2 &pos) {
|
|
|
|
|
|
position_ = pos;
|
|
|
|
|
|
markTransformDirty();
|
|
|
|
|
|
updateSpatialIndex();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setPosition(float x, float y) { setPosition(Vec2(x, y)); }
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setRotation(float degrees) {
|
|
|
|
|
|
rotation_ = degrees;
|
|
|
|
|
|
markTransformDirty();
|
|
|
|
|
|
updateSpatialIndex();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setScale(const Vec2 &scale) {
|
|
|
|
|
|
scale_ = scale;
|
|
|
|
|
|
markTransformDirty();
|
|
|
|
|
|
updateSpatialIndex();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setScale(float scale) { setScale(Vec2(scale, scale)); }
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setScale(float x, float y) { setScale(Vec2(x, y)); }
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setAnchor(const Vec2 &anchor) {
|
|
|
|
|
|
anchor_ = anchor;
|
|
|
|
|
|
markTransformDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setAnchor(float x, float y) { setAnchor(Vec2(x, y)); }
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setSkew(const Vec2 &skew) {
|
|
|
|
|
|
skew_ = skew;
|
|
|
|
|
|
markTransformDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setSkew(float x, float y) { setSkew(Vec2(x, y)); }
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setOpacity(float opacity) {
|
|
|
|
|
|
opacity_ = std::clamp(opacity, 0.0f, 1.0f);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setVisible(bool visible) { visible_ = visible; }
|
|
|
|
|
|
|
2026-02-13 18:46:42 +08:00
|
|
|
|
void Node::setColor(const Color3B& color) { color_ = color; }
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setFlipX(bool flipX) { flipX_ = flipX; }
|
|
|
|
|
|
|
|
|
|
|
|
void Node::setFlipY(bool flipY) { flipY_ = flipY; }
|
|
|
|
|
|
|
2026-02-11 19:40:26 +08:00
|
|
|
|
void Node::setZOrder(int zOrder) {
|
|
|
|
|
|
if (zOrder_ != zOrder) {
|
|
|
|
|
|
zOrder_ = zOrder;
|
|
|
|
|
|
childrenOrderDirty_ = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Vec2 Node::convertToWorldSpace(const Vec2 &localPos) const {
|
|
|
|
|
|
glm::vec4 worldPos =
|
|
|
|
|
|
getWorldTransform() * glm::vec4(localPos.x, localPos.y, 0.0f, 1.0f);
|
|
|
|
|
|
return Vec2(worldPos.x, worldPos.y);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Vec2 Node::convertToNodeSpace(const Vec2 &worldPos) const {
|
|
|
|
|
|
glm::mat4 invWorld = glm::inverse(getWorldTransform());
|
|
|
|
|
|
glm::vec4 localPos = invWorld * glm::vec4(worldPos.x, worldPos.y, 0.0f, 1.0f);
|
|
|
|
|
|
return Vec2(localPos.x, localPos.y);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
glm::mat4 Node::getLocalTransform() const {
|
|
|
|
|
|
if (transformDirty_) {
|
|
|
|
|
|
localTransform_ = glm::mat4(1.0f);
|
|
|
|
|
|
|
|
|
|
|
|
// T - R - S order
|
|
|
|
|
|
localTransform_ = glm::translate(localTransform_,
|
|
|
|
|
|
glm::vec3(position_.x, position_.y, 0.0f));
|
|
|
|
|
|
|
|
|
|
|
|
if (rotation_ != 0.0f) {
|
|
|
|
|
|
localTransform_ = glm::rotate(localTransform_, rotation_ * DEG_TO_RAD,
|
|
|
|
|
|
glm::vec3(0.0f, 0.0f, 1.0f));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (skew_.x != 0.0f || skew_.y != 0.0f) {
|
|
|
|
|
|
glm::mat4 skewMatrix(1.0f);
|
|
|
|
|
|
skewMatrix[1][0] = std::tan(skew_.x * DEG_TO_RAD);
|
|
|
|
|
|
skewMatrix[0][1] = std::tan(skew_.y * DEG_TO_RAD);
|
|
|
|
|
|
localTransform_ *= skewMatrix;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
localTransform_ =
|
|
|
|
|
|
glm::scale(localTransform_, glm::vec3(scale_.x, scale_.y, 1.0f));
|
|
|
|
|
|
|
2026-02-12 21:50:21 +08:00
|
|
|
|
// 注意:锚点偏移在渲染时处理,不在本地变换中处理
|
|
|
|
|
|
// 这样可以避免锚点偏移被父节点的缩放影响
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
|
|
|
|
|
transformDirty_ = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return localTransform_;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
glm::mat4 Node::getWorldTransform() const {
|
|
|
|
|
|
if (worldTransformDirty_) {
|
2026-02-13 10:00:39 +08:00
|
|
|
|
// 使用线程局部存储的固定数组,避免每帧内存分配
|
|
|
|
|
|
// 限制最大深度为 256 层,足以覆盖绝大多数场景
|
|
|
|
|
|
thread_local std::array<const Node*, 256> nodeChainCache;
|
|
|
|
|
|
thread_local size_t chainCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
chainCount = 0;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
const Node *current = this;
|
2026-02-13 10:00:39 +08:00
|
|
|
|
while (current && chainCount < nodeChainCache.size()) {
|
|
|
|
|
|
nodeChainCache[chainCount++] = current;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
auto p = current->parent_.lock();
|
|
|
|
|
|
current = p.get();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从根节点开始计算
|
|
|
|
|
|
glm::mat4 transform = glm::mat4(1.0f);
|
2026-02-13 10:00:39 +08:00
|
|
|
|
for (size_t i = chainCount; i > 0; --i) {
|
|
|
|
|
|
transform = transform * nodeChainCache[i - 1]->getLocalTransform();
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
worldTransform_ = transform;
|
|
|
|
|
|
worldTransformDirty_ = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return worldTransform_;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::markTransformDirty() {
|
|
|
|
|
|
// 避免重复标记,提高性能
|
|
|
|
|
|
if (!transformDirty_ || !worldTransformDirty_) {
|
|
|
|
|
|
transformDirty_ = true;
|
|
|
|
|
|
worldTransformDirty_ = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 递归标记所有子节点
|
|
|
|
|
|
for (auto &child : children_) {
|
|
|
|
|
|
child->markTransformDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-11 22:30:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::batchUpdateTransforms() {
|
|
|
|
|
|
// 如果本地变换脏了,先计算本地变换
|
|
|
|
|
|
if (transformDirty_) {
|
|
|
|
|
|
(void)getLocalTransform(); // 这会计算并缓存本地变换
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果世界变换脏了,需要重新计算
|
|
|
|
|
|
if (worldTransformDirty_) {
|
|
|
|
|
|
auto parent = parent_.lock();
|
|
|
|
|
|
if (parent) {
|
|
|
|
|
|
// 使用父节点的世界变换(确保父节点已经更新)
|
|
|
|
|
|
worldTransform_ = parent->getWorldTransform() * localTransform_;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 根节点
|
|
|
|
|
|
worldTransform_ = localTransform_;
|
|
|
|
|
|
}
|
|
|
|
|
|
worldTransformDirty_ = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 递归更新子节点
|
|
|
|
|
|
for (auto &child : children_) {
|
|
|
|
|
|
child->batchUpdateTransforms();
|
|
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::onEnter() {
|
|
|
|
|
|
running_ = true;
|
|
|
|
|
|
for (auto &child : children_) {
|
|
|
|
|
|
child->onEnter();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::onExit() {
|
|
|
|
|
|
running_ = false;
|
|
|
|
|
|
for (auto &child : children_) {
|
|
|
|
|
|
child->onExit();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::onUpdate(float dt) {
|
2026-02-25 21:22:35 +08:00
|
|
|
|
updateTweens(dt);
|
2026-02-11 19:40:26 +08:00
|
|
|
|
onUpdateNode(dt);
|
|
|
|
|
|
|
|
|
|
|
|
// Update children
|
|
|
|
|
|
for (auto &child : children_) {
|
|
|
|
|
|
child->onUpdate(dt);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::onRender(RenderBackend &renderer) {
|
|
|
|
|
|
if (!visible_)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
onDraw(renderer);
|
|
|
|
|
|
|
|
|
|
|
|
for (auto &child : children_) {
|
|
|
|
|
|
child->onRender(renderer);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::onAttachToScene(Scene *scene) {
|
|
|
|
|
|
scene_ = scene;
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到场景的空间索引
|
|
|
|
|
|
if (spatialIndexed_ && scene_) {
|
|
|
|
|
|
lastSpatialBounds_ = Rect();
|
|
|
|
|
|
updateSpatialIndex();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (auto &child : children_) {
|
|
|
|
|
|
child->onAttachToScene(scene);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::onDetachFromScene() {
|
|
|
|
|
|
// 从场景的空间索引移除
|
|
|
|
|
|
// 注意:即使 lastSpatialBounds_ 为空也要尝试移除,
|
|
|
|
|
|
// 因为节点可能通过其他方式被插入到空间索引中
|
|
|
|
|
|
if (spatialIndexed_ && scene_) {
|
|
|
|
|
|
scene_->removeNodeFromSpatialIndex(this);
|
|
|
|
|
|
lastSpatialBounds_ = Rect();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
scene_ = nullptr;
|
|
|
|
|
|
for (auto &child : children_) {
|
|
|
|
|
|
child->onDetachFromScene();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Rect Node::getBoundingBox() const {
|
|
|
|
|
|
// 默认返回一个以位置为中心的点矩形
|
|
|
|
|
|
return Rect(position_.x, position_.y, 0, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::updateSpatialIndex() {
|
|
|
|
|
|
if (!spatialIndexed_ || !scene_) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Rect newBounds = getBoundingBox();
|
|
|
|
|
|
if (newBounds != lastSpatialBounds_) {
|
|
|
|
|
|
scene_->updateNodeInSpatialIndex(this, lastSpatialBounds_, newBounds);
|
|
|
|
|
|
lastSpatialBounds_ = newBounds;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::update(float dt) { onUpdate(dt); }
|
|
|
|
|
|
|
|
|
|
|
|
void Node::render(RenderBackend &renderer) {
|
|
|
|
|
|
if (childrenOrderDirty_) {
|
|
|
|
|
|
sortChildren();
|
|
|
|
|
|
}
|
|
|
|
|
|
onRender(renderer);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::sortChildren() {
|
|
|
|
|
|
// 使用插入排序优化小范围更新场景
|
|
|
|
|
|
// 插入排序在大部分已有序的情况下性能接近O(n)
|
|
|
|
|
|
size_t n = children_.size();
|
|
|
|
|
|
if (n <= 1) {
|
|
|
|
|
|
childrenOrderDirty_ = false;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 小数组使用插入排序,大数组使用std::sort
|
|
|
|
|
|
if (n < 32) {
|
|
|
|
|
|
// 插入排序
|
|
|
|
|
|
for (size_t i = 1; i < n; ++i) {
|
|
|
|
|
|
auto key = children_[i];
|
|
|
|
|
|
int keyZOrder = key->getZOrder();
|
|
|
|
|
|
int j = static_cast<int>(i) - 1;
|
|
|
|
|
|
|
|
|
|
|
|
while (j >= 0 && children_[j]->getZOrder() > keyZOrder) {
|
|
|
|
|
|
children_[j + 1] = children_[j];
|
|
|
|
|
|
--j;
|
|
|
|
|
|
}
|
|
|
|
|
|
children_[j + 1] = key;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 大数组使用标准排序
|
|
|
|
|
|
std::sort(children_.begin(), children_.end(),
|
|
|
|
|
|
[](const Ptr<Node> &a, const Ptr<Node> &b) {
|
|
|
|
|
|
return a->getZOrder() < b->getZOrder();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
childrenOrderDirty_ = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::collectRenderCommands(std::vector<RenderCommand> &commands,
|
|
|
|
|
|
int parentZOrder) {
|
|
|
|
|
|
if (!visible_)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算累积 Z 序
|
|
|
|
|
|
int accumulatedZOrder = parentZOrder + zOrder_;
|
|
|
|
|
|
|
|
|
|
|
|
// 生成当前节点的渲染命令
|
|
|
|
|
|
generateRenderCommand(commands, accumulatedZOrder);
|
|
|
|
|
|
|
|
|
|
|
|
// 递归收集子节点的渲染命令
|
|
|
|
|
|
// 注意:这里假设子节点已经按 Z 序排序
|
|
|
|
|
|
for (auto &child : children_) {
|
|
|
|
|
|
child->collectRenderCommands(commands, accumulatedZOrder);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-25 21:22:35 +08:00
|
|
|
|
Tween& Node::tween() {
|
|
|
|
|
|
auto tw = std::make_shared<Tween>(this);
|
|
|
|
|
|
tweens_.push_back(tw);
|
|
|
|
|
|
return *tweens_.back();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Tween& Node::tween(const std::string &name) {
|
|
|
|
|
|
auto tw = std::make_shared<Tween>(this, name);
|
|
|
|
|
|
tweens_.push_back(tw);
|
|
|
|
|
|
return *tweens_.back();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Tween &Node::moveTo(const Vec2 &pos, float duration, TweenEasing easing) {
|
|
|
|
|
|
TweenProperty props;
|
|
|
|
|
|
props.position = pos;
|
|
|
|
|
|
TweenOptions options;
|
|
|
|
|
|
options.easing = easing;
|
|
|
|
|
|
auto tw = std::make_shared<Tween>(this);
|
|
|
|
|
|
tw->to(duration, props, options);
|
|
|
|
|
|
tw->start();
|
|
|
|
|
|
tweens_.push_back(tw);
|
|
|
|
|
|
return *tweens_.back();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Tween &Node::moveBy(const Vec2 &delta, float duration, TweenEasing easing) {
|
|
|
|
|
|
TweenProperty props;
|
|
|
|
|
|
props.position = delta;
|
|
|
|
|
|
TweenOptions options;
|
|
|
|
|
|
options.easing = easing;
|
|
|
|
|
|
auto tw = std::make_shared<Tween>(this);
|
|
|
|
|
|
tw->by(duration, props, options);
|
|
|
|
|
|
tw->start();
|
|
|
|
|
|
tweens_.push_back(tw);
|
|
|
|
|
|
return *tweens_.back();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Tween &Node::scaleTo(float scale, float duration, TweenEasing easing) {
|
|
|
|
|
|
TweenProperty props;
|
|
|
|
|
|
props.scale = Vec2(scale, scale);
|
|
|
|
|
|
TweenOptions options;
|
|
|
|
|
|
options.easing = easing;
|
|
|
|
|
|
auto tw = std::make_shared<Tween>(this);
|
|
|
|
|
|
tw->to(duration, props, options);
|
|
|
|
|
|
tw->start();
|
|
|
|
|
|
tweens_.push_back(tw);
|
|
|
|
|
|
return *tweens_.back();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Tween &Node::scaleBy(float delta, float duration, TweenEasing easing) {
|
|
|
|
|
|
TweenProperty props;
|
|
|
|
|
|
props.scale = Vec2(delta, delta);
|
|
|
|
|
|
TweenOptions options;
|
|
|
|
|
|
options.easing = easing;
|
|
|
|
|
|
auto tw = std::make_shared<Tween>(this);
|
|
|
|
|
|
tw->by(duration, props, options);
|
|
|
|
|
|
tw->start();
|
|
|
|
|
|
tweens_.push_back(tw);
|
|
|
|
|
|
return *tweens_.back();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Tween &Node::rotateTo(float degrees, float duration, TweenEasing easing) {
|
|
|
|
|
|
TweenProperty props;
|
|
|
|
|
|
props.rotation = degrees;
|
|
|
|
|
|
TweenOptions options;
|
|
|
|
|
|
options.easing = easing;
|
|
|
|
|
|
auto tw = std::make_shared<Tween>(this);
|
|
|
|
|
|
tw->to(duration, props, options);
|
|
|
|
|
|
tw->start();
|
|
|
|
|
|
tweens_.push_back(tw);
|
|
|
|
|
|
return *tweens_.back();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Tween &Node::rotateBy(float degrees, float duration, TweenEasing easing) {
|
|
|
|
|
|
TweenProperty props;
|
|
|
|
|
|
props.rotation = degrees;
|
|
|
|
|
|
TweenOptions options;
|
|
|
|
|
|
options.easing = easing;
|
|
|
|
|
|
auto tw = std::make_shared<Tween>(this);
|
|
|
|
|
|
tw->by(duration, props, options);
|
|
|
|
|
|
tw->start();
|
|
|
|
|
|
tweens_.push_back(tw);
|
|
|
|
|
|
return *tweens_.back();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Tween &Node::fadeIn(float duration, TweenEasing easing) {
|
|
|
|
|
|
TweenProperty props;
|
|
|
|
|
|
props.opacity = 1.0f;
|
|
|
|
|
|
TweenOptions options;
|
|
|
|
|
|
options.easing = easing;
|
|
|
|
|
|
auto tw = std::make_shared<Tween>(this);
|
|
|
|
|
|
tw->to(duration, props, options);
|
|
|
|
|
|
tw->start();
|
|
|
|
|
|
tweens_.push_back(tw);
|
|
|
|
|
|
return *tweens_.back();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Tween &Node::fadeOut(float duration, TweenEasing easing) {
|
|
|
|
|
|
TweenProperty props;
|
|
|
|
|
|
props.opacity = 0.0f;
|
|
|
|
|
|
TweenOptions options;
|
|
|
|
|
|
options.easing = easing;
|
|
|
|
|
|
auto tw = std::make_shared<Tween>(this);
|
|
|
|
|
|
tw->to(duration, props, options);
|
|
|
|
|
|
tw->start();
|
|
|
|
|
|
tweens_.push_back(tw);
|
|
|
|
|
|
return *tweens_.back();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Tween &Node::fadeTo(float opacity, float duration, TweenEasing easing) {
|
|
|
|
|
|
TweenProperty props;
|
|
|
|
|
|
props.opacity = opacity;
|
|
|
|
|
|
TweenOptions options;
|
|
|
|
|
|
options.easing = easing;
|
|
|
|
|
|
auto tw = std::make_shared<Tween>(this);
|
|
|
|
|
|
tw->to(duration, props, options);
|
|
|
|
|
|
tw->start();
|
|
|
|
|
|
tweens_.push_back(tw);
|
|
|
|
|
|
return *tweens_.back();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::stopAllTweens() {
|
|
|
|
|
|
for (auto &tw : tweens_) {
|
|
|
|
|
|
tw->stop();
|
|
|
|
|
|
}
|
|
|
|
|
|
tweens_.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Node::updateTweens(float dt) {
|
|
|
|
|
|
for (auto it = tweens_.begin(); it != tweens_.end();) {
|
|
|
|
|
|
(*it)->update(dt);
|
|
|
|
|
|
if ((*it)->isFinished()) {
|
|
|
|
|
|
it = tweens_.erase(it);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
++it;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-11 19:40:26 +08:00
|
|
|
|
} // namespace extra2d
|