Redream 编辑器资源使用说明

一、文件后缀与编译关系

1.1 编辑器源文件(ccb/ 目录)

后缀 格式 说明
.red XML (plist) Redream 编辑器的场景/UI 源文件,包含节点树结构和时间线定义
.rebolt JSON Redream 编辑器的行为树配置源文件,定义交互逻辑流程
.anim XML (plist) Redream 编辑器的动画曲线源文件,定义单条可复用的路径动画

1.2 编译后运行时文件(_ccbi/ 目录)

后缀 格式 说明
.redream Protobuf 二进制 .red(+ 可选的 .rebolt)编译合并而来,是代码中实际加载的文件
.redanim Protobuf 二进制 .anim 编译而来,是动画曲线的运行时文件
.xml XML 行为树的运行时文件(Behaviac 格式),由 .rebolt 中的行为树定义编译而来
.batchredream Protobuf 二进制 纹理合批优化版本的 .redream,加载嵌套子文件时自动回退查找

1.3 编译关系

编辑器源文件 (ccb/)              →  编译  →  运行时文件 (_ccbi/)
───────────────────────────────────────────────────────────────
xxx.red                          →           xxx.redream
xxx.red + xxx.rebolt             →           xxx.redream + 若干 .xml
xxx.anim                         →           xxx.redanim

构建打包时只有 _ccbi/ 目录下的文件会进入 app。代码中加载时写 .redream 后缀(或不写后缀,REDReader 会自动补 .redream)。

1.4 不带行为树 vs 带行为树


二、加载方式

2.1 QCoreLayer::Layer(最常用,开箱即用)

auto* layer = redutils::QCoreLayer::Layer("xxx.redream");
parentNode->addChild(layer);

加载 .redream 文件,自动把内部子节点按变量名存入字典,自动创建 QCoreLayerDelegate 挂到 REDAnimationManager 上接收动画完成回调。内置 setTimelineCallback 机制,可以直接用 lambda 注册时间线关键帧回调,不需要自己实现任何接口。

2.2 RedreamLoader::Layer(轻量加载)

auto* loader = RedreamLoader::Layer("xxx.redream");
parentNode->addChild(loader);

加载 .redream 文件,自动存子节点到字典。不会自动创建 delegate,onResolveREDCCCallFuncSelector 默认返回 nullptr(不处理时间线回调)。比 QCoreLayer 更轻量,适合只需要静态渲染 + 简单 playAnim 的场景。

2.3 RedreamLoader::ReboltLayer(行为树加载)

auto* loader = RedreamLoader::ReboltLayer("xxx.redream");
parentNode->addChild(loader);

Layer 的基础上,额外从 REDReader 中取出 ReboltRedManager,调用 retain() 持有它,并把自己设为 NotifyDevelopmentDelegate。这样 .redream 文件中配置的行为树才能运行,行为树中的"通知工程师"节点才能触发回调。如果 .redream 文件里没有配置行为树,和 Layer 没有区别。

2.4 RUReboltLayer::createReboltLayer(Rebolt 封装层)

auto* layer = redutils::RUReboltLayer::createReboltLayer("xxx.redream");
parentNode->addChild(layer);
layer->runBehaviacWhitFunName("初始化");

更高层的 Rebolt 封装,内部也是通过 REDReader 加载,自动设置 NotifyDevelopmentDelegate,并提供 registerOnNotify 等便捷接口。

2.5 自定义 NodeLoaderLibrary

auto* lib = redream::NodeLoaderLibrary::newDefaultNodeLoaderLibrary();
lib->registerNodeLoader("RedreamLoader", RedreamLoaderLoader::loader());
auto* loader = RedreamLoader::LayerWithLib("xxx.redream", lib);

.redream 文件中使用了自定义节点类型时,需要注册对应的 NodeLoader。

2.6 加载后缀规则

// 写 .redream 后缀(推荐)
auto* layer = RedreamLoader::Layer("xxx.redream");

