因 Unity 犯贱,我决定不再更新相关插件。实不是我将其过迁怒于各位开发者朋友,只是想尽快完成自己的项目,早日脱离 Unity 另寻他路。UE 对我过于冗余,况且一朝被蛇咬十年怕井绳,有 Unity 开此先例,我已无法信任闭源的免费引擎。我欲在项目完成之后,学习引擎与图形 API ,方可灵活运用其他开源引擎而不再受人羁绊。我所分享的插件本为项目所用,经考虑通用适配细细打磨后与开发者朋友们分享。再无余力顾此,望各位见谅!
TMPro Player 是一款基于 TextMeshPro 的富文本标签管理插件,实现自定义富文本标签功能。
- 内置打字机效果,可以通过标签控制打字机效果的暂停与速度;
- 实现解析标签功能,用户只需要手动添加标签的定义以及标签要执行的效果的实现;
- 与 TextMeshPro 的内置标签兼容,当用户定义的标签与其相同时,则以用户定义为优先。
点击查看 CHANGELOG
本工具依赖于 com.unity.textmeshpro@3.0.6 以上版本。暂不支持 4.0 以上预览版本。
在项目根目录使用命令:
# 安装最新版本
openupm add com.gsr.tmproplayer
# 安装指定版本
openupm add com.gsr.tmproplayer@1.1.6
如果你使用了魔改的 com.unity.textmeshpro
,并且无法正确被识别为依赖时,请使用如下命令安装:
openupm add com.gsr.tmproplayer -f
在 Unity 引擎中,打开 Package Manager
,点击左上角的 +
,选择 Add package from git URL
,然后输入本项目的 git 地址即可:
https://github.com/Giresharu/TMPro-Player.git?path=Assets#1.1.6
建议使用 git URL 安装时指定版本号,否则会默认更新为最新仓库,而非最新版本。有可能会安装不稳定的仓库。
安装完插件后,可在 Packages/com.gsr.tmproplayer/Example/
目录下找到范例 Examples.unitypackage
。
范例中包含了文字振动
、文字波浪
以及文字淡入
功能的演示。
- 在场景上添加一个 TMPPlayerRichTagManager 单例;
- 在拥有 TextMeshPro 组件的 GameObject 上添加 TMProPlayer 组件;
- 直接在 Inspector 中 TextMeshPro 的
text
字段里填写内容,或者使用 TMProPlayer 类型的SetText
方法来添加内容。
- 标签分为成对标签以及单个标签;
- 类似
<tag>
</tag>
这样有头有尾的标签就是成对标签,他们会在文字初始化时就按照先后顺序执行,并通过协程在后台循环,处理范围内的文字; - 只有
<tag>
一个的即为单个标签,他们将会在打字机效果中,右侧的文字被显示之前(同一帧内)调用,同一个文字左侧有多个标签时,将会按照先后顺序依次调用; - 如果单个标签返回了一个 IEnumerator 类型,则会导致打字机效果被阻塞,通过这个规则可以实现打字机效果的暂停。
要自定义标签,就需要编写 TMPPlayerRichTagManager 的派生类,以此代替之放在场景上作单例。
要注册标签,我们需要在派生类覆写 TMPPlayerRichTagManager 的 Initialize
方法,并在其注册标签:
protected override void Initialize() {
base.Initialize(); // 如果你不需要默认的标签,可以不调用基类的 Initialize
SetActionInfo(args => ExampleTag((string)args[0]), "ExampleTag", false, "ExampleTag", "et");
}
static void ExampleTag(string value = "?"){
Debug.log(value);
}
其中 SetActionInfo 便是注册标签的方法。更详细的说明请参考 SetActionInfo 的介绍。
作为组件可以挂在拥有 TextMeshPro(UGUI) 或 TextMeshPro 的 GameObject 上。用于实现打字机效果以及对标签的解析与执行。
属性 / 字段 | 类型 | 描述 |
---|---|---|
Delay | int | 打字机效果中当前每个文字的间隔时间(毫秒) |
TextMeshPro | TMP_Text | 返回 GameObject 上挂载的 TextMeshPro 类型的组件(只读) |
CurrentChar | TMP_CharacterInfo | 返回当前打字机效果已经输出的最后一个文字的信息(只读) |
LastChar | TMP_CharacterInfo | 返回当前打字机效果已经输出的上一个文字的信息(只读) |
NextChar | TMP_CharacterInfo | 返回当前打字机效果还未输出的下一个文字的信息(只读) |
IsTyping | bool | 当前是否正在进行打字机协程(只读) |
IsSkipping | bool | 当前是否正在进行跳过(包括软跳)(只读) |
IsHardSkipping | bool | 当前是否正在进行跳过(只读) |
IsSoftSkipping | bool | 当前是否正在进行软跳(只读) |
VisibleCount | int | 当前已显示的文字数量(包括转义字符等不可视字符,但不包括被解析过的标签)(只读) |
isTypeWriter | bool | 是否使用打字机效果,修改后要下次 SetText 才会生效 |
openStyle | string | 自动添加在每次 SetText 的文字之前的文字,可以填写标签,用于方便地设置一些长时间不会改用的效果 |
closeStyle | string | 自动添加在每次 SetText 的文字之后的文字,作用同上 |
defaultDelay | int | 打字机效果中,默认的文字间隔时间(毫秒) |
timeScale | float | 对打字机效果的时间缩放,用于快进。也可凭个人喜好用作标签动作的时间缩放 |
public void SetText(string text, bool isAdditive = false, bool newline = false)
代替 TextMeshPro 中的 SetText
方法,用于设置想要被解析并显示的文本内容。Start 时会自动调用 SetText
来解析显示 TextMeshPro 的 text
。
参数 | 类型 | 描述 |
---|---|---|
text | string | 要被解析的原始文本 |
isAdditive | bool | 是否增量更新文本,如果为 true ,则不会清除已经显示的文字和效果,直接在后面添加 |
newLine | bool | 是否另起一行 |
public void Skip(bool invokeSingleActions = true)
跳过当前打字机效果,直接显示全部文字。
参数 | 类型 | 描述 |
---|---|---|
invokeSingleActions | bool | 是否执行被跳过的文字之中的单个标签 |
public void SoftSkip()
跳过文字,直到触发了 Func
类型的标签(如内置的 <pause>
标签这类会对打字机效果启用暂停的标签),之后结束跳过,恢复打字机效果。该效果别名软跳。
public void SetSoftSkip(bool value)
设置持续软跳的开关。如果开启,则会一直软跳,直到手动关闭。
参数 | 类型 | 描述 |
---|---|---|
value | bool | 开启/关闭软跳 |
public void AddUpdateFlags(TMP_VertexDataUpdateFlags updateFlag)
添加本帧需要更新渲染的 TextMeshPro 网格的信息,如:顶点位置、定点颜色、uv等,被添加的 flag 会在 LateUpdate
时通过 TextMeshPro.UpdateVertexData
统一更新。
用于制作文字网格动画相关的事件。
参数 | 类型 | 描述 |
---|---|---|
updateFlag | TMP_VertexDataUpdateFlags | 表示需要更新的 TMP 网格信息的枚举类型 |
public void RemoveUpdateFlags(TMP_VertexDataUpdateFlags updateFlag)
移除本帧需要更新渲染的某个网格信息。
public bool CheckUpdateFlags(TMP_VertexDataUpdateFlags updateFlags)
检查某个网格信息是否已经被标记为本帧需要更新。
TMProPlayer 解析标签所需要的单例,当 TMPPlayerRichTagManager 类在场景上存在时, TMProPlayer 才能根据它来解析标签。
可以通过继承该类来自定义标签。
protected void SetActionInfo(Action<object[]> action, string methodName, bool needClosingTag, params string[] keys)
protected void SetActionInfo(Func<object[], IEnumerator> func, string methodName, bool needClosingTag, params string[] keys)
在管理器里注册一个标签,并绑定需要执行的事件。
参数 | 类型 | 描述 |
---|---|---|
action | Action<object[]> | 触发标签时会执行的 Action |
func | Func<object[], IEnumerator> | 触发标签时会执行的 Func,返回值为 IEnumerator 。 用于直在打字机协程中直接执行协程相关的标签,而不另起一个协程 |
methodName | string | Action/Func 中调用的方法的名称,用于反射查询其参数的默认值,以供用户填写标签时省略默认参数 |
needClosingTag | bool | 是否需要闭合标签。拥有闭合标签的标签会在解析后立刻执行,并且会对事件提供标签的范围;而没有闭合标签的标签会在打字机效果执行到的时候执行 |
keys | string[] | 用户在文本里填写的标签关键字,同一个 Action/Func 可以有多个不同的关键字。当关键字冲突时,后注册的会覆盖先注册的 |
委托类型所用参数,是在 TMProPlayer 中被调用时,由 TMProPlayer 提供的。首先 TMProPlayer 解析并提供了标签的参数(以 object
类型),并按照索引顺序并转换类型提供给 Action
/ Func
要执行的方法。
SetActionInfo(args => ExampleTag((string)args[0], (int)args[1], (float)args[2]), "ExampleTag", false, "ExampleTag", "et");
// 这样就可以解析 <et=str,10,22.2> 的标签并执行对应的委托
TMProPlayer 还提供了以下非标签参数的参数,你可以按任意顺序插入在方法的形参中,以便形参调用:
类型 | 描述 |
---|---|
TMProPlayer | 执行标签的 TMProPlayer 自身 |
CancellationToken | TMProPlayer 的 actionTokenSource ,会在非增量更新以及销毁的时候取消 |
List<(int, int)> | 成对标签的范围列表,列表内每个元组分别为起始和结束的标签所在索引 |
例如:
SetActionInfo(args => ExampleTag((TMProPlayer)args[0], (string)args[1], (int)args[2], (float)args[3] ,(CancellationToken)args[4], (List<(int,int)>)args[5]), "ExampleTag", true, "ExampleTag", "et");
// 这样就可以提供这些非标签参数给 ExampleTag 方法
不过成对标签通常不会直接这么写,因为它们通常牵涉到循环,如一些文字动画效果。
如上所述,成对标签通常牵涉到循环,所以我们会在文本初始化时就执行他们,而通过 CancellationToken 在文本被销毁或非增量更新的时候结束它们。并结合标签的索引来实现效果。
那么我们就应该使用协程来实现成对标签(如果你选择使用 Unitask 或其他异步方案,也是可以的,这里只描述协程的做法):
// 改成用 StartCoroutine 来调用 IEnumerator 类型的方法
SetActionInfo(args => StartCoroutine(ExampleTag((TMProPlayer)args[0], (string)args[1], (int)args[2], (float)args[3] ,(CancellationToken)args[4], (List<(int,int)>)args[5])), "ExampleTag", true, "ExampleTag", "et");
static IEnumerator ExampleTag(TMProPlayer tmpp, string value1, int value2, float value3, CancellationToken token, List<(int start, int end)> ranges){
while(!token.IsCancellationRequested){
// 在其中对范围内的文字进行处理
}
}
protected override void Initialize()
初始化方法,会在 Awake
的时候自动调用。可以在此处调用 SetActionInfo 。
继承 TMPPlayerRichTagManager 时,若需要保留基类实现的标签,请写上下列代码:
base.Initialize();
以调用基类的初始化方法。
protected static List<int> IndicesInRange(TMP_TextInfo textInfo, List<(int start, int end)> ranges, bool isLeftOpen = false, bool isRightOpen = true)
以 List 的形式返回索引范围内的文字在 characterInfo
中的索引集合。通俗的解释就是把标签提供的范围信息转换为能够满足该范围条件的所有文字。由于标签的索引以右侧文字为准,所以默认为左闭右开区间。
characterInfo
即被 TextMeshPro
解析内置标签后的文字信息,而我们解析并自定义标签的信息是在其之前,所以一旦文本中内包含 TextMeshPro
的内置标签,就会导致自定义标签所对应的索引错位。调用这个方法能够获得最终的索引的集合。
参数 | 类型 | 描述 |
---|---|---|
textInfo | TMP_TextInfo | TextMeshPro 类型的 textInfo 属性,包含了文本的一系列信息 |
ranges | List<(int start, int end)> | 标签的范围,可以一次解析多个范围,所以以元组类型的 List 的形式存在 |
isLeftOpen | bool | range 所代表的区间是否左开?默认为 false |
isRightOpen | bool | range 所代表的区间是否右开?默认为 true |
protected static HashSet<int> IndicesInRangeHashSet(TMP_TextInfo textInfo, List<(int start, int end)> ranges, bool isLeftOpen = false, bool isRightOpen = true)
以 HashSet 的形式返回索引范围内的文字在 characterInfo
中的索引集合。
protected static Dictionary<int, T> IndicesInRangeDictionary<T>(TMP_TextInfo textInfo, List<(int start, int end)> ranges, bool isLeftOpen = false, bool isRightOpen = true)
以 Dictionary 的形式返回索引范围内的文字在 characterInfo
中的索引集合。value 的类型 T 可以是任意类型,用于记录某个 characterInfo
的状态或着其他信息。比如使用 bool 类型来表示当前文字是否已经触发过某个效果。
Apache License 2.0
Copyright 2023 Giresharu
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.