Skip to main content

从 VRChat 虚拟形象到世界创作: 由动画控制器驱动的世界交互 —— AnimatorUtilities

引言

在 VRChat 中,动画控制器 (Animator) 是一个强大的工具。

在虚拟形象的制作中,由于脚本支持的缺失,动画控制器被广泛用于驱动各种交互行为,例如表情切换、物品开关、玩家交互等。通过参数、状态机和动画层的组合,创作者可以在不编写脚本的情况下实现较为复杂的逻辑控制。

在世界制作中,尽管 Udon 的引入提供了更为灵活的交互逻辑实现方式,但动画控制器依然在一些特定场景中发挥着重要的作用。例如,当交互流程更适合用状态机描述时,动画控制器可以提供更直观和高效的解决方案;当需要控制复杂动画时,动画控制器的使用可以大大简化动画控制的实现难度,同时缓解 Udon 程序带来的性能开销。

AnimatorUtilities 组件包基于这一背景而设计,旨在为世界创作者提供一套基于动画控制器的交互实现工具。允许创作者在不编写 Udon 代码的情况下,将世界交互事件映射到动画控制器参数上,从而驱动动画状态机,实现交互逻辑与动画表现的联动。

安装

  1. 下载 Xuan25-VRC-Common-Utilities 组件包,并将其导入到你的 Unity 项目中。该组件包包含 UdonTelemetry 组件以及一系列相关依赖组件。你可以使用 Git 将其 clone 到项目的 Packages 目录下,或者直接下载压缩包并将其解压到该目录中。
  2. 在 Unity 项目中,找到 Packages/Xuan25 Common Utilities/AnimatorUtilities/Examples 目录下的示例场景 Examples.unity,并将其打开。你可以在该场景中查看和测试 AnimatorUtilities 组件的使用方法。

使用方法

在接下来的使用方法说明中,一个简单的“三棱柱”结构将会被用作示例,该结构包含三个侧面,每个侧面标注了一个数字。在接下来的示例中,这个三棱柱的旋转状态将会被通过不同的交互方式来控制,从而展示 AnimatorUtilities 组件的功能。这一部分的预制件 Prefab/TriangularPrism.prefab 可在 Packages/Xuan25 Common Utilities/AnimatorUtilities/Examples 目录下找到。在后文中,如无特别说明,所有的文件路径均指向该目录下的内容。

离散参数循环切换

这一部分将说明 DiscreteAnimatorDriver 组件的使用方法,该组件允许创作者通过不同的交互方式来循环切换动画控制器的一个离散参数。

在这一部分的示例中,一个包含三个状态和一个整型参数 State 的动画控制器将会被用来控制三棱柱的旋转状态,其中参数的范围为 0-2,每个值驱动一个旋转状态。动画控制器可在 Animation/DiscreteIntController.controller 文件中找到。

为了驱动动画控制器中的参数,一个名为 Driver 的物件被创建,并用于附加 DiscreteAnimatorDriver (离散动画驱动器) 组件。该组件的 Animator 字段被设置为上述动画控制器所在的物件,Parameter Name 字段被设置为上述参数的名称 State, Parameter Type 设置为整型 Int,Parameter Int Value 的列表被设置为 [0, 1, 2] 三个项目。Global 字段被设置为 True,以允许动画控制器参数在同一个实例的所有玩家间同步,这类似于 Avatar 参数的 Sync 选项。

通过交互

通过交互来切换动画控制器参数的实现相对简单,只需要在 Driver 物件上附加一个 Collider 组件并勾选 Is Trigger 用来检测玩家的交互事件。当玩家与该物件进行交互时,DiscreteAnimatorDriver 组件会自动将动画控制器中的参数切换到参数列表中下一个值,从而驱动动画状态的切换。为了让 DiscreteAnimatorDriver 在初始化的时候自动切换到参数列表中的第一个值,Reset on Start 选项被设置为 True。

erDiagram 玩家 }|--|| 离散动画驱动器 : 交互 离散动画驱动器 ||--|| 动画控制器 : 切换值 离散动画驱动器 { 字符串 参数名称 类型 参数类型 列表 参数列表 布尔 是否为全局 }

