using UdonSharp; using UnityEngine; using VRC.SDKBase; [UdonBehaviourSyncMode(BehaviourSyncMode.NoVariableSync)] [DisallowMultipleComponent] [AddComponentMenu("Artyom Scripting System/Laser Receiver Controller")] public class LaserReceiver : UdonSharpBehaviour { public GameObject targetObject; // The object to turn on/off public int laserLayer = 22; // Layer number to detect the laser public Animator[] animators; // Array to hold Animators public string[] parameterNames; // Array to hold corresponding parameter names public GameObject audioObjectTrue; // GameObject to enable when the state changes to true public GameObject audioObjectFalse; // GameObject to enable when the state changes to false public GameObject audioObjectLoop; // GameObject to loop while the state is true public Collider triggerCollider; // Reference to the trigger collider (this object's collider if empty) public Renderer targetRenderer; // Renderer to change material public Material materialTrue; // Material to apply when the state is true public Material materialFalse; // Material to apply when the state is false public int materialIndex = 0; // Index of the material to change [Tooltip("Log each change of the number of lasers inside")] public bool debugMode; private int colliderCount = 0; // Counter for colliders with the laser layer private Collider[] collidersInsideTrigger = new Collider[16]; // Reused every frame private BoxCollider box; // Set when the trigger is a box: checked exactly, rotation included private void Start() { if (triggerCollider == null) triggerCollider = GetComponent(); if (triggerCollider == null) { Debug.LogError("[AUMSS] Laser Receiver Controller: no Trigger Collider assigned, and none on this object."); enabled = false; // Stops Update, which would otherwise throw and halt the script return; } box = triggerCollider.GetComponent(); if (box != triggerCollider) box = null; // Another collider on the same object: fall back to the bounds // Ensure the target object is initially off if (targetObject != null) { targetObject.SetActive(false); } // Ensure the looping audio is initially off if (audioObjectLoop != null) { audioObjectLoop.SetActive(false); } // Initial check for colliders inside the trigger UpdateColliderCount(); } private void Update() { if (triggerCollider == null) return; // Not set up (enabled = false only takes effect next frame) // Continuously check for colliders inside the trigger UpdateColliderCount(); } private void UpdateColliderCount() { Vector3 boxCenter; Vector3 boxHalfExtents; Quaternion boxRotation; if (box != null) { Transform boxTransform = box.transform; boxCenter = boxTransform.TransformPoint(box.center); boxHalfExtents = Vector3.Scale(box.size, boxTransform.lossyScale) * 0.5f; boxHalfExtents = new Vector3(Mathf.Abs(boxHalfExtents.x), Mathf.Abs(boxHalfExtents.y), Mathf.Abs(boxHalfExtents.z)); boxRotation = boxTransform.rotation; } else { // Other collider shapes: the world-space box around them boxCenter = triggerCollider.bounds.center; boxHalfExtents = triggerCollider.bounds.extents; boxRotation = Quaternion.identity; } int found = Physics.OverlapBoxNonAlloc(boxCenter, boxHalfExtents, collidersInsideTrigger, boxRotation, 1 << laserLayer, QueryTriggerInteraction.UseGlobal); // Update the collider count based on the result int newColliderCount = 0; for (int i = 0; i < found; i++) { if (collidersInsideTrigger[i].GetComponent() == null) { newColliderCount++; } } if (newColliderCount != colliderCount) { colliderCount = newColliderCount; if (debugMode) Debug.Log("[AUMSS] Laser Receiver Controller: lasers inside: " + colliderCount); // Turn on or off the target object based on the collider count bool on = colliderCount > 0; if (targetObject != null) { targetObject.SetActive(on); } UpdateAnims(on); } } private void UpdateAnims(bool state) { int count = Mathf.Min(animators.Length, parameterNames.Length); for (int i = 0; i < count; i++) { if (animators[i] != null && !string.IsNullOrEmpty(parameterNames[i])) { animators[i].SetBool(parameterNames[i], state); } } // Enable or disable GameObjects based on the state if (state) { if (audioObjectFalse != null) audioObjectFalse.SetActive(false); if (audioObjectTrue != null) audioObjectTrue.SetActive(true); if (audioObjectLoop != null) audioObjectLoop.SetActive(true); // Enable looping audio if (targetRenderer != null && materialTrue != null) SetMaterial(materialTrue); // Change material to true } else { if (audioObjectTrue != null) audioObjectTrue.SetActive(false); if (audioObjectFalse != null) audioObjectFalse.SetActive(true); if (audioObjectLoop != null) audioObjectLoop.SetActive(false); // Disable looping audio if (targetRenderer != null && materialFalse != null) SetMaterial(materialFalse); // Change material to false } } private void SetMaterial(Material material) { // sharedMaterials: .materials would make a new copy of every material on each change Material[] materials = targetRenderer.sharedMaterials; if (materialIndex >= 0 && materialIndex < materials.Length) { materials[materialIndex] = material; targetRenderer.sharedMaterials = materials; } } }