// 不写后缀,REDReader 自动补 .redream
auto* layer = RedreamLoader::Layer("xxx");

// 两者等价

REDReader::readNodeGraphFromFile 的逻辑:如果传入的文件名没有后缀(找不到 .),自动追加 .redream;如果有后缀则原样使用。


三、时间线(REDSequence)

3.1 概念

时间线是 .redream 文件的核心概念。一个 .redream 文件可以包含多条命名时间线,每条时间线(REDSequence)包含:

3.2 链式播放

在 Redream 编辑器中可以设置一条时间线播完后自动接着播另一条。代码中体现在 sequenceCompleted 里:

int nextSeqId = sequence->getChainedSequenceId();
if (nextSeqId != -1) {
    // 当前时间线完成,自动播放下一条
    delegate->completedAnimationAndPlayNextSequenceNamed(...);
    delegate->completedAnimationSequenceNamed(...);
    runAnimationsForSequenceIdTweenDuration(nextSeqId, 0, callback);
} else {
    // 真正播完了,没有后续
    delegate->completedAnimationSequenceNamed(...);
}

3.3 播放

// RedreamLoader / QCoreLayer 的简化接口
float duration = loader->playAnim("常态");       // 播放并返回时长
float time = loader->getAnimTime("动画_解锁");   // 仅获取时长

// QCoreLayer 支持播完回调
coreLayer->playAnim("动画名", [](float dt) {
    // 动画播完后执行
});

// 播放后自动从父节点移除自己
coreLayer->playAnimAndRemoveSelf("out");
// 内部实现:播放时间线,注册完成回调,回调中调用 this->removeFromParent()

// 检查时间线是否存在
bool valid = coreLayer->isValidAnim("某条时间线");

3.4 底层 API(REDAnimationManager)

// 从节点获取动画管理器
auto* animMgr = REDAnimationManager::fromNode(node);
// 或者
auto* animMgr = dynamic_cast<REDAnimationManager*>(node->getUserObject());

// 按名称播放
animMgr->runAnimationsForSequenceNamed("timeline_name");

// 带过渡时间和完成回调
animMgr->runAnimationsForSequenceNamedTweenDuration("name", 0.3f,
    [](Node* n, AnimationCompleteType type) {
        // type 取值:
        // CHAINED   — 播完但会继续播下一条链式时间线
        // COMPLETED — 完全播放完毕
        // STOPED    — 被 stopAllNodeAction() 停止
    });

// 按 ID 播放
int seqId = animMgr->getSequenceId("timeline_name");
animMgr->runAnimationsForSequenceIdTweenDuration(seqId, 0);

// 获取时间线时长
float dur = animMgr->getSequenceDuration("timeline_name");

// 刷新到首帧状态(不播放动画,只设置属性到第一帧的值)
animMgr->refreshStatusOnFirstFream(seqId, 0);

// 动画速度控制
animMgr->setAnimationSpeed(2.0f);   // 2 倍速
coreLayer->setAnimationSpeed(0.5f); // 半速

四、停止与重新开始

4.1 停止

// RedreamLoader 方式 — 停止该节点上的所有 cocos2d Action
loader->stopAnimAll();  // 内部调用 this->stopAllActions()

// REDAnimationManager 方式 — 停止所有节点动画并触发回调
animMgr->stopAllNodeAction();

stopAllNodeAction() 会: 1. 停止所有节点上的 Action 2. 触发 delegate 的 stopAnimationSequenceNamed 回调

回调触发的优先级: - 优先级 1:尝试把 _rootNode 强转为 REDAnimationManagerDelegate,如果成功则调用它 - 优先级 2:使用通过 setDelegate() 设置的 _delegateQCoreLayer 加载时自动设置的 QCoreLayerDelegate) - 优先级 3:使用通过 runAnimationsWithListen() 传入的一次性 _listen(调用后自动置空)

4.2 重新开始

