Skip to main content

VRCTween

VRCTween 允许您使用强大的 DOTween 库在 VRChat 世界中创建平滑动画。您可以用几行代码动画化位置、旋转、缩放等。

概述​

VRCTween 是一个内置的补间动画系统,随时间平滑插值(或"补间")值。无需逐帧手动更新位置或旋转,VRCTween 为您处理数学计算和时序。

常见用例:

  • 动画化 UI 元素,如按钮、面板和菜单。
  • 创建平滑的对象移动。
  • 为世界增添精致感。

VRCTween 使用 DOTween,一个流行的 Unity 补间库,并与 UdonSharp 和 Udon Graph 配合使用。

基本用法​

创建补间​

使用目标类型上的扩展方法或 VRCTween 上的静态方法创建补间。每个方法返回一个可用于控制和配置补间的 VRCTweenHandle:

using VRC.SDK3.Components;


public class MyScript : UdonSharpBehaviour
{
public GameObject cube;

void Start()
{
// Move the cube up over 2 seconds.
VRCTweenHandle tweenHandle = cube.TweenPosition(new Vector3(0, 5, 0), 2f, VRCTweenEase.OutQuad);
}
}

接收回调​

要在补间完成时收到通知,在句柄上链式调用 .OnComplete():

public class MyScript : UdonSharpBehaviour
{
void Start()
{
gameObject.TweenScale(Vector3.one * 2f, 1f, VRCTweenEase.OutBounce)
.OnComplete(this, nameof(OnScaleComplete));
}

public void OnScaleComplete()
{
Debug.Log("Scale animation complete!");
}
}

补间类型​

补间创建方法可作为目标类型上的扩展方法(例如 gameObject.TweenPosition(...))或 VRCTween 上的静态方法使用。所有方法都返回一个 VRCTweenHandle。

  • Transform:TweenPosition、TweenLocalPosition、TweenRotation、TweenLocalRotation、TweenScale
  • Path:TweenPath、TweenLocalPath
  • UI:TweenColor、TweenFade(Graphic)、TweenFade(CanvasGroup)、TweenValue(Slider)、TweenAnchorPos、TweenSizeDelta
  • Sprite:TweenColor、TweenFade(SpriteRenderer)
  • Renderer:TweenColor、TweenFloat(Renderer)
  • Light:TweenIntensity、TweenColor(Light)
  • Audio:TweenVolume、TweenPitch(AudioSource)

有关详细代码示例,请参见内置补间类型。

虚拟补间​

虚拟补间通过每帧写入 UdonBehaviour 上的变量来动画化任意值(float、int、Color、Vector3)。当没有内置补间类型满足您的需求时使用它们。详情和示例请参见虚拟补间。

控制和配置补间​

使用 VRCTweenHandle 上的实例方法控制和配置补间。配置方法返回句柄以支持链式调用:

cube.TweenPosition(new Vector3(0, 5, 0), 2f, VRCTweenEase.OutQuad)
.From()
.SetDelay(0.5f)
.SetLoops(2, VRCTweenLoopType.Yoyo)
.OnComplete(this, nameof(OnDone));

有关完整的 API 参考和缓动类型表,请参见设置和控制。

示例​

此示例演示了句柄存储、回调和清理:

using UdonSharp;
using UnityEngine;
using VRC.SDK3.Components;


public class TweenExample : UdonSharpBehaviour
{
public GameObject button;
private VRCTweenHandle scaleTween;

public override void Interact()
{
// Scale up and down when clicked.
scaleTween = button.TweenScale(Vector3.one * 1.2f, 0.15f, VRCTweenEase.OutQuad)
.OnComplete(this, nameof(OnTweenComplete));
}

public void OnTweenComplete()
{
// Scale back down.
button.TweenScale(Vector3.one, 0.15f, VRCTweenEase.OutQuad);
}

void OnDestroy()
{
// Clean up tweens when object is destroyed.
button.KillAllTweens();
}
}

最佳实践​

存储补间句柄​

如果需要稍后控制补间,请保存句柄:

private VRCTweenHandle myTween;

void Start()
{
myTween = gameObject.TweenPosition(Vector3.up, 2f, VRCTweenEase.OutQuad);
}

public void StopTween()
{
myTween.Kill();
}

清理补间​

补间在完成时会自动清理。但是,仍在运行的补间(例如无限循环或长时补间)应在对象销毁时停止:

void OnDestroy()
{
gameObject.KillAllTweens();
}

链式补间与回调​

通过使用回调和自定义事件名称创建序列:

void Start()
{
cube.TweenPosition(Vector3.up * 5, 1f, VRCTweenEase.OutQuad)
.OnComplete(this, nameof(OnFirstTweenComplete));
}

public void OnFirstTweenComplete()
{
cube.TweenRotation(new Vector3(0, 180, 0), 1f, VRCTweenEase.InOutQuad)
.OnComplete(this, nameof(OnSecondTweenComplete));
}

public void OnSecondTweenComplete()
{
Debug.Log("Sequence complete!");
}

使用 DelayedCall 创建可取消的计时器​

VRCTween.DelayedCall 是 SendCustomEventDelayedSeconds 的可取消替代方案:

