首页 / 编辑器专栏 / 旧版编辑器 · 节点类型

旧版编辑器 · 节点类型

这里的「旧版编辑器」指当前线上在用的整套宇宙编辑器体系—— 策划在 UE 里摆的行为树、挂在节点上的行为与条件、以及客户端对应的执行器。 新一代编辑器另开一页(新版编辑器),等资料齐了再同步。

本页是第一批内容:节点类型说明。先把「策划能摆出来的东西一共有几类、各自什么语义、 谁来执行」讲清楚,其余章节(保存格式、导表、调试)后续补。

13 种 EBtNode 105 个 EAction 50 种 EChildQuest 76 种 ECondition 数据取自 071 分支
页面状态:建设中 本页只完成了节点类型一章。待补清单在文末, 里面列了后面要写的部分,欢迎按需要调整优先级。

1. 先分清三层:树 / 节点 / 挂载物

策划打开编辑器看到的东西,代码里其实是三层,三层各有一套枚举,谁也不是谁。 排查「策划说找不到这个」的时候,第一步就是判断他说的是哪一层。

1
树(编辑器对象)
一棵树对应一个编辑器对象:关卡主控树(ILevelPlay.Tree)、 任务树(IQuest)、野外事件树(IWildEvent)。 数据结构都是 IBehaviorTree = { Vars, Root }, Root 恒为 Start 节点。
三种树共用同一套节点类型,但各自有独立的下拉过滤—— 同一个行为在主控树能摆、在任务树可能摆不出来。
2
节点(EBtNode,13 种)
树上每一个方框。只管流程:顺序、并行、循环、分支、终止。 详见第 2 章。
3
挂载物(真正干活的东西)
行为 EAction(做什么)、 子任务条件 EChildQuest(等什么达成)、 判断条件 ECondition(当下真不真)。 这三套枚举加起来两百多项,是真正的内容量所在。详见第 4–6 章。
「节点类型」这个词有歧义 策划口中的「节点」大多指第 3 层(「加一个传送节点」=加一个 EAction); 程序口中的「节点」通常指第 2 层(EBtNode)。
接需求时先对齐这一点,能省掉一轮返工。

2. 13 种行为树节点(EBtNode)

定义在 UniverseEditor/Interface/IQuest.ts, 同文件里的 nodeTips 就是策划在编辑器里悬停看到的原文。 按「能挂几个子节点」分三组,这个分组直接决定了树能怎么搭:

// IQuest.ts:1374 —— 结构约束就写在类型里
export type TOneChildBtNode    = IAlwaysFalseBtNode | IAlwaysTrueBtNode | IRepeaterBtNode | IStartBtNode;
export type TManyChildrenBtNode = IParallelSelectBtNode | ISelectBtNode | ISequenceBtNode;
export type TLeafBtNode        = IActionBtNode | IChildQuestBtNode | IConditionBtNode
                                | IQuestFailedBtNode | IQuestSucceedBtNode;

2.1 单子节点(4 种)

类型作用编辑器里的原文提示
Start树的根,恒定唯一,IBehaviorTree.Root 就是它(无提示)
Repeater循环执行下属节点循环节点,可以重复执行下属节点,每次单个循环结束后判断是否退出循环(返回 true)。循环节点内部不支持中断,可以通过使用并行节点 ParallelSelect 新增条件中途打断循环节点
AlwaysTrue吞掉子树结果,强制返回 True子节点执行完成后,强制返回值为 True(不熟悉行为树不推荐使用)
AlwaysFalse强制返回 False,用来把分支标成「可选」ParallelSelect 并行步骤中需要设置可选步骤时使用,AlwaysFalse 下属分支为可选
AlwaysFalse 不是「让它失败」,是「让它不计入完成数」 ParallelSelect 配了「完成 2 个」,下面挂 4 个分支,其中 1 个包在 AlwaysFalse 里—— 那个分支就是可做可不做:玩家做了有表现,不做也不耽误并行节点判定通过。
这是策划最常用的一个组合技,看到 AlwaysFalse 先按这个思路理解。

2.2 Repeater 的六个字段

循环节点是单子节点里字段最多、也最容易配错的一个:

