ARTICLE DETAIL

资讯详情

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

Flame Jenny 对话引擎 `<<if>>` 条件分支命令完全指南:语法、求值逻辑与源码实现

Flame Jenny 对话引擎 `<<if>>` 条件分支命令完全指南:语法、求值逻辑与源码实现 Flame Jenny 对话引擎if条件分支命令完全指南语法、求值逻辑与源码实现【免费下载链接】flameA Flutter based game engine.项目地址: https://gitcode.com/GitHub_Trending/fl/flameif是 Flame 游戏引擎内置的 Yarn 对话脚本语言由flame_jenny桥接包封装的 Jenny 运行时中最核心的控制流命令相当于绝大多数编程语言中的if关键字。本文以 if.md 为骨架结合 Jenny 运行时的解析器与执行器源码系统讲解if / elseif / else / endif的完整语法、逐分支求值规则、实战写法、错误处理与底层实现原理帮助你为游戏 NPC 对话编写出可靠的分支逻辑。一、if是什么if命令会先对它的条件condition进行求值再根据求值结果决定接下来执行哪一段语句。这与绝大多数编程语言中的if关键字在语义上是等价的它把对话脚本变成了一台真正的逻辑机器让 NPC 的台词、玩家的选项、游戏事件的走向都依赖于变量的值。在 Jenny 的命令体系中if被归类为控制流Control flow内置命令与jump切换到另一个节点、stop停止当前节点、visit临时跳转再返回、wait暂停对话共同构成脚本的流程控制能力。一个if命令可以包含多个部分整体结构如下if condition1 statements1... elseif condition2 statements2... else statementsN... endif使用时的硬性规则来自 if.md每个命令中的条件condition必须具有布尔boolean类型elseif块的数量可以是任意多个0 个、1 个或多个elseif块与else块都是可选的可以有也可以没有最后的endif必须存在用于收尾整个分支结构每个块内部的语句必须缩进缩进是 Jenny 划分块边界的语法手段。二、运行时求值逻辑条件如何被逐级判定理解了结构之后关键问题是运行时这些条件到底按什么顺序、以什么规则求值if.md 给出了明确的语义先对if块中的条件condition1求值如果condition1为true对话执行statements1其余的elseif条件不再求值其余语句块也不会执行如果condition1为false则继续计算condition2即第一个elseif的条件若condition2为true执行statements2若为false则继续尝试下一个elseif依此类推如果所有elseif条件都为false最终会落入else块并执行statementsN无论走哪条分支在最终endif之后对话都会继续执行后面的语句。可以用一张伪代码流程图概括求值 condition1 ├─ true → 执行 statements1 ──────────┐ └─ false → 求值 condition2 │ ├─ true → 执行 statements2 │ └─ false → 求值 condition3 │ ... │ 全为 false → 执行 statementsN若有 else ▼ 继续执行 endif 之后的语句需要注意两点语义细节短路语义一旦某个条件为true后续所有条件都不会被求值。这意味着你把代价较高或有副作用的表达式放在靠后的elseif中是安全的——它们根本不会被触发边界判定与顺序敏感条件从上到下依次匹配因此条件的书写顺序直接决定优先级。通常应把最苛刻、最特殊的条件写在前面把宽泛的条件写在后面详见下文示例。三、实战示例按声望值变化的守卫对话if.md 提供了一个完整的对话示例守卫Guard会根据你与当地市民的声望reputation用不同的方式向你打招呼如果声望低于 −100甚至会当场攻击你title: GuardGreeting --- if $reputation 100 Guard: Hail to the savior of the people! elseif $reputation 30 Guard: Nice to meet you, sir! elseif $reputation 0 Guard: Hello elseif $reputation -30 Guard: Im keeping an eye on you... elseif $reputation -100 Guard: You filthy scum! else Guard: Youll pay for your crimes! #auto attack endif 这个例子展示了几个值得借鉴的写法多级elseif链一个if命令里串联了 5 个elseif把声望值从高到低切成 6 个区间每段对应一种反应。这正是任意数量elseif规则的实践顺序决定优先级条件必须按从严格到宽松的顺序排列。若把$reputation 0写在$reputation 100前面那么声望 150 的玩家也会只听到 Hello因为第一个命中的条件会短路后续所有判定else兜底当声望 ≤ −100 时所有条件都不满足落入else块。此时守卫说出 Youll pay for your crimes!并通过行内标签#auto让这句话自动显示无需玩家点击随后执行attack——这是一个用户自定义命令可以在运行时通过UserDefinedCommand机制注册到脚本中参见 user_defined_commands.md块内语句可嵌套其他命令attack位于else块内部且已正确缩进。只要缩进正确块内既可以有对话行也可以有其他命令甚至可以是另一个if嵌套分支。四、条件表达式怎么写布尔类型、变量与运算符if的条件必须是布尔类型。在 Jenny 的表达式体系参见 expressions.md中布尔值来自以下几类来源布尔字面量true、false关系比较、!、、、、用于比较两个同类型值结果恒为布尔逻辑运算!或not、或and、||或or、^或xor符号形式和单词形式完全等价布尔函数调用如表达式函数库中的if(cond, a, b)、visited(NodeName)、visit_count(NodeName)等见 functions.md变量本身如果某个$变量的类型是bool它可以直接作为条件。运算符具有确定的优先级这一点可以在 Jenny 解析器中直接看到见 parse.dart优先级高→低运算符6*、/、%5-、4、!、、、、3not/!2and、xor^1or||因此守卫示例中的$reputation 100会先读取全局变量$reputation变量名以$开头参见 variables.md再与数字字面量比较最后产生布尔结果。你也可以组合更复杂的条件例如if $reputation 100 $hasRoyalTitle true Guard: Your Majesty! Forgive my rudeness! elseif $reputation 100 || $bribedTheGuard Guard: Hail to the savior of the people! endif注意 Jenny不支持运算符链式比较如$a $b $c需要多值同时比较时应显式使用例如$x $y $x $z。这一点在 operators.md 中有明确说明。五、缩进与空块语法的严谨之处Jenny 的脚本采用缩进敏感的块语法。在if condition之后如果没有立即遇到另一个命令就必须出现一个缩进的块否则解析器会抛出语法错误。这一点在解析器parseCommandIfBlock中有直接体现见 parse.dartif (peekToken() ! Token.startIndent) { syntaxError(the body of the $commandName command must be indented); }相应地else块的缩进检查在parseCommandElseBlock中见 parse.dart。值得注意的是如果if后面紧跟另一个命令中间没有对话行、没有缩进块解析器会允许该分支为空块const Block.empty()例如if $isDead jump DeathScene else Guard: Youre alive! endif这里if的条件为真时直接跳转到DeathScene节点关于jump的细节见 jump.md不需要任何对话行因此无需缩进块。六、底层实现源码视角看if如何工作6.1 执行器IfCommandJenny 运行时的if由IfCommand类实现完整代码位于 if_command.dart。它的核心数据结构与执行逻辑非常简洁class IfCommand extends Command { const IfCommand(this.ifs); /// First entry here is the if command, subsequent entries are the /// elseif commands, and the last entry is the else block (if /// present), which is represented as an [IfBlock] with condition /// constTrue. final ListIfBlock ifs; override void execute(DialogueRunner dialogue) { for (final ifBlock in ifs) { if (ifBlock.condition.value) { dialogue.enterBlock(ifBlock.block); break; } } } } class IfBlock { const IfBlock(this.condition, this.block); final BoolExpression condition; final Block block; }这段实现与文档描述的语义完全对应可以提炼出三个源码级事实统一为条件块列表ifs列表的第一个元素是if的条件块中间的若干个是elseif的条件块最后一个如果存在else的condition被固定为constTrue——即条件恒为真这正是else天然兜底语义的实现方式顺序扫描 短路execute用一个for循环从头到尾扫描条件块命中第一个condition.value true的块就enterBlock并break。因此一旦命中后续条件不再求值是循环结构天然保证的enterBlock即跳进子块enterBlock在 dialogue_runner.dart 中的实现是_currentIterator!.diveInto(block)即让当前节点的语句迭代器潜入被选中的语句块执行完块内语句后继续返回endif之后的语句——与文档所述对话最终会继续执行endif之后的语句完全吻合。6.2 解析器parseCommandIfif的语法解析集中在 parse.dart 的parseCommandIf/parseCommandIfBlock/parseCommandElseBlock三个方法中主要工作包括解析if条件块后进入循环逐个识别elseif、else、endif只允许一个else若出现第二个else抛出syntaxError(only one else is allowed)endif必须存在循环中若遇到无法识别的命令抛出syntaxError(endif expected)类型检查每个条件的表达式必须isBoolean否则抛出typeError(expression in an $commandName command must be boolean)——这与文档条件必须具有布尔类型的规则一一对应孤立命令防护如果在没有if的情况下直接使用else、elseif或endif解析器在parseCommand的默认分支中直接报错this command is only allowed after an if见 parse.dart。6.3 测试验证仓库自带的单元测试对上述规则做了逐条验证见 if_command_test.dartcommand name测试确认命令名称为ifIfStatements.yarn场景测试验证了if true ... else ... endif在运行时只执行条件为真的分支输出 4 行来自if块的台词else块完全不执行non-boolean condition in if测试确认if true字符串条件会触发TypeError: expression in an if command must be boolean并附带精确的line 3 column 6位置提示else without an if测试确认孤立else会触发SyntaxError: this command is only allowed after an if。如果你在自己的.yarn脚本中遇到类似报错可以对照这些测试用例快速定位问题。七、进阶用法if与选项行的条件除了作为独立语句块使用if还有一处精妙的内联用法对话选项行的条件。在 options.md 描述的选项语法中你可以在选项文本末尾附加一个if 条件只有当条件为真时该选项才可被玩家选择。其解析入口是maybeParseLineCondition见 parse.dartif (peekToken() ! Token.commandIf) { syntaxError(only if command is allowed for an option); } ... if (!expression.isBoolean) { typeError(the condition in if should be boolean); }也就是说选项行只允许使用if这一种命令作为条件且条件同样必须是布尔类型。典型写法如下Guard: What do you want? - Ask about the reward if $questCompleted Guard: Ah yes, your payment. Here you go. set $rewardCollected true - Just passing through只有当$questCompleted为true时Ask about the reward 才会出现在选项列表里。这为剧情解锁、好感度门槛等玩法提供了极轻量的实现手段。八、常见错误与排查清单结合文档规则、源码与测试整理一份if的排错清单症状原因对应规则/源码expression in an if command must be boolean条件不是布尔类型如字符串、数字、变量类型不匹配文档条件必须为布尔类型parse.dart 类型检查the body of the if command must be indented条件后的语句块没有缩进文档块内语句必须缩进parse.dartthis command is only allowed after an if在if之外单独使用else / elseif / endif解析器默认分支报错parse.dart有对应测试only one else is allowed一个if结构中写了多个elseparse.dartendif expected缺少收尾的endif或在分支之间出现未知命令文档endif必须存在parse.dart分支永远走不到条件顺序写反宽泛条件在前严格条件被短路运行时顺序扫描 短路语义if_command.dart九、总结if是 Jenny 对话脚本中功能最基础也最常用的控制流命令它以条件块列表 顺序扫描 短路命中的方式实现了多级条件分支配合elseif可以构建任意复杂度的分支树配合else提供兜底逻辑配合缩进规则保证块结构清晰。结合 if_command.dart 的执行器、parse.dart 的解析器以及 if_command_test.dart 的测试用例你可以确认其语义与绝大多数编程语言完全一致从而放心地把 NPC 的 AI 反应、任务进度、声望系统等交互逻辑交给这套简洁的 Yarn 语法。想继续深入了解可依次阅读 Jenny 的语言总览、变量、运算符 以及setset.md、declaredeclare.md等配套命令文档将 Jenny 集成进 Flame 游戏的完整流程见 jenny.md。【免费下载链接】flameA Flutter based game engine.项目地址: https://gitcode.com/GitHub_Trending/fl/flame创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表