资讯详情

深度解析 Jenny 对话系统的 MarkupAttribute:标记范围描述器与动态参数机制

📅 2026/9/16 21:52:03 | 华诺云谱 👁 阅读
深度解析 Jenny 对话系统的 MarkupAttribute:标记范围描述器与动态参数机制
深度解析 Jenny 对话系统的 MarkupAttribute标记范围描述器与动态参数机制【免费下载链接】flameA Flutter based game engine.项目地址: https://gitcode.com/GitHub_Trending/fl/flame导读MarkupAttribute是 Flame 游戏引擎官方对话系统 jenny 中用于描述一行对话文本内特定子区间的数据结构它记录了某个标记标签如[b]、[link]所覆盖的文本位置、长度以及携带的参数是开发者将Yarn脚本中的装饰信息斜体、颜色、物品引用、tooltip 等映射到游戏表现层的桥梁。读完本文你将掌握 MarkupAttribute 的完整属性语义、动态参数求值原理、源码级生命周期以及如何在游戏内消费这些属性实现富文本渲染与交互。什么是 MarkupAttribute在 jenny 的 Yarn 脚本中一行对话文本可以被[标签名]...[/标签名]形式的标记标签划分为多个片段。这些片段在脚本被解析后并不会出现在最终输出的文本里标记只做注解不改变文本内容与显示而是被抽象为一个个MarkupAttribute对象挂载在对应的 DialogueLine 上。例如下面这行.yarn脚本含有两段被标记标签包裹的文本因此会对应生成两个MarkupAttribute[b]Jenny[/b] is a library based on \ [link urldocs.yarnspinner.dev]YarnSpinner[/link] for Unity.其中第一个属性来自[b]Jenny[/b]标签名为b第二个属性来自[link]...[/link]标签名为link并带有一个参数url。从哪获取 MarkupAttribute所有MarkupAttribute都存放在该行DialogueLine的attributes属性中。源码 dialogue_line.dart 中定义/// The list of markup spans associated with the line. ListMarkupAttribute get attributes _content.attributes ?? const [];当一行没有任何标记时该列表为空。同时请注意 line_content.dart 的求值流程调用evaluate()时每个属性的reset()会先被触发随后文本中的内联表达式{...}被逐一替换并在替换时通过maybeShift()修正各属性的起止位置——这正是下文要讲的位置偏移机制的来源。核心属性全解MarkupAttribute暴露了 5 个核心只读属性见 markup_attribute.dart属性类型含义nameString标记标签的名称。例如[b]的属性名为b[link]的属性名为link。源码注释指出该名称始终是一个合法的 IDvalid IDstartint标记范围在最终文本中的起始索引包含该位置endint标记范围在最终文本中的结束索引不包含该位置lengthint被标记文本的长度恒等于end - startparametersMapString, dynamic该标记携带的所有参数键值对start / end 的边界语义start包含、end不包含的区间语义意味着可以用text.substring(start, end)直接取出被标记的文本。在上一节的示例中第一个属性的name为b其parameters为空 Map第二个属性的parameters等于{url: docs.yarnspinner.dev}。零宽zero-width标记也是合法存在当start end时该属性不覆盖任何字符。这在自闭合标签如[gp/]中非常常见——它只标记一个位置点而非一段文本。测试 dialogue_line_test.dart 验证了这一点The price is 243[gp/] per item求值后gp属性的length为 0而start恰好等于The price is 243.length。与 DialogueLine 的关系DialogueLine还提供content、character说话者、tags#hashtag列表等元信息MarkupAttribute仅是其中标记范围这一类信息。需要注意的是标记标签本身不会出现在DialogueLine.text中——text是经过表达式替换、标记剥离和转义处理之后的最终展示文本见 dialogue_line.dart。动态参数运行时求值parameters的值类型只可能是String、num或bool三者之一具体取决于.yarn脚本中表达式求值出的类型。与静态文本不同参数值可以是动态表达式在每次执行该行时重新求值。例如下面这行脚本中color参数每次执行都会取变量$color的当前值My [i]favorite[/i] color is [bb color$color]{$color}[/bb].测试 dialogue_line_test.dart 完整演示了这一机制X [box index$index /] Y Z初始设置$index 1evaluate()后attributes[0].parameters[index]为1将变量改为$index 42再次evaluate()后该参数变为42。源码层面的求值时机从实现看每个参数在 markup_attribute.dart 中并不是直接存值而是保存了一个表达式对象MapString, Expressioninternal void reset() { _start _startTextPosition.floor(); _end _endTextPosition.floor(); _parameterExpressions?.forEach((key, expression) { _parameterValues![key] expression.value; }); }reset()被标记为internal它会在父级DialogueLine每次被对话运行器DialogueRunner派发时被调用见 line_content.dart 的attributes?.forEach((a) a.reset())。因此每次行被运行时参数值都会基于最新的变量状态重新计算——这就是动态参数的底层原理。构造函数中传入的parameters参数接收的是MapString, Expression而对外暴露的parametersgetter 返回的才是求值后的MapString, dynamic。位置偏移机制当标记包围动态文本Yarn 脚本允许标记标签包裹内联表达式插值文本例如Hello, [b]{$player}[/b]!此时被标记的文本长度在每次运行时都可能不同玩家名字长短不一。MarkupAttribute通过分数位置fractional position技术解决这一问题构造函数接收的startTextPosition/endTextPosition是double类型并叠加了subIndex * _subFactor的偏移量_subFactor 1.0 / 1024见 markup_attribute.dart。LineContent.evaluate()在逐个插入表达式时调用maybeShift(position, length, subIndex)line_content.dartinternal void maybeShift(int position, int length, int subIndex) { final fractionalPosition position subIndex * _subFactor; if (_startTextPosition fractionalPosition) { _start length; } if (_endTextPosition fractionalPosition) { _end length; } }含义是如果某个属性范围位于当前插入点之后则将其start/end整体向后平移插入文本的长度从而保证start、end始终指向最终文本中的正确位置。测试dialogue_line_test.dart中的dynamic markup attributes用例同时验证了动态参数与位置跟踪的正确性。同时需要明确一个边界标记可以包围动态文本但标记本身不能被动态生成。插值表达式中的内容即使包含方括号文本也会被原样插入、不会被解析为新的标记。运行时的值域与转义由于parameters的取值受 Yarn 表达式系统约束实际可用的值形态包括字符串字面量单引号或双引号包裹如placeTS数字整数或小数如x23、y-74布尔与变量true/false关键字以及$变量名形式的运行时变量函数表达式如[item id$reward/]中$reward可来自local $reward if($chapter1, A0325, A1018)的条件赋值。若一行中需要出现真正的方括号文本而非标记语法必须使用反斜杠转义如\[world\]。词法分析器 tokenize.dart 中的eatTextEscapeSequence()会在\后识别[、]、{、}、#、\、:、/、-、、、n等字符并输出为普通文本。解析与构造从脚本到对象MarkupAttribute由解析器 parse.dart 中的parseLineContent()生成其关键流程为遇到[Token.startMarkupTag时调用parseMarkupTag()解析标签名与参数自闭合标签[name/]立即构建属性范围起点与终点重合普通开标签推入markupStack栈等待匹配的闭合标签闭合标签[/name]从栈顶弹出对应开标签计算endTextPosition后调用_Markup.build()parse.dart生成MarkupAttributeclose-all 标签[/]一次性关闭栈中所有已打开的标记若行结束时栈仍非空抛出markup tag [...] was not closed语法错误。_Markup.build()中参数为空时传入null使最终parametersgetter 返回不可变的空 Mapconst String, dynamic{}这是源码中的一处细节优化markup_attribute.dart。实战在游戏中消费 MarkupAttribute标记标签本身不会改变文本或显示具体如何呈现完全由开发者决定。以下是几个基于 jenny 文档 markup.md 与当前仓库示例的典型应用场景。场景一富文本样式渲染通过name区分样式结合start/end从DialogueLine.text中截取对应片段并施加不同的渲染title: Scene117_Harry_MrMalfoy --- Harry: Im not afraid of [cursed]Voldemort[/cursed]! MrMalfoy: You must be really brave... or really [i]stupid[/i]? 游戏中可以对cursed属性对应的文字施加特殊效果如抖动、绿色描边对i属性渲染为斜体。场景二附加交互信息tooltip标记可以携带任意参数作为附加元数据例如为地名提供 tooltiptitle: MonkDialogue --- Monk: Visit [tooltip placeTS x23 y-74]Llewellyn[/] in Thunderstorm, \ he will be able to help you. 注意此处使用了 close-all 标签[/]代替[/tooltip]简化长标签名的书写。游戏可将place、x、y参数用于鼠标悬停提示或寻路小地图的定位。场景三可交互的非文本元素自闭合标签通常标记一个插入点由游戏替换为交互对象title: BlacksmithQuest --- local $reward if($chapter1, A0325, A1018) Smith: Find me my lost ring, and Ill give you this [item id$reward/]. 这里[item id$reward/]是一个零宽属性id参数在运行时根据章节动态确定游戏可将该位置渲染为物品图标点击后展示物品详情卡。边界与注意要点必须成对闭合每个开标签都要有匹配的闭合标签否则解析报错唯一例外是自闭合标签与 close-all 标签[/]嵌套必须规范标记范围必须完全包含或完全互斥不允许交叉如Lorem [S]ipsum dolor [A]sit[/A] amet[/S], consectetur [B]adipiscing[/B] elit是正确的嵌套示例位置随时可能变化start、end是最终文本中的位置随着该行每次重新求值表达式值变化而偏移应在每次DialogueLine派发到视图层后再读取不要缓存跨行的位置值自闭合标签的空格处理若自闭合标签两侧都有空格其后紧跟的一个空格会被从结果文本中移除[wave/] consectetur会输出... sit amet, consectetur中间只剩一个空格若希望保留两个空格可在标签后多加一个空格。该规则实现在 tokenize.dart 的eatMarkupEnd()中行内表达式不能包含标记{...}内部不支持标记语法。参考文件索引本文主体文档markup_attribute.md运行时对象markup_attribute.dart宿主行对象dialogue_line.dart、line_content.dart解析与词法parse.dart、tokenize.dart语言语法markup.md、lines.md测试验证dialogue_line_test.dart、parse_test.dart【免费下载链接】flameA Flutter based game engine.项目地址: https://gitcode.com/GitHub_Trending/fl/flame创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。