字段含义注意
MaxRepeatTimes最大循环次数勾了无限循环后直接忽略
TickIntervalMs循环 tick 间隔(毫秒)0 视为默认 1000,最小 500
IsInfiniteLoop无限循环勾上后忽略 MaxRepeatTimes
InfiniteLoopTimeoutMs无限循环超时(毫秒)≤0 表示不超时,仅无限循环生效
IsTimeoutFail超时后节点算失败还是成功默认成功
ExitCondition退出循环条件每单次循环流程结束后才判断,不是随时判断
「循环退不出来」的两个常见原因 ① ExitCondition 只在单次循环跑完之后求值——循环体内部卡住(等一个永远不来的事件), 条件再对也判断不到;
② 想中途打断,必须在外面套 ParallelSelect 再挂打断条件, 在 Repeater 内部加节点是没用的(官方提示原文就是这么写的)。

2.3 多子节点(3 种)+ 分支选择

类型作用编辑器里的原文提示
Sequence顺序执行顺序执行下属节点,下面通常挂接多个 ChildQuest
Select依次尝试,一个成功就结束依次执行子节点,子节点返回 True 则终止并返回 True,否则执行下个子节点(不熟悉行为树不推荐使用)
ParallelSelect并行 N 选 M并行步骤:并行执行多个步骤,要求完成其中若干个。节点中配置完成数目。可通过 AlwaysFalse 节点将下属分支设为可选
ConditionSelector互斥分支分支选择:根据之前的对话、战斗结果选择分支,分支之间互斥。节点中配置分支条件

ParallelSelect 的 Cnt 字段:0 表示要求全部完成,>0 表示完成对应个数即可。

ConditionSelector 结构上不是 Children[] 而是 Slots[], 每个 slot 是 { Node, Condition } 一对——分支和它的进入条件绑在一起:

export interface IConditionSelectorBtNodeSlot {
    Node: TBtNode;
    Condition: IConditionGroup;
}

2.4 四个逻辑节点共享的一批字段(ILogicBtNode)

Sequence / Select / ParallelSelect / ConditionSelector 都继承 ILogicBtNode, 所以它们身上挂着一批和流程无关、但很关键的字段。 找不到某个功能配在哪,先来这里翻一遍:

字段作用
SaveConfig存档 & 回退点配置——存档点不是独立节点,是挂在逻辑节点上的
LockWalkMode节点激活期间只保留基础移动与视角,禁用技能/跳跃/冲刺/钩锁/交互
PostAkMusicList节点激活时发的音频事件列表,节点退出时不做任何处理
PostAkEvent已废弃被 PostAkMusicList 取代,仅运行时兼容读取存量单条配置
HotFixActionConfigs热更修复行为列表(Condition 节点上也有这个字段)
RelatedChildQuests【关卡行为树专用】关联的任务子节点
TidQuestAliasName / Desc【任务专用】在这个节点期间临时改任务名 / 描述
LevelId / RewardConfig【任务专用】关卡 Id 与奖励配置
带【任务专用】的字段在主控树上也能配出来 它们只是语义上限任务树,类型上并没有隔离。 主控树上填了任务别名不会报错,但也不会有任何效果—— 这类「配了没生效」查起来最耗时,看到字段注释里写了【xx专用】就当硬约束对待。

2.5 叶子节点(5 种)

类型作用关键字段
Action执行一串行为Actions: IActionInfo[],就这一个字段
ChildQuest一个任务步骤:等条件达成Condition + EnterActions / FinishActions + 追踪相关
Condition当场判真假(叶节点,不是装饰器)Condition、WaitUntilTrue(持续检测直到为真)
QuestSucceed成功并终止整棵树FinishActions、IsHalfFinish(是否中途结束)
QuestFailed失败并终止整棵树AutoFailed(直接失败)、NotReturnToSavePoint(失败后不回存档点)、FinishActions
Condition 与 ChildQuest 的分工 Condition 是瞬时判断:此刻为真就往下走,为假就返回 False。 勾了 WaitUntilTrue 才会持续检测。
ChildQuest 是一个会显示在任务 UI 上的步骤:有追踪文本、有寻路箭头、有进入/退出行为。 「玩家要做点什么」用 ChildQuest,「程序判断一下状态」用 Condition。

2.6 ChildQuest 节点的字段(最厚的一个)

字段作用
Condition完成条件,类型是 TChildQuestCondition(50 选 1)
TidTip / EdTidTip追踪文本。Tid 走多语言,EdTid 只在编辑期可见
TidTipPriority多条追踪文本时,优先级大的显示在上方
ShowNavigation是否显示寻路
TrackTarget追踪目标(位置 / 实体 / 刷怪器 / 分步 / 搬运物 五类)
EnterActions / FinishActions进入节点、退出节点时执行的行为列表
HideUi隐藏任务 UI(跑纯逻辑步骤时用)
EdTid 运行时读不到 和 Scheme 那边是同一条规矩:导表时 EdTid 不进 ClientJson, 所以「Tid 为空回退 EdTid」的代码是死代码。 细节见 Scheme 页的 EdTid / Tid 章节。

