// BlendShapeControl.cs // Drives a blend shape (morph target) of a scene model from buttons in the world: play it, stop it, switch looping on // and off, make it faster or slower, and step its resting value. Everybody in the world sees the same thing at the // same moment, and players who join later see it as it is now (the instance server keeps the state). // // Set up: // 1. Put a model with blend shapes in your scene. Its Inspector lists them under "Blend Shapes" (the names to use here), // where you can also give each one a resting value, Auto play, Loop and Seconds. // 2. Add this script to any object. Drag the model into Target (empty = the object this script is on) and type the // shape's name into ShapeName ("*" = every blend shape of the model). // 3. Drag up to five objects into the button slots (any you leave empty are simply not used). using IHXR.Simulation; public class BlendShapeControl : ScriptBase { [Header("The blend shape")] [Tooltip("The model with the blend shape. Empty = the object this script is on.")] public SceneObject Target; [Tooltip("The blend shape's name as the Inspector lists it, e.g. Smile. \"*\" = all of the model's blend shapes.")] public string ShapeName = "Smile"; [Tooltip("Seconds for one cycle (out to the far end and back) when it plays.")] public float Seconds = 1.5f; [Tooltip("Keep playing (true) or do one cycle and stop (false).")] public bool Loop = true; [Header("Buttons (optional)")] public SceneObject PlayButton; public SceneObject StopButton; public SceneObject LoopButton; public SceneObject FasterButton; public SceneObject ValueButton; float value; // the resting value ValueButton steps through: 0, 0.25, 0.5, 0.75, 1, 0 … SceneObject Model => Target != null && !string.IsNullOrEmpty(Target.ObjectId) ? Target : Self; static bool Has(SceneObject o) => o != null && !string.IsNullOrEmpty(o.ObjectId); public override void Start() { if (Has(PlayButton)) MakeInteractable(PlayButton, _ => Play(), true, hintText: "Play " + ShapeName); if (Has(StopButton)) MakeInteractable(StopButton, _ => StopBlendShape(Model, ShapeName), true, hintText: "Stop " + ShapeName); if (Has(LoopButton)) MakeInteractable(LoopButton, ToggleLoop, true, hintText: "Loop on / off"); if (Has(FasterButton)) MakeInteractable(FasterButton, Faster, true, hintText: "Faster"); if (Has(ValueButton)) MakeInteractable(ValueButton, StepValue, true, hintText: "Change the resting value"); } void Play() { // loop and seconds are optional: left out, the shape keeps what it has (its Inspector settings or an earlier call). PlayBlendShape(Model, ShapeName, Loop, Seconds); Log($"{ShapeName}: playing, {Seconds:0.##} s per cycle, loop {(Loop ? "on" : "off")}"); } void ToggleLoop(InteractionInfo info) { Loop = !Loop; // Switching the loop off while it plays lets the current cycle finish, then it rests at its value. SetBlendShapeLoop(Model, ShapeName, Loop); ShowToast(info.Username, null, "Loop " + (Loop ? "on" : "off")); // only the player who pressed it sees this } void Faster(InteractionInfo info) { // 1.5 s → 1 s → 0.66 s … then back to slow. Changing the speed while it plays keeps it where it is in its cycle. Seconds = Seconds <= 0.3f ? 2f : Seconds / 1.5f; SetBlendShapeSeconds(Model, ShapeName, Seconds); ShowToast(info.Username, null, $"{Seconds:0.##} s per cycle"); } void StepValue(InteractionInfo info) { value = value >= 1f ? 0f : value + 0.25f; // The resting weight (0..1): where the shape sits when it is not playing, and where a cycle starts and ends. SetBlendShape(Model, ShapeName, value); ShowToast(info.Username, null, $"{ShapeName} at {value * 100:0}%"); } }