没有专门的"重新开始"接口。直接再次调用 playAnim 即可,它会停止当前正在播放的时间线并开始新的:

loader->playAnim("常态");  // 重新播放

五、静态渲染与动态渲染

5.1 静态渲染

加载 .redream 文件后,节点树已经构建完毕,可以直接作为静态 UI 使用,不需要播放任何时间线:

auto* loader = RedreamLoader::Layer("SL_棋盘模块_基本元素_棋盘格.redream");
parentNode->addChild(loader);

// 获取内部子节点进行静态操作
Sprite* bg = loader->getSprite("棋盘格");
bg->setOpacity(128);

5.2 动态渲染(Rebolt 行为树系统)

Rebolt 是 Redream 的行为树驱动系统,用于动态交互逻辑。行为树在 Redream 编辑器中配置,运行时由 ReboltRedManager 驱动。

// 加载带 Rebolt 的文件
auto* loader = RedreamLoader::ReboltLayer("xxx.redream");
parentNode->addChild(loader);

// 获取 Rebolt 管理器
auto* reboltMgr = loader->getReboltManager();

Rebolt 行为树内部可以执行的操作(这些是行为树节点自动调用的,不需要程序员手动调用):

5.3 Rebolt 的"通知工程师"机制

行为树中有一种节点叫"通知工程师"(Notify Development),用于行为树和 C++ 代码之间的通信:

  1. 行为树执行到"通知工程师"节点
  2. 创建 NotifyDevelopmentWaiter,调用 delegate->onNotifyDevelopment(mgr, waiter, notify, param, isWait, outNode)
  3. 程序员在回调中根据 notify(通知名称)和 param(参数)执行自定义逻辑
  4. 如果 isWait == true,行为树暂停等待,程序员完成后调用 waiter->notifyDevelopmentEnd() 让行为树继续
  5. 如果 isWait == false,行为树不等待,直接继续执行

RedreamLoader 已经实现了 NotifyDevelopmentDelegate 接口并暴露了 lambda:

auto* loader = RedreamLoader::ReboltLayer("xxx.redream");
loader->_reboltCallBack = [](ReboltRedManager* mgr, NotifyDevelopmentWaiter* waiter,
                              std::string notify, std::string param, bool isWait, Node* outNode) {
    if (notify == "doSomething") {
        // 执行自定义逻辑
    }
    if (isWait) {
        waiter->notifyDevelopmentEnd();  // 通知行为树继续
    }
};

六、显示与隐藏

// 直接操作 cocos2d Node 的可见性
loader->setVisible(true);
loader->setVisible(false);

// 操作内部子节点
Node* child = loader->getNode("某个节点");
child->setVisible(false);

// 透明度控制
Sprite* sprite = loader->getSprite("棋盘格");
sprite->setOpacity(0);     // 完全透明
sprite->setOpacity(255);   // 完全不透明

setVisible(false) 只是不渲染,节点仍然在节点树中,不会被释放。


七、位置与坐标转换

7.1 获取内部定位点

// 通过变量名获取 redream 文件中的定位点节点
Node* locator = loader->getNode("元素定位点");

7.2 坐标转换(cocos2d 标准方式)

// 局部坐标 → 世界坐标
Vec2 worldPos = locator->convertToWorldSpace(Vec2::ZERO);

// 世界坐标 → 另一个节点的局部坐标
Vec2 localPos = targetParent->convertToNodeSpace(worldPos);

7.3 Rebolt 中获取全局坐标

行为树内部可以获取节点的全局坐标(这些是行为树节点自动调用的):

7.4 redream 内部的相对定位

// redream 库提供的绝对位置计算工具
Vec2 absPos = redream::getAbsolutePosition(pt, posType, containerSize, propName);

八、获取子节点

8.1 按类型获取

RedreamLoaderQCoreLayer 都提供按变量名获取子节点的方法。变量名在 Redream 编辑器中设置。