3. 行为节点内容物:EAction(105 个)

Action 节点和 ChildQuest 的进入/退出钩子里挂的,都是 IActionInfo[]。 枚举定义在 Interface/IAction.ts,配置表在 Common/Operation/Action.ts 的 actionConfig。

105 个行为,八成在服务器
actionConfig 里 83 条标 ImplementType: 'Server', 其余标 'Client'。
「这个行为不生效」第一步就是看它归谁——服务器实现的行为,客户端根本没有对应代码可以断点。
客户端只注册了 24 个执行器
LevelEventCenter(Game/LevelGamePlay/LevelEvents/)里 setup(EAction.X, LevelEventX) 一共 24 行。
客户端侧能改的行为就这些,其余全在服务器。

3.1 actionConfig 的字段,决定节点「摆得出来吗」

// Common/Operation/Action.ts
interface IActionItem {
    ImplementType: 'Client' | 'Server';
    Context?: TActionContextType[];              // entity / levelPlay / quest / wildEvent / gm
    RestrictedMapContext?: 'DungeonOnly' | 'WpOnly';  // 限副本 / 限大世界
    IsTest?: boolean;                            // 非测试模式下会被过滤掉
    ContextForTest?: TActionContextType[];       // 测试阶段额外开放的上下文
    IsAbandoned?: boolean;                       // 废弃标记
    Tags?: string[];                            // 分类标签,如 ['HotFix']
}

Context 的分布(一个行为可以同时属于多个上下文):

Context出现次数含义
levelPlay84关卡主控树上可摆
entity64实体的交互 / 触发组件里可摆
wildEvent54野外事件树上可摆
quest24任务树上可摆
「策划说找不到这个节点」的四个排查点 ① Context 里没列他所在的那种树; ② RestrictedMapContext 限了副本 / 大世界; ③ IsTest: true 而当前不是测试模式; ④ Scheme 加了但编辑器没重启——下拉在进程内只建一次。

4. 子任务节点内容物:EChildQuest(50 种)

ChildQuest 节点的 Condition.Type,决定这个步骤「等的是什么」。 枚举在 IQuest.ts,一共 50 项:击杀、到达区域、交互、等待时间、比较变量、检查刷怪器剩余数、爬塔星级……

4.1 客户端只实现了 17 种,其余 33 种兜底给服务器

// Game/Module/GeneralLogicTree/Define/NodeTypeDefine.ts
public static readonly ChildQuestNodeType: Record<EChildQuest, NodeTypeData> = {
    [EChildQuest.DoInteractNew]:  new NodeTypeData(InteractNewBehaviorNode),      // 客户端专属实现
    [EChildQuest.ReachArea]:      new NodeTypeData(ReachAreaBehaviorNode),
    [EChildQuest.TowerClimb]:     new NodeTypeData(TowerClimbNode),
    // …… 共 17 个有专属节点类

    [EChildQuest.Kill]:           new NodeTypeData(ServerAchieveChildQuestNode),  // 服务器判定,客户端只收结果
    [EChildQuest.CompareVar]:     new NodeTypeData(ServerAchieveChildQuestNode),
    // …… 共 33 个走这个兜底类
};
这张表是「该去哪查」的路标 子任务不完成时先查这张表:
落在 ServerAchieveChildQuestNode → 客户端只是在等服务器发达成通知, 本地断点毫无意义,去看协议有没有到;
有专属节点类 → 客户端自己在判,直接去那个 XxxNode.ts 里断点。

4.2 有客户端专属实现的那 17 种(按用途分)

用途节点类
交互 / 移动InteractNewBehaviorNode、ReachAreaBehaviorNode
等待WaitTimeNode、WaitEntityLoadedNode、WaitFlowAssetEndNode
刷怪器CheckAiSpawnerRemainCountNode、CheckAiSpawnerKilledCountNode
教学 / 引导CheckPlayerInputNode、CheckGuideCompleteNode、CheckGuideHaveCurrentTutorialNode、FinishComboTeachingNode、CheckQteCompleteNode
状态检查CheckEntityStateNode、CheckItemNumBehaviorNode、CheckPlayerAttackTargetNode
UI 交互ClickQuestFinishTipNode
玩法TowerClimbNode(→ 爬塔模块)

5. 判断条件:ECondition(76 种)

