// SprayCan.cs // A grabbable spray paint can, one script per can (any colour). Whoever holds it gets a Spray button (on screen, the F // key on desktop, the right stick click in VR); while it is held down the can hisses, its spray particles run, and it // sends the paint to whatever wall the player aims at. Walls are separate objects with the SprayWall script: every can // paints every SprayWall, and a scene can have as many walls as it likes. // // Set up: make the can Grabbable (Aim With Camera on, the default), then slot its Particles (a particle system, a child // of the can) and its SpraySound (an object with an Audio Source). Pick its PaintColor. using IHXR.Simulation; public class SprayCan : ScriptBase { protected override bool SyncTransformStateByDefault => false; [Tooltip("This can's paint colour.")] public Color PaintColor = new Color(0.9f, 0.1f, 0.1f); [Tooltip("Spray width in metres right at the nozzle.")] public float NozzleSize = 0.03f; [Tooltip("How much wider the spray gets per metre from the wall.")] public float SpreadPerMetre = 0.18f; [Tooltip("No paint reaches a wall from further away than this (metres).")] public float MaxDistance = 2.5f; [Tooltip("The spray particle system (a child of the can); it runs while spraying.")] public SceneObject Particles; [Tooltip("An object with an Audio Source: plays over and over while spraying.")] public SceneObject SpraySound; [Tooltip("The button's label, desktop key and VR button.")] public string ButtonLabel = "Spray"; public string ButtonKey = "F"; public VRButton ButtonVR = VRButton.RightStickClick; [Tooltip("Writes what each spray hits to the script log (to check a wall is being hit).")] public bool DebugMode = false; private string holder = ""; private bool spraying; public override void Start() { if (IsValid(Particles)) SetParticlesPlaying(Particles, false); ListenForGrab(OnGrab); ListenForActionButton("spray", OnSpray); holder = GetHolder(); if (holder.Length > 0) ShowActionButton(holder, "spray", ButtonLabel, ButtonKey, ButtonVR); } private bool IsValid(SceneObject o) => o != null && !o.IsNull; // Picked up: the holder gets the Spray button. Let go: it goes, and any spraying stops. private void OnGrab(GrabInfo info) { if (info.IsGrabbed) { if (holder.Length > 0 && holder != info.Username) HideActionButton(holder, "spray"); holder = info.Username; ShowActionButton(holder, "spray", ButtonLabel, ButtonKey, ButtonVR); } else if (info.Username == holder) { HideActionButton(holder, "spray"); holder = ""; SetSpraying(false); } } // While the button is down: paint goes to the wall that is hit (the SprayWall there decides what to do with it). private void OnSpray(ActionButtonInfo info) { if (info.Username != holder) { if (DebugMode) Log("SprayCan: " + info.Username + " pressed Spray but " + (holder.Length > 0 ? holder : "nobody") + " holds the can"); return; } SetSpraying(info.IsDown); if (!info.IsDown) return; if (DebugMode) Log("SprayCan: " + info.State + (info.HasHit ? " hit " + string.Join(" / ", info.HitObjectIds) + (info.HasUV ? " at " + info.HitUV.x.ToString("0.00") + ", " + info.HitUV.y.ToString("0.00") : " (no texture position)") + ", " + info.HitDistance.ToString("0.00") + " m" : " (aiming at nothing)")); if (!info.HasHit || !info.HasUV) return; float distance = info.HitDistance < 0 ? MaxDistance : info.HitDistance; if (distance > MaxDistance) return; PostScriptEvent("SprayPaint", "objectId", info.HitObjectId, "rootObjectId", info.HitRootObjectId, "objectIds", string.Join("|", info.HitObjectIds), "material", info.HitMaterialIndex, "u", info.HitUV.x, "v", info.HitUV.y, "distance", distance, "maxDistance", MaxDistance, "nozzle", NozzleSize, "spread", SpreadPerMetre, "r", PaintColor.r, "g", PaintColor.g, "b", PaintColor.b, "sprayer", holder + "/" + Self.ObjectId); } private void SetSpraying(bool on) { if (spraying == on) return; spraying = on; if (IsValid(Particles)) SetParticlesPlaying(Particles, on); if (IsValid(SpraySound)) { if (on) PlayAudioLoop(SpraySound); else StopAudio(SpraySound); } } }