private VRCTweenHandle timerHandle;

void Start()
{
// Schedule a delayed callback.
timerHandle = VRCTween.DelayedCall(this, nameof(OnTimerFinished), 3.0f);
}

public void CancelTimer()
{
// Cancel the timer at any time.
timerHandle.Kill();
}

public void OnTimerFinished()
{
Debug.Log("3 seconds have passed!");
}

Udon Graph​

VRCTween 可以在 Udon Graph 中使用。在节点搜索中查找 VRCTween 和 VRCTweenHandle 节点:

  1. 搜索"VRCTween TweenPosition"(或任何创建方法)来创建补间。
  2. 连接你的 GameObject 和参数。
  3. 节点会输出一个 VRCTweenHandle。
  4. 使用 VRCTweenHandle 节点(Kill、Pause、SetDelay、OnComplete 等)来控制和配置它。

对于回调,使用 VRCTweenHandle OnComplete 节点:

  • 连接补间句柄和你的 UdonBehaviour(通常是"this")。
  • 提供一个自定义事件名称字符串。
  • 在你的图表中实现相应的自定义事件。

限制​

  • 补间对每个玩家是本地的,不会自动跨网络同步。
  • 对于需要网络同步的动画,考虑结合使用 Udon 的网络功能和补间。
  • 非常短的补间持续时间(少于 0.01 秒)可能动画不平滑。
  • 补间句柄在每个场景实例中是唯一的。

输入验证​

VRCTween 会拒绝会破坏 DOTween 状态或 Unity Transform 的值。拒绝不会引发异常,以避免导致包含的 UdonBehavior 崩溃,因此一个"什么都没做"的补间通常意味着以下输入之一无效。

创建方法在以下情况下返回无效句柄(并跳过创建补间):

  • target 为 null。
  • duration 为负数、NaN 或 Infinity。零是允许的,用于创建可复用的补间。
  • 位置、缩放或路径航点包含 NaN、Infinity,或绝对值超过约 520,000 单位的组件。旋转补间仅需要有限值;允许较大的欧拉角。
  • 路径的 resolution 会被静默限制在 1–50 范围内;超出该范围的值仍会创建补间,只是分辨率会被限制。

配置和控制方法在给定无效参数时静默无操作:

  • SetDuration 忽略负数、NaN 或 Infinity 值。零是允许的。
  • SetDelay 忽略负数、NaN 或 Infinity 值。
  • Goto 忽略 NaN 或 Infinity,否则限制在补间的持续时间内。
  • ChangeEndValue 忽略在创建时未通过相同有限/幅度检查的 float/vector。

如果需要在补间是否实际启动时进行分支判断,请在创建后检查 handle.IsValid。

性能提示​

一般提示​

  • 使用 KillAllTweens() 一次性清理对象上的所有补间。
  • 避免同时创建数百个补间。将它们分散到多个帧中以分摊负载(见下方示例)。
  • 对于大多数世界(5 到 50 个通过交互触发的补间),创建新补间完全没问题。DOTween 内部池化补间对象,因此开销很小。

错开大量补间​

如果你需要为大量对象(如一网格的瓦片)制作动画,请避免在同一帧中创建所有补间。相反,将它们随时间分散,每个之间使用小延迟:

[SerializeField] private GameObject[] tiles;
[SerializeField] private float delayBetween = 0.05f;

private int _nextIndex;

public void AnimateAll()
{
_nextIndex = 0;
_AnimateNext();
}

public void _AnimateNext()
{
if (_nextIndex >= tiles.Length) return;

tiles[_nextIndex].TweenScale(Vector3.one, 0.3f, VRCTweenEase.OutBack);
_nextIndex++;

SendCustomEventDelayedSeconds(nameof(_AnimateNext), delayBetween);
}

这样每次调用创建一个补间,将工作分散到多个帧中。视觉效果是错开的级联动画,而非所有内容同时变化——这反而可能看起来更有趣!

在热路径中复用补间​

如果你有频繁重定向的补间,例如跟随移动目标的 UI 元素,或响应每帧玩家输入的对象,使用 ChangeEndValue、SetDuration 和 SetEase 复用句柄可以避免每帧分配。在 300 帧内更新 500 个补间的内部基准测试中,复用模式分配的内存比销毁重建模式少 46 倍,运行速度快 10 倍。

这种优化在补间以高频率创建和销毁时很重要。对于一次性动画(门、按钮、缩放弹跳),简单的销毁重建代码更易读且工作良好。

VRCTweenHandle _moveHandle;

void Start()
{
// Create once with infinite loops so it stays alive after completing.
_moveHandle = gameObject.TweenPosition(Vector3.zero, 1f, VRCTweenEase.OutQuad)
.SetLoops(-1, VRCTweenLoopType.Restart)
.Pause();
}

public void MoveTo(Vector3 target, float duration)
{
// Reconfigure and restart without allocating.
_moveHandle.ChangeEndValue(target, true)
.SetDuration(duration)
.SetEase(VRCTweenEase.OutCubic);
_moveHandle.Restart();
}

更多详情请参见设置与控制中的补间复用。

资源​