Condition 节点、ConditionSelector 的分支条件、 Repeater 的退出条件,用的都是 IConditionGroup,里面是 ECondition。

项数量 / 位置
枚举定义Interface/ICondition.ts,76 项
客户端求值器LevelConditionCenter 里 SetupCondition(...) 62 处
执行器文件Game/LevelGamePlay/LevelConditions/,62 个 LevelConditionCheckXxx.ts
纯客户端条件ClientConditions/ 下 2 个(任务状态、子任务节点状态)
新增一个 Condition 要过四层,漏一层症状各不相同 枚举 → 数据接口 → ConditionChild.ts 穷举默认表 → scheme + 注册。
第 3 层漏了是编译报错(还算好查), 第 4 层漏了是编译通过但下拉里没有(最难查)。
而且下拉三种树各一套:Quest 用 condition2Scheme、 关卡玩法用 …ForLevelPlay、野外事件用 …ForWildEvent—— 只注册一处的结果就是「策划在 A 树看得到、B 树看不到」。
完整实例见 W36-R01 的四层链路。

6. 客户端这边谁在执行

编辑器概念客户端落点规模
EAction(行为)LevelGamePlay/LevelEvents/LevelEventXxx.ts27 个文件 / 24 处注册
ECondition(条件)LevelGamePlay/LevelConditions/LevelConditionCheckXxx.ts62 个文件 / 62 处注册
EChildQuest(子任务)Module/GeneralLogicTree/BehaviorNode/ChildQuestNode/17 个专属类 + 1 个服务器兜底类
EBtNode(流程节点)Module/GeneralLogicTree/BehaviorNode/LogicNode/NodeTypeDefine.NewNodeObj 只造 5 类
客户端 NewNodeObj 只处理 5 种 EBtNode ChildQuest / Condition / QuestFailed / ParallelSelect / Sequence, 其余全部 default: break 返回 undefined。
也就是说 Select、Repeater、ConditionSelector、AlwaysTrue/False 在客户端没有对应的运行时对象——它们的流程语义由服务器负责, 客户端只负责把服务器点亮的节点显示出来。
这解释了为什么很多「树没往下走」的问题在客户端断不到点。

7. 加一个节点要动几处(速查)

加一个 EAction
① Interface/IAction.ts 枚举 + 参数接口
② Common/Operation/Action.ts 的 actionConfig(Context / ImplementType)
③ EditorCommon/Scheme/Action/*.ts 参数 Scheme
④ 客户端实现的话,再加 LevelEventXxx.ts + LevelEventCenter 注册
→ 详见 Scheme 页
加一个 ECondition
四层:枚举 → 接口 → ConditionChild.ts 穷举表 → scheme 注册(三种树各一处)。 客户端判定的还要加 LevelConditionCheckXxx.ts + SetupCondition。
→ 详见 W36-R01
加一个 EChildQuest
枚举 + 条件接口 + childQuestConfigs + scheme, 客户端要判定的话在 NodeTypeDefine.ChildQuestNodeType 里挂专属节点类, 否则挂 ServerAchieveChildQuestNode 由服务器判。
共同的三条
① 已有结构加字段必须 Optional; ② 改完 grep 有没有第二个注册点; ③ 编辑器 Scheme 下拉进程内只建一次,必须重启编辑器才看得到新项。

8. 待补清单

本页目前只写了节点类型。后面按这个顺序补(顺序可调):

待补章节要讲清楚的问题
三种树的差异主控树 / 任务树 / 野外事件树,各自能摆什么、不能摆什么,为什么
树变量与黑板IBehaviorTree.Vars 怎么定义、谁能读写、和服务器怎么同步
保存格式与导表编辑器存的 json 长什么样、dbChildId 这类「仅 DB 数据有效」字段是干嘛的
节点状态与调试树跑到哪一步怎么看、日志前缀、GM 指令
存档点(SaveConfig)挂在逻辑节点上的存档 / 回退点,回退时哪些状态会重置
热更节点(HotFixActionConfigs)Tags: ['HotFix'] 的行为是怎么被筛出来的
与新版编辑器的对应关系哪些概念保留、哪些被取代 —— 等新版那页有内容后一起写
数据口径 本页所有数量(105 / 50 / 76 / 13 / 24 / 62 / 17 / 33)都是在 d:\zhaozibo_071\Source\Client\TypeScript 分支上实测统计的。 分支不同数字会有出入,引用前先自己 grep 一遍。
注:Scheme 页里「171 个 EAction / 82 个 Server」是更早分支的口径,两处不一致以本页为准。