Skip to content

03 — Timed Animator

TL;DR: Moves a Transform along a sine wave around its starting position. Amplitude, frequency, and axis are public fields; Play/Stop/SetAmplitude/SetFrequency are public methods you can bind to UI buttons, triggers, or call from other scripts. Works in any ObjectContext, though writing transform position requires the target to be inside your content root (ScopeContext.Self).

  • One GameObject holding the TimedAnimator script.
  • Target field either empty (defaults to the script’s own transform) or wired to a child Transform of your content root.
  • CCKWasmProjectDescriptor on the content root.
using UnityEngine;
using WasmScripting;
public partial class TimedAnimator : WasmBehaviour
{
public Transform target;
public Vector3 axis = Vector3.up;
public float amplitude = 1.0f;
public float frequencyHz = 0.5f;
public bool autoStart = true;
[WasmSerialized] private Vector3 originLocalPosition;
[WasmSerialized] private Vector3 cachedAxis;
[WasmSerialized] private bool running;
[WasmSerialized] private float startTime;
void Start()
{
if (target == null) target = transform;
originLocalPosition = target.localPosition;
cachedAxis = axis.normalized;
if (autoStart) Play();
}
void Update()
{
if (!running || target == null) return;
float t = Time.time - startTime;
float offset = Mathf.Sin(t * frequencyHz * 2f * Mathf.PI) * amplitude;
target.localPosition = originLocalPosition + cachedAxis * offset;
}
[ExternallyVisible]
public void Play()
{
if (running) return;
cachedAxis = axis.normalized;
running = true;
startTime = Time.time;
}
[ExternallyVisible]
public void Stop()
{
running = false;
if (target != null) target.localPosition = originLocalPosition;
}
[ExternallyVisible] public void SetAmplitude(float a) => amplitude = Mathf.Max(0f, a);
[ExternallyVisible] public void SetFrequency(float hz) => frequencyHz = Mathf.Max(0.01f, hz);
}

The cachedAxis field avoids a per-frame sqrt inside axis.normalized. axis is a public field so the inspector can still tune it — changes take effect on the next Play() or force-refresh by calling Play() again.

Source file: examples/03_TimedAnimator.cs.

  • Transform.get_localPosition(Any, Any, Any), safe.
  • Transform.set_localPosition(Any, Any, Self). The binding only lets you write a transform that lives inside your content root. Dragging an arbitrary world transform into Target will throw WasmAccessDeniedException at the first assignment.
  • Time.time, Mathf.Sin, Vector3.normalized — all (Any, Any, Any).

Without it, after a pause/resume the animator would snap back to (0,0,0) in local space when computing originLocalPosition + offset. Storing the starting position preserves the author’s editor placement.

Because the field is private and flagged [WasmSerialized], it survives from the initial capture (during the first Start) into the next deserialize cycle. Without the attribute, it would be re-read from the transform and confused if the transform had already moved.

  • Swap axis = Vector3.up for axis = new Vector3(1f, 0f, 1f) to move diagonally.
  • Replace target.localPosition writes with rotation: target.localRotation = Quaternion.Euler(0f, offset * 90f, 0f).
  • Add [Range(0.1f, 5f)] to frequencyHz for a nicer inspector slider (Unity attribute — honored in editor, has no WASM runtime effect).
  • Bind SetAmplitude(float) to a UI slider to tune live.
  • Permissions — why Self scope matters for writes.
  • Serialization — why we need [WasmSerialized] on the private fields.