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).
Scene setup
Section titled “Scene setup”- One GameObject holding the
TimedAnimatorscript. Targetfield either empty (defaults to the script’s own transform) or wired to a child Transform of your content root.CCKWasmProjectDescriptoron 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.
Permission model
Section titled “Permission model”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 intoTargetwill throwWasmAccessDeniedExceptionat the first assignment.Time.time,Mathf.Sin,Vector3.normalized— all(Any, Any, Any).
Why store originLocalPosition
Section titled “Why store originLocalPosition”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.
Extensions to try
Section titled “Extensions to try”- Swap
axis = Vector3.upforaxis = new Vector3(1f, 0f, 1f)to move diagonally. - Replace
target.localPositionwrites with rotation:target.localRotation = Quaternion.Euler(0f, offset * 90f, 0f). - Add
[Range(0.1f, 5f)]tofrequencyHzfor a nicer inspector slider (Unity attribute — honored in editor, has no WASM runtime effect). - Bind
SetAmplitude(float)to a UI slider to tune live.
Related
Section titled “Related”- Permissions — why Self scope matters for writes.
- Serialization — why we need
[WasmSerialized]on the private fields.