方法 返回类型 说明
getNode("name") Node* 最基础的节点,只有位置、缩放、旋转、可见性等几何属性。用于定位点、容器等
getSprite("name") Sprite* 精灵,显示一张图片。有 setTexturesetSpriteFramesetOpacity
getLabel("name") Label* cocos2d 原生文本节点,支持 TTF/BMFont,有 setStringsetColor
getRedLabel("name") RedLabel* Redream 自定义的双层文本节点(见下方说明)
getParticle("name") ParticleSystemQuad* 粒子系统节点
getREDNodeButton("name") REDNodeButton* Redream 自定义按钮(见下方说明)
getSubLoader("name") RedreamLoader* 嵌套的子 red 文件节点

QCoreLayer 额外提供:

方法 返回类型 说明
getCoreLayer("name") QCoreLayer* QCoreLayer 版本的嵌套子 red 文件
getCoreBtn("name") QCoreBtn* QCoreLayer 体系下的按钮封装
getCustomeProperty("name") Value 获取 Redream 编辑器中设置的自定义属性值

8.2 RedLabel(双层文本节点)

RedLabel 继承自 Node,内部包含两个 Label:一个前景、一个背景,用于实现描边/阴影效果。

RedLabel* label = loader->getRedLabel("标题");
label->setString("Hello");                    // 同时设置前景和背景文本
label->setFrontColor(Color3B::WHITE);         // 前景色
label->setBackColor(Color3B::BLACK);          // 背景色(描边/阴影)
label->setFrontOpacity(255);                  // 前景透明度
label->setBackOpacity(128);                   // 背景透明度
label->setFrontBMFontFilePath("font.fnt");    // 前景字体
label->setBackBMFontFilePath("font_bg.fnt");  // 背景字体
label->setDimensions(200, 50);                // 文本框尺寸
label->setHorizontalAlignment(TextHAlignment::CENTER);  // 水平对齐
label->setVerticalAlignment(TextVAlignment::CENTER);    // 垂直对齐
label->enableWrap(true);                      // 自动换行
label->setLineSpacing(5.0f);                  // 行间距

8.3 REDNodeButton(Redream 按钮)

REDNodeButton 继承自 cocos2d::extension::Control,支持触摸事件和按下缩放效果。

REDNodeButton* btn = loader->getREDNodeButton("按钮");
btn->setZoomOnTouchDown(true);    // 按下时缩放
btn->setScaleRatio(1.1f);        // 按下缩放比例
btn->setSwallowsTouches(true);   // 吞噬触摸事件
btn->setEnabled(true);           // 启用/禁用
// 触摸移动取消(如在 TableView 中的按钮,滑动时取消点击)
btn->setTouchMovedCanceled(true, 10.0f);

九、自定义函数/回调

项目中有两套不同的"自定义函数"机制。

9.1 机制 A:时间线关键帧回调

在 Redream 编辑器中,可以在时间线的某个时间点插入一个"回调"关键帧,指定一个函数名。播放到那个时间点时,引擎会查找并调用对应的 C++ 函数。

QCoreLayer 已经封装好了这个机制,提供直接可用的 API:

auto* layer = redutils::QCoreLayer::Layer("xxx.redream");

// 直接用 lambda 注册时间线关键帧回调
layer->setTimelineCallback("myCallback", []() {
    // 时间线播到这个关键帧时执行
});

layer->playAnim("某条时间线");

内部原理:QCoreLayeronResolveREDCCCallFuncSelector 中注册了 EventListenerCustom,监听 "__CCCallFuncSelector__" + selectorName 事件。当时间线播到回调帧时 dispatch 该事件,然后从 _timelineCallbacks map 中找到对应的 lambda 执行。

9.2 机制 B:动画完成回调

监听时间线播放完成事件:

// QCoreLayer 方式 — 直接用 lambda
coreLayer->registerCompletedAniCallBack([](string timeLineName) {
    // timeLineName 是播完的时间线名称
    if (timeLineName == "动画_解锁") {
        // 处理完成逻辑
    }
});