示例: Prefab/SimpleDiscreteStateRotateWithInteract.prefab

通过面板

为了通过面板来切换动画控制器参数,一个包含 UI 按钮的 Canvas 被创建,并且在按钮的 On Click 事件中添加了一个对 Driver 物件的 DiscreteAnimatorDriver 组件的 Interact 方法的调用。每当玩家点击该按钮时,动画控制器参数就会切换到 DiscreteAnimatorDriver 组件的参数列表中下一个值,从而驱动动画状态的切换。

erDiagram 玩家 }|--|{ 按钮 : 点击 按钮 }|--|| 离散动画驱动器 : 交互 离散动画驱动器 ||--|| 动画控制器 : 切换值 按钮 { 离散动画驱动器 触发目标 } 离散动画驱动器 { 字符串 参数名称 类型 参数类型 列表 参数列表 布尔 是否为全局 }

示例: Prefab/SimpleDiscreteStateRotateWithUI.prefab

通过拾取

动画控制器参数还可以通过拾取后使用来切换。为了实现这一功能,被抓取的 VRC_Pickup 组件所在的物件需要再附加一个 DiscreteAnimatorDriverPickupProxy (离散动画驱动器拾取代理) 组件,并将该组件的 Target 字段设置为上述 DiscreteAnimatorDriver 物件。当玩家抓取并使用该物件时,动画控制器参数就会切换到 DiscreteAnimatorDriver 组件的参数列表中下一个值,从而驱动动画状态的切换。

erDiagram 玩家 }|--|{ VRC_Pickup : 拾取并使用 VRC_Pickup ||--|| 离散动画驱动器拾取代理 : 触发 离散动画驱动器拾取代理 }|--|| 离散动画驱动器 : 触发 离散动画驱动器 ||--|| 动画控制器 : 切换值 离散动画驱动器拾取代理 { 离散动画驱动器 触发目标 } 离散动画驱动器 { 字符串 参数名称 类型 参数类型 列表 参数列表 布尔 是否为全局 }

示例: Prefab/SimpleDiscreteStateRotateWithPickup.prefab

离散参数选择

这一部分将说明 DiscreteAnimatorDriver 组件的另一个功能,即允许玩家直接选择动画控制器参数列表中的一个值来切换动画状态。

在这一部分中,DiscreteAnimatorDriver 组件的 Reset on Start 选项被设置为 False,以允许动画控制器参数在玩家交互前保持默认值。其余设置与上一部分相同。

通过交互

为了允许玩家选择不同的参数,三个具有 Collider 组件的物件被创建,并且每个物件都附加了一个 DiscreteAnimatorDriverIndexerInteractable (离散动画驱动器索引可交互) 组件,其 Target 字段被设置为上述 DiscreteAnimatorDriver 物件,Index (列表索引) 字段被设置为参数列表中的一个值(0、1、2)的索引。当玩家与其中一个物件进行交互时,动画控制器参数就会被设置为该物件对应的参数值,从而驱动动画状态的切换。

erDiagram 玩家 }|--|{ 离散动画驱动器索引可交互 : 交互 离散动画驱动器索引可交互 }|--|| 离散动画驱动器 : 设置索引 离散动画驱动器 ||--|| 动画控制器 : 设置值 离散动画驱动器索引可交互 { 离散动画驱动器 设置目标 列表索引 参数列表索引 } 离散动画驱动器 { 字符串 参数名称 类型 参数类型 列表 参数列表 布尔 是否为全局 }

示例: Prefab/SimpleDiscreteStateSeletctWithInteract.prefab

通过面板

为了通过面板来切换动画控制器参数,一个包含三个 UI 按钮的 Canvas 被创建。此外,还有三个索引物件被创建并附加了 DiscreteAnimatorDriverIndexerButtonController (离散动画驱动器索引按钮控制器) 组件,其 Target 字段被设置为上述 DiscreteAnimatorDriver 物件,Index 字段被设置为参数列表中的一个值(0、1、2)的索引,Buttons 字段被分布添加了上述三个按钮。当玩家点击其中一个按钮时,动画控制器参数就会被设置为该按钮对应的参数值,从而驱动动画状态的切换。

