ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ET 框架 ConditionExpr 条件表达式解析器实战:从 Luban 配置到行为树运行

ET 框架 ConditionExpr 条件表达式解析器实战:从 Luban 配置到行为树运行 ET 框架 ConditionExpr 条件表达式解析器实战从 Luban 配置到行为树运行【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET导读ConditionExprcn.etetet.conditionexpr是 ET 框架中把 Excel/Luban 表格里填写的条件表达式字符串自动编译为行为树Behavior Tree节点的解析器与运行器专门解决进入副本条件、领取奖励条件、任务接取条件、功能开启条件这类策划可配置的条件判断需求。读完本文你将掌握条件表达式的完整语法比较运算、逻辑运算、错误码绑定、多 owner 变量与专用节点参数、它在 Luban bean 中的配置方式、底层词法分析 → 语法分析 → 行为树生成的编译链路以及运行期通过BTEnv执行并返回错误码的调用范式。一、包结构与定位cn.etetet.conditionexpr位于 Packages/cn.etetet.conditionexpr只依赖已声明的三个底层包behaviortree、unit、numeric。它不重复造轮子通用逻辑节点/||/!直接复用cn.etetet.behaviortree中的BTSequence、BTSelector、BTNot自己只定义条件叶子节点。目录约定Scripts/Model/Share 表达式模型、编译器Lexer/Parser、BT 节点数据、包元数据 Scripts/Hotfix/Share 运行时 HandlerBTNumericCompareHandler、ConditionRootHandler Scripts/Hotfix/Test 本包测试用例 Scripts/Model/Test 仅测试使用的 ConditionExpr 模型节点 Luban/Config/Base ConditionExpr Luban bean 定义__beans__.xlsx核心源码文件与职责文件职责ConditionExprLexer.cs词法分析把表达式字符串切成 TokenConditionExprParser.cs语法分析递归下降构建行为树ConditionExprCompiler.cs对外编译入口Lexer Parser的封装ConditionVariableRegistry.cs变量名 → 节点类型 的注册表BTNumericCompare.cs普通数值比较节点ConditionExpr.Partial.csLuban bean 的EndInit编译钩子二、Luban 配置字段与填写方式2.1 Bean 定义本包在Luban/Config/Base/__beans__.xlsx中定义了一个 Luban beanET.ConditionExpr字段含义字段类型含义Exprstring条件表达式ErrorCodeint叶子条件没有显式错误码时使用的默认错误码Descstring策划备注不参与运行时判断2.2 在业务表中引用任意业务表可以直接声明该 bean 类型的字段##var EnterCondition ##type ET.ConditionExpr ##comment 进入条件填写示例Expr (HP 10 : 10001 || MP 100 : 10002) Speed 0 : 10003 ErrorCode 9999 Desc 进入副本条件2.3 编译时机EndInit配置对象创建时Luban 生成的类会调用ConditionExpr.EndInit()。该 partial 方法见 ConditionExpr.Partial.cs把字符串交给编译器partial void EndInit() { this.Root ConditionExprCompiler.Compile(this.Expr, this.ErrorCode); }编译结果保存在ConditionExpr.Root类型为ConditionRoot。也就是说配置加载完成的那一刻表达式就已经被编译成行为树运行期不需要再做字符串解析性能开销只在加载阶段发生一次。三、表达式语法完整说明3.1 比较运算 !对应枚举ConditionCompareOp见 ConditionCompareOp.csGreater1, GreaterEqual2, Less3, LessEqual4, Equal5, NotEqual6。3.2 逻辑运算 || ! ()运算优先级与 C 系语言一致! ||建议复杂条件显式加括号例如(HP 10 : 10001 || MP 100 : 10002) Speed 0 : 100033.3 错误码绑定每个叶子条件可以用冒号绑定显式错误码HP 10 : 10001如果叶子条件没写: errorCode则使用 bean 上的默认ET.ConditionExpr.ErrorCodeHP 10编译时默认错误码参数来自Compile(expr, defaultErrorCode)的第二个参数。3.4 多 owner 语法OwnerKey.Variable当BTEnv中存在多个 owner 时用点号指定从哪个 owner 读取变量Unit1.HP 0 || Unit2.MP 100约束OwnerKey.Variable只允许一层点号Unit1.A.B这类多级引用会在编译期报错condition variable reference invalid见 ConditionExprParser.csOwnerKey必须是调用方通过BTEnv.AddEntity(key, owner)传入的 owner key目标节点必须声明public string OwnerKey字段否则编译时报错condition node owner key field not found当前BTNumericCompareHandler按现有NumericComponent契约读取Unit即要求env.GetEntityUnit(node.OwnerKey)取到的实体带NumericComponent。3.5 专用节点参数语法NodeName(Param1, Param2)专用条件节点支持括号传字符串参数Friend1(HP) 0 Friend2(HP, MP) 100规则Friend1、Friend2是通过ConditionVariableAttribute注册的节点名HP、MP会按文本原样写入节点的public string[] Params字段只有声明了Params字段类型必须是string[]的节点才能使用括号参数数值节点BTNumericCompare没有Params字段所以HP(Friend1)这种写法会在编译期报错condition node params field not found测试用例 Conditionexpr_Compile_Test.cs 专门验证了这一点OwnerKey.Variable与括号参数不能混用于同一专用节点测试用例验证Unit1.Friend1(HP) 0会因目标节点缺少OwnerKey字段而报错。四、编译原理从字符串到行为树编译入口是 ConditionExprCompiler.cspublic static ConditionRoot Compile(string expr, int defaultErrorCode 1) { ConditionExprLexer lexer new(expr); ListConditionToken tokens lexer.Tokenize(); ConditionExprParser parser new(tokens, defaultErrorCode); return parser.Parse(); }4.1 词法分析LexerConditionExprLexer.cs 逐字符扫描产出 Token 序列类型定义见 ConditionToken.cs标识符以字母或_开头可含字母、数字、_、..被包含进标识符以便支持Unit1.HP这种 owner 引用数字支持long解析也支持负数字面量-后跟数字解析失败抛condition number parse error运算符/、/、后必须跟组成、!/!、、||、(、)、:、,。或或|后面没有配对字符时抛condition token error空白全部跳过所以表达式里可以随意加空格扫描结束追加一个End结束符。4.2 语法分析ParserConditionExprParser.cs 采用递归下降 优先级分层ParseExpression → ParseOr → ParseAnd → ParseUnary → ParsePrimary → ParseCompareParseOr处理||生成BTSelectorParseAnd处理生成BTSequenceParseUnary处理!生成BTNot默认错误码取 defaultErrorCode可被:覆盖ParsePrimary处理括号支持对括号组整体应用: errorCode与叶子比较ParseCompare解析变量 [OwnerKey.] [括号参数] 比较符 数值 [: errorCode]通过ConditionVariableRegistry查变量对应的节点类型反射实例化节点并填充字段。值得一提的优化AddSequenceChild/AddSelectorChild会把连续的A B C摊平成单层BTSequence避免嵌套行为树过深见 ConditionExprParser.cs。4.3 字段填充与校验编译期对节点字段做强校验OwnerKey字段必须存在且类型为string否则抛condition node owner key field must be stringParams字段必须存在且类型为string[]否则抛condition node params field must be string[]错误码超出int范围抛condition error code out of range括号组错误码只允许作用在叶子节点或!节点上否则抛condition group error code only support leaf or not node。从源码结构看这套校验把大量配置写错的问题提前到编译期暴露而不是拖到运行期才出错。五、变量注册机制5.1 普通数值变量普通数值变量来自NumericType枚举。ConditionVariableRegistry.Awake()见 ConditionVariableRegistry.cs会遍历NumericType的全部枚举值把每个枚举名注册为变量节点类型指向BTNumericCompareHP - BTNumericCompare, NumericType.HP MP - BTNumericCompare, NumericType.MP Speed - BTNumericCompare, NumericType.Speed通过CodeTypes.Instance.GetTypes(typeof(ConditionVariableAttribute))扫描所有标记了[ConditionVariable(...)]的类注册专用节点。因此表达式中的变量名必须和NumericType常量名一致HP 10 MP 100 Speed 0 Unit1.HP 0 Unit2.MP 100如果变量没有注册编译时抛condition variable not registered: Xxx重复注册同一个变量名也会抛异常duplicate condition variable: Xxx。5.2 注册表存放方式注册结果保存在ConditionVariableRegistry的实例字段variableNodeTypes/numericTypes两个字典中不使用静态字典。该注册表是一个挂在 ET 生命周期上的SingletonConditionVariableRegistry符合包 AGENTS 约定的Attribute 扫描结果必须挂在 ET 生命周期对象上。六、运行时执行与错误码语义6.1 返回值约定行为树返回值约定0 条件成功 非 0 条件失败值为错误码6.2 三种逻辑节点的短路语义BTSequence对应从左到右执行 遇到第一个失败节点直接返回该节点错误码 全部成功返回 0BTSelector对应||从左到右执行 任意子节点成功返回 0 全部失败时返回第一个失败节点的错误码BTNot对应!子节点失败时! 成功返回 0 子节点成功时! 失败返回 BTNot.ErrorCode6.3 示例推演对表达式(HP 10 : 10001 || MP 100 : 10002) Speed 0 : 10003HP 满足且 Speed 满足 - 0 HP 不满足但 MP 满足且 Speed 满足 - 0 HP 和 MP 都不满足 - 10001 HP 或 MP 满足但 Speed 不满足 - 10003测试用例 Conditionexpr_Run_Test.cs 用真实UnitNumericComponent验证了这三种返回路径HP5/MP50 → 10001MP100 → 0HP10/Speed0 → 10003。6.4 数值比较 Handler运行时叶子节点由 BTNumericCompareHandler.cs 执行Unit owner env.GetEntityUnit(node.OwnerKey); NumericComponent numericComponent owner.NumericComponent; long left numericComponent.GetAsLong(node.NumericType); return ConditionCompareHelper.Compare(left, node.Op, node.Value) ? ErrorCode.ERR_Success : node.ErrorCode;比较逻辑集中在 ConditionCompareHelper.cs 的Compare(long left, ConditionCompareOp op, long right)中未知操作符抛unknown condition compare op。6.5 Root HandlerConditionRootHandler.cs 是整棵条件树的入口空树直接成功子节点数量大于 1 抛异常否则把唯一的子节点交给BTDispatcher执行。七、运行期调用范式7.1 使用配置对象中的 Root配置加载完成后直接用 Luban 对象里的RootConditionExpr condition mapConfig.EnterCondition; BTEnv env BTEnv.Create(scene, unit.Id); try { env.AddEntity(ConditionExprEnvKeys.Unit, unit); int errorCode BTHelper.RunTree(condition.Root, env); if (errorCode ! ErrorCode.ERR_Success) { return errorCode; } } finally { env.Dispose(); }ConditionExprEnvKeys.Unit的值为字符串Unit见 ConditionExprEnvKeys.cs。裸变量没有 owner 前缀的变量默认从Unit这个 key 读取 owner所以调用方必须用env.AddEntity(ConditionExprEnvKeys.Unit, unit)注册默认 owner。7.2 直接编译字符串也可以在代码里直接编译表达式字符串跳过 LubanConditionRoot root ConditionExprCompiler.Compile( (HP 10 : 10001 || MP 100 : 10002) Speed 0 : 10003, 9999);7.3 多 owner key 示例ConditionRoot root ConditionExprCompiler.Compile( Unit1.HP 0 || Unit2.MP 100, 9999); BTEnv env BTEnv.Create(scene, unit1.Id); try { env.AddEntity(Unit1, unit1); env.AddEntity(Unit2, unit2); int errorCode BTHelper.RunTree(root, env); } finally { env.Dispose(); }带 owner key 的变量会读取表达式前缀对应的 keyUnit1.HP从 keyUnit1取实体Unit2.MP从 keyUnit2取实体。运行测试 Conditionexpr_Run_Test.cs 用两个带NumericComponent的Unit实体验证了三条分支路径双失败 → 20001仅 HP 分支成功 → 0仅 MP 分支成功 → 0。八、扩展专用变量节点当某个变量不能从NumericComponent读取时例如 VIP 等级、好友关系等业务数据可以通过ConditionVariableAttribute注册专用节点让它与BTNumericCompare平行工作。8.1 定义一个专用节点[ConditionVariable(VipLevel)] public class BTVipLevelCompare : BTCondition { public string[] Params; public ConditionCompareOp Op; public long Value; public int ErrorCode; }8.2 注册规则专用节点必须继承 BTCondition 如果要接收表达式中的比较符、目标值和错误码需要声明 Op / Value / ErrorCode public 字段 如果要支持 OwnerKey.Variable 语法需要声明 OwnerKey public 字段类型必须是 string 如果要支持 NodeName(Param1, Param2) 语法需要声明 Params public 字段类型必须是 string[] 变量名不能和已有 NumericType 或其它 ConditionVariableAttribute 重复 注册结果保存在 ConditionVariableRegistry 实例字段中不使用静态字典其中Op/Value/ErrorCode三个字段是通过反射按BTNumericCompare的字段名写入的见 ConditionExprParser.cs 的SetCompareField因此字段名不能改动OwnerKey与Params则由专门的SetOwnerKeyField/SetParamsField反射写入并校验类型。BTNumericCompare和BTVipLevelCompare是平行关系都继承BTCondition。专用节点的 Handler 负责按业务规则取值并比较与数值 Handler 一样继承ABTHandlerT即可。测试用的专用节点 Conditionexpr_ParamCompareNode.cs 放在Scripts/Model/Test而非Scripts/Model/Share遵循只给测试使用的节点不要污染共享模型的包约定。九、测试与验证本包测试位于Packages/cn.etetet.conditionexpr/Scripts/Hotfix/Test共三个测试文件Conditionexpr_Compile_Test.cs验证表达式编译结构Root 子节点数量与类型、NumericType注册、多 owner 解析、专用节点Params解析、以及两类编译期报错Conditionexpr_LubanBean_Test.cs验证 Luban bean 构造后EndInit自动编译Root非空且叶子节点为BTNumericCompareConditionexpr_Run_Test.cs在真实SceneUnitNumericComponent上跑行为树验证错误码返回与多 owner 数值读取。执行测试dotnet build ET.sln Remove-Item ./Logs -Recurse -Force -ErrorAction SilentlyContinue Test --NameConditionexpr | dotnet ./Bin/ET.App.dll --SceneNameTest覆盖点汇总表达式编译结构 Luban bean EndInit 编译 NumericType 注册到 BTNumericCompare 运行时错误码返回 多 owner key 数值读取 专用节点 Params 参数解析十、开发约定速查最后汇总本包 AGENTS 文档中的关键开发约束方便后续在包内扩展时遵守只依赖已声明的底层包behaviortree、unit、numeric 不新增静态注册表或静态缓存Attribute 扫描结果必须挂在 ET 生命周期对象上 通用逻辑节点复用 cn.etetet.behaviortree 中的 BTSequence、BTSelector、BTNot ConditionExpr 自己的节点定义在本包内不放回 cn.etetet.btnode 只给测试使用的 ConditionExpr 模型节点放在 Scripts/Model/Test OwnerKey.Variable 只允许一层点号OwnerKey 必须是调用方已通过 BTEnv.AddEntity 传入的 key 专用条件节点支持 NodeName(Param1, Param2) 语法没有 Params 字段的节点不能使用括号参数从整体看ConditionExpr 的定位非常清晰把策划填写的条件字符串在配置加载期编译为行为树运行期由 ET 行为树调度器统一执行错误码即条件失败原因。这种设计既让策划免于写 C# 代码又让程序侧复用成熟的行为树执行体系是配置驱动玩法逻辑的一个典型实践。【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表