// QCoreLayer 的 playAnim 也支持完成回调
coreLayer->playAnim("动画名", [](float dt) {
    // 播完后执行(通过 scheduleOnce 实现)
});

9.3 机制 C:Rebolt 通知工程师

行为树系统的回调机制,见第五章"Rebolt 的通知工程师机制"。

与机制 A 的区别: - 机制 A 是"时间线播到某个时间点 → 触发回调",用于动画过程中的事件 - 机制 C 是"行为树执行到某个逻辑节点 → 通知程序",用于交互逻辑流程中需要程序介入的地方


十、成员变量绑定

10.1 概念

成员变量绑定是 Redream 编辑器和 C++ 代码之间的节点引用机制。在 Redream 编辑器中,可以给任意节点设置一个"变量名"。加载 .redream 文件时,引擎会对每个有变量名的节点调用 onAssignREDMemberVariable(target, name, node),把节点指针传给 C++ 代码。

10.2 已有实现

RedreamLoaderQCoreLayer 都已经实现了这个接口,把所有带变量名的节点存入 _mapChilden 字典:

// RedreamLoader 的实现
bool RedreamLoader::onAssignREDMemberVariable(Ref* pTarget, const char* name, Node* node) {
    if (pTarget == this && node != this && strcmp(name, "") != 0) {
        _mapChilden.insert(pair<string, Node*>(string(name), node));
        return true;
    }
    return false;
}

10.3 意义

这就是为什么可以通过 loader->getNode("元素定位点") 按名称获取节点——这些名称就是编辑器中设置的变量名,在加载时通过成员变量绑定机制存入了字典。

好处:让程序可以按名称引用编辑器中的任意节点,而不需要通过遍历子节点树或硬编码层级关系来查找。编辑器中调整了节点层级结构,只要变量名不变,代码就不需要改。


十一、ANIM 文件(.redanim)

11.1 概念

.anim(编辑器源文件)/ .redanim(编译后运行时文件)是从 .red 文件中提取出来的单条动画曲线,用于在代码中动态应用到任意节点上(比如飞行路径动画)。

.redream 的区别:.redream 包含完整的节点树 + 多条时间线;.redanim 只包含一个节点、一条时间线的动画曲线数据,不包含节点树。

11.2 加载与使用

// 从 .redanim 文件创建(传入文件名,内部自动补 .redanim 后缀)
auto* anim = redream::RedreamAnim::createFromAnimFile("path/to/anim");

// 获取动画时长
float duration = anim->getDuration();

// 设置起止位置(覆盖文件中的默认值,用于路径动画)
anim->setStartPosition(Vec2(100, 200));
anim->setEndPosition(Vec2(500, 300));

// 生成 cocos2d Action(autorelease)
// rotateWith: 是否沿路径方向旋转
Action* action = anim->getAction(true);

// 应用到任意节点
myNode->runAction(action);

11.3 Rebolt 中的用法

行为树中可以通过"播放动画文件"节点使用 .redanim

// ReboltRedManager 内部实现
void ReboltRedManager::runAnimFile(Node* node, std::string animPath,
    Vec2 startPos, Vec2 endPos, bool rotateWith,
    const std::function<void()>& func, ReboltErrorInfo& errorInfo) {
    Node* par = node->getParent();
    Vec2 localStartPos = par->convertToNodeSpace(startPos);
    Vec2 localEndPos = par->convertToNodeSpace(endPos);

    auto anim = redream::RedreamAnim::createFromAnimFile(animPath);
    anim->setStartPosition(localStartPos);
    anim->setEndPosition(localEndPos);
    auto seq = Sequence::create(anim->getAction(rotateWith), CallFunc::create(func), NULL);
    node->runAction(seq);
}

11.4 支持的动画属性

.redanim 文件支持以下属性的关键帧动画: - position — 位置(支持贝塞尔曲线路径) - scale — 缩放 - rotation — 旋转 - opacity — 透明度 - skew — 倾斜 - displayFrame — 纹理帧动画(在 .redanim 中会被忽略,无意义)

11.5 缓动类型(Easing)

关键帧支持丰富的缓动曲线:

类别 类型
基础 LINEARINSTANT
三次 CUBIC_INCUBIC_OUTCUBIC_INOUT
弹性 ELASTIC_INELASTIC_OUTELASTIC_INOUT
弹跳 BOUNCE_INBOUNCE_OUTBOUNCE_INOUT
回弹 BACK_INBACK_OUTBACK_INOUT
正弦 SineInSineOutSineInOut
指数 ExponentialInExponentialOutExponentialInOut
圆形 CircleActionInCircleActionOutCircleActionInOut
2 次方 QuadraticActionInQuadraticActionOutQuadraticActionInOut
3 次方 CubicActionInCubicActionOutCubicActionInOut
4 次方 QuarticActionInQuarticActionOutQuarticActionInOut
5 次方 QuinticActionInQuinticActionOutQuinticActionInOut
自定义 Custom

十二、cocos2d-x 基础概念

12.1 父子节点(parent / child)

cocos2d 的场景是一棵节点树。每个 Node 可以有一个 parent 和多个 children。

parent->addChild(child);         // child 加入 parent 的子节点列表,引用计数 +1
parent->addChild(child, zOrder); // 指定渲染层级
child->removeFromParent();       // 从 parent 中移除,引用计数 -1
parent->removeChild(child);      // 同上
parent->removeAllChildren();     // 移除所有子节点
parent->getChildren();           // 获取所有子节点
child->getParent();              // 获取父节点

子节点的坐标、缩放、旋转、可见性都相对于父节点。父节点被移除时,所有子节点也会被移除。

addChild 只需要调用一次。调用后 child 就一直存在于 parent 的子节点列表中,每帧都会被渲染,直到主动调用 removeFromParent()removeChild() 把它移除。不需要每帧 addChild。

一个节点只能有一个 parent。如果对已经有 parent 的节点再次 addChild,会出错。

12.2 引用计数(retain / release / autorelease)

cocos2d 使用引用计数管理内存。所有继承自 Ref 的对象都有引用计数。

obj->retain();      // 引用计数 +1(你想持有它时调用)
obj->release();     // 引用计数 -1(不再需要时调用)
obj->autorelease(); // 放入自动释放池,这一帧结束时自动 release 一次
// 引用计数归 0 时自动 delete

create() 模式

cocos2d 中几乎所有类都遵循 CREATE_FUNC 宏定义的创建模式:

// CREATE_FUNC 宏展开后等价于:
static MyType* create() {
    MyType* pRet = new(std::nothrow) MyType();  // 堆上分配,引用计数 = 1
    if (pRet && pRet->init()) {
        pRet->autorelease();  // 放入自动释放池
        return pRet;
    } else {
        delete pRet;
        return nullptr;
    }
}

生命周期示例

auto* sprite = Sprite::create("image.png");
// 此时引用计数 = 1,且已标记 autorelease
// 如果这一帧内没有人 retain 它(比如 addChild),帧末它就会被释放

parent->addChild(sprite);
// addChild 内部调用 sprite->retain(),引用计数 = 2
// 帧末 autorelease 池清理,release 一次,引用计数 = 1
// sprite 安全存活

parent->removeChild(sprite);
// removeChild 内部调用 sprite->release(),引用计数 = 0
// 触发 delete,sprite 被析构

手动 retain 的场景

如果你要在成员变量中持有一个不在节点树中的 Ref 对象,需要手动 retain/release:

// RedreamLoader::ReboltLayer 中的实际例子
ReboltRedManager* reboltM = reboltReader->getReboltRedManager();
reboltM->retain();  // 手动 retain,因为 ReboltRedManager 不会被 addChild