erDiagram 玩家 }|--|{ 按钮 : 点击 按钮 }|--|| 离散动画驱动器索引按钮控制器 : 触发 离散动画驱动器索引按钮控制器 }|--|| 离散动画驱动器 : 设置索引 离散动画驱动器 ||--|| 动画控制器 : 设置值 离散动画驱动器索引按钮控制器 { 离散动画驱动器 设置目标 列表索引 参数列表索引 按钮列表 关联按钮列表 } 离散动画驱动器 { 字符串 参数名称 类型 参数类型 列表 参数列表 布尔 是否为全局 }

示例: Prefab/SimpleDiscreteStateSelectWithUI.prefab

通过玩家移动

动画控制器参数还可以通过检测玩家移动进入不同的区域来切换。为了实现这一功能,三个具有 Collider 组件的物件被创建,并且每个物件都附加了一个 DiscreteAnimatorDriverIndexerTrigger (离散动画驱动器索引触发器) 组件,其 Target 字段被设置为上述 DiscreteAnimatorDriver 物件,Index 字段被设置为参数列表中的一个值(0、1、2)的索引。当玩家进入其中一个物件的碰撞区域时,动画控制器参数就会被设置为该物件对应的参数值,从而驱动动画状态的切换。

通常情况下,基于玩家移动的动画呈现与每个独立玩家的视角位置密切相关,因此玩家之间的动画不应该被同步。为了实现这一点,DiscreteAnimatorDriver 组件的 Global 字段被设置为 False,以允许动画控制器参数在每个玩家的本地实例中独立存在。

erDiagram 玩家 }|--|{ 离散动画驱动器索引触发器 : 进入 离散动画驱动器索引触发器 }|--|| 离散动画驱动器 : 设置索引 离散动画驱动器 ||--|| 动画控制器 : 设置值 离散动画驱动器索引触发器 { 离散动画驱动器 设置目标 列表索引 参数列表索引 } 离散动画驱动器 { 字符串 参数名称 类型 参数类型 列表 参数列表 布尔 是否为全局 }

示例: Prefab/SimpleDiscreteStateSelectWithTrigger.prefab

连续参数控制

这一部分将说明 ContinuousAnimatorDriverSliderController 组件的使用方法,该组件允许创作者通过 UI 滑动条来控制动画控制器的一个连续 (浮点) 参数。

在这一部分的示例中,一个包含一个状态和一个整型参数 Speed 的动画控制器将会被用来控制三棱柱的旋转状态,其中参数的范围为 0-1,参数值驱动旋转速度的快慢。动画控制器可在 Animation/ContinuousFloatController.controller 文件中找到。

为了驱动动画控制器中的参数,一个名为 Driver 的物件被创建,并用于附加 ContinuousAnimatorDriverSliderController (连续动画驱动器滑动条控制器) 组件。该组件的 Animator 字段被设置为上述动画控制器所在的物件,Parameter Name 字段被设置为上述参数的名称 Speed,Value 字段被设置为 0 作为初始值。Global 字段被设置为 True,以允许动画控制器参数在同一个实例的所有玩家间同步。

通过面板

为了通过面板来控制动画控制器参数,一个包含 UI 滑动条的 Canvas 被创建,并且被添加到了上述 ContinuousAnimatorDriverSliderController 组件的 Sliders 字段中。当玩家调整该滑动条时,动画控制器参数就会被设置为滑动条对应的值,从而驱动动画状态的切换。

erDiagram 玩家 }|--|{ 滑动条 : 调整 滑动条 }|--|| 连续动画驱动器滑动条控制器 : 设置并同步 连续动画驱动器滑动条控制器 ||--|| 动画控制器 : 设置值 连续动画驱动器滑动条控制器 { 字符串 参数名称 动画控制器 设置目标 参数名称 目标参数名称 滑动条列表 关联滑动条列表 布尔 是否为全局 }

示例: Prefab/SimpleContinuousRotateWithUI.prefab

附录