// 对应地,在析构函数中必须手动 release:
RedreamLoader::~RedreamLoader() {
    if (_loaderReboltRedManager != nullptr) {
        _loaderReboltRedManager->release();
        _loaderReboltRedManager = nullptr;
    }
}

核心规则

12.3 定时器(schedule)

// 每帧调用
node->schedule([](float dt) { /* dt 是距上一帧的秒数 */ }, "key");

// 延迟一次性调用
node->scheduleOnce([](float dt) { /* 只执行一次 */ }, 2.0f, "key");

// 固定间隔调用
node->schedule([](float dt) { }, 0.5f, "key");  // 每 0.5 秒

// 取消定时器
node->unschedule("key");

QCoreLayer::playAnim 中就用了 scheduleOnce 来实现"动画播完后回调":

void QCoreLayer::playAnim(std::string animName, const std::function<void(float)>& cbFinishAnim, std::string cbScheduleKey) {
    _mAnimCtrl->runAnimationsForSequenceNamed(animName.c_str());
    if (cbFinishAnim) {
        float animTime = getAnimTime(animName);
        scheduleOnce(cbFinishAnim, animTime, cbScheduleKey);
    }
}

十三、典型使用模式

13.1 IRedream 基类模式

项目中定义了 IRedream 基类,约定子类在 initRebolt 中完成资源加载和初始化:

class IRedream : public cocos2d::Node {
public:
    virtual void initRebolt() = 0;
};

13.2 静态展示(KeyRedream 示例)

class KeyRedream : public IRedream {
    void initRebolt() override {
        _redream = RedreamLoader::Layer(_file);  // 加载
        addChild(_redream);                       // 挂到场景树
        _redream->playAnim("常态");               // 播放默认时间线
    }

    void refreshVisualState(int type) {
        _redream->playAnim("常态");               // 切换状态时重新播放
    }

private:
    std::string _file = "YJ_棋盘模块_特殊元素_钥匙.redream";
    RedreamLoader* _redream = nullptr;
};

13.3 带定位点的布局(SpoolCellRedream 示例)

class SpoolCellRedream : public IRedream {
    void initRebolt() override {
        _redream = RedreamLoader::Layer(_file);
        addChild(_redream);
    }

    void setCellSize(float width, float height) {
        Sprite* bgSprite = _redream->getSprite("棋盘格");
        Size originalSize = bgSprite->getContentSize();
        float scale = std::min(width / originalSize.width, height / originalSize.height);
        _contentRoot->setScale(scale);
    }

    Node* getElementLocatorNode() const {
        return _redream->getNode("元素定位点");
    }

    Node* getBundleLocatorNode(BundleLocatorType type) const {
        switch (type) {
            case BundleLocatorType::HorizontalBackLeftTop:
                return _redream->getNode("绳子横向_左上");
            // ...
        }
    }

private:
    std::string _file = "SL_棋盘模块_基本元素_棋盘格.redream";
    RedreamLoader* _redream = nullptr;
};

13.4 带动画反馈的元素

// 播放动画并在完成后恢复常态
_redream->playAnim("动画_激活");
const float delay = _redream->getAnimTime("动画_激活");
runAction(Sequence::create(
    DelayTime::create(delay),
    CallFunc::create([this]() {
        _redream->playAnim("常态");
    }),
    nullptr
));

13.5 播放后自动移除的特效

auto* efx = redutils::QCoreLayer::Layer("shop_loading.redream");
scene->addChild(efx);
efx->playAnim("1");

// 播完 "out" 动画后自动从场景中移除并释放
efx->playAnimAndRemoveSelf("out");

13.6 Rebolt 行为树驱动的界面

auto* layer = redutils::RUReboltLayer::createReboltLayer("shop_interface.redream");
parentNode->addChild(layer);

// 注册通知回调
layer->registerOnNotify([this](const redutils::ReboltNotifyData& data) {
    // 处理行为树发出的通知
});

// 运行行为树中的指定函数
layer->runBehaviacWhitFunName("初始化");