using System.Collections.Generic; using UnityEngine; using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets; namespace XRMultiplayer { /// /// Represents an anti-gravity zone in the game world. /// Objects and characters within the zone experience anti-gravity effects. /// public class AntiGravityZone : MonoBehaviour { [Header("Anti-Gravity Zone Settings")] /// /// The minimum range of float speeds for objects within the zone. /// [SerializeField] Vector2 m_ObjectFloatSpeedRangeMin = new(4.5f, 12.0f); /// /// The maximum range of float speeds for objects within the zone. /// [SerializeField] Vector2 m_ObjectFloatSpeedRangeMax = new Vector2(13.0f, 15.0f); /// /// The minimum range of float speeds for the player within the zone. /// [SerializeField] Vector2 m_PlayerFloatSpeedRangeMin = new Vector2(0, 0); /// /// The maximum range of float speeds for the player within the zone. /// [SerializeField] Vector2 m_PlayerFloatSpeedRangeMax = new Vector2(.025f, .075f); /// /// The range of float speeds for the particle system. /// [SerializeField] Vector2 m_ParticleFloatSpeedMinMax = new Vector2(0, 3); /// /// The maximum velocity for objects within the zone. /// [SerializeField] float m_MaxObjectVelocityThreshold = 4.0f; /// /// The particle system for the zone. /// [SerializeField] ParticleSystem[] m_Particles; /// /// The collider for the zone. /// [SerializeField] SubTrigger m_AntiGravitySubTrigger; /// /// The renderer for the zone. /// [SerializeField] Renderer m_Renderer; [Header("Powered Settings")] /// /// The renderers for the zone that get colored when powered on. /// [SerializeField] Renderer[] m_PoweredOnRends; /// /// The audio source for the zone. /// [SerializeField] AudioSource m_PoweredOnAudio; /// /// The material for the zone when powered on. /// [SerializeField] Material m_PoweredOnMaterial; /// /// The material for the zone when powered off. /// [SerializeField] Material m_PoweredOffMaterial; [Header("Black Hole Settings")] [SerializeField] GameObject[] m_BlackHoleObjects; float currentSpeed; /// /// The float speed range for objects within the zone. /// Vector2 m_FloatSpeedRange; /// /// The float speed range for the player within the zone. /// Vector2 m_PlayerFloatSpeedRange; /// /// The list of rigidbodies within the zone. /// CharacterController m_CharacterController; /// /// The list of rigidbodies within the zone. /// List m_RigidbodyList = new List(); bool m_IsBlackHole = false; bool m_IsPowered = false; /// private void Awake() { PowerOff(); UpdateSpeed(0); m_AntiGravitySubTrigger.OnTriggerAction += SubTriggered; ToggleBlackHoles(false); } /// void Update() { if (m_IsBlackHole) return; if (m_CharacterController != null) { m_CharacterController.transform.position += Vector3.up * Random.Range(m_PlayerFloatSpeedRange.x, m_PlayerFloatSpeedRange.y); } } /// void FixedUpdate() { if (m_IsBlackHole) return; foreach (var rigidbody in m_RigidbodyList) { if (rigidbody == null) continue; if (!rigidbody.isKinematic) { // If the rigidbody is not kinematic and it's Y velocity is under the threshold, apply a force to simulate floating. if (rigidbody.linearVelocity.y < m_MaxObjectVelocityThreshold) { currentSpeed = Random.Range(m_FloatSpeedRange.x, m_FloatSpeedRange.y); rigidbody.AddForce(Vector3.up * currentSpeed); } } } } /// /// Turns off the anti-gravity zone. /// /// Called from the Socket Interactor. public void PowerOff() { m_IsPowered = false; m_AntiGravitySubTrigger.subTriggerCollider.enabled = false; m_Renderer.enabled = false; m_PoweredOnAudio.enabled = false; m_RigidbodyList.Clear(); foreach (var p in m_Particles) { p.Stop(false, ParticleSystemStopBehavior.StopEmitting); } foreach (Renderer r in m_PoweredOnRends) { if (r == null) continue; r.material = m_PoweredOffMaterial; } if (m_CharacterController != null) { m_CharacterController.GetComponentInChildren().useGravity = true; m_CharacterController = null; } ToggleBlackHoles(false); } /// /// Turns on the anti-gravity zone. /// /// Called from the Socket Interactor. public void PowerOn() { m_IsPowered = true; m_PoweredOnAudio.enabled = true; foreach (Renderer r in m_PoweredOnRends) { r.material = m_PoweredOnMaterial; } if (m_IsBlackHole) { m_Renderer.enabled = false; ToggleBlackHoles(true); } else { m_Renderer.enabled = true; m_AntiGravitySubTrigger.subTriggerCollider.enabled = true; foreach (var p in m_Particles) { p.Play(); } } } /// /// Updates the speed of the anti-gravity effects based on the given value. /// /// The value used to interpolate the speed range. public void UpdateSpeed(float value) { m_FloatSpeedRange = new Vector2(Mathf.Lerp(m_ObjectFloatSpeedRangeMin.x, m_ObjectFloatSpeedRangeMax.x, value), Mathf.Lerp(m_ObjectFloatSpeedRangeMin.y, m_ObjectFloatSpeedRangeMax.y, value)); m_PlayerFloatSpeedRange = new Vector2(Mathf.Lerp(m_PlayerFloatSpeedRangeMin.x, m_PlayerFloatSpeedRangeMax.x, value), Mathf.Lerp(m_PlayerFloatSpeedRangeMin.y, m_PlayerFloatSpeedRangeMax.y, value)); for (int i = 0; i < m_Particles.Length; i++) { var main = m_Particles[i].main; main.startSpeed = Mathf.Lerp(m_ParticleFloatSpeedMinMax.x / (i + 1), m_ParticleFloatSpeedMinMax.y / (i + 1), value); } } /// /// Callback for the SubTrigger action. /// /// /// void SubTriggered(Collider other, bool entered) { if (entered) { SubTriggerEntered(other); } else { SubTriggerExited(other); } } /// /// Callback for the SubTrigger OnTriggerEnter. /// /// private void SubTriggerEntered(Collider other) { Rigidbody body = other.GetComponentInParent(); if (body != null) { if (m_RigidbodyList.Contains(body)) return; // If the object is an interactable and it throws on detach, add it to the list. if (body.TryGetComponent(out UnityEngine.XR.Interaction.Toolkit.Interactables.XRGrabInteractable interactable)) { if (interactable.throwOnDetach) { m_RigidbodyList.Add(body); } } else { m_RigidbodyList.Add(body); } } else { // If the object is a character controller, add it to the list. if (other.TryGetComponent(out CharacterController controller)) { m_CharacterController = controller; m_CharacterController.GetComponentInChildren().useGravity = false; } } } /// /// Callback for the SubTrigger OnTriggerExit. /// /// private void SubTriggerExited(Collider other) { Rigidbody body = other.GetComponentInParent(); if (body != null) { if (m_RigidbodyList.Contains(body)) { m_RigidbodyList.Remove(body); } } else { if (other.TryGetComponent(out CharacterController controller)) { controller.GetComponentInChildren().useGravity = true; m_CharacterController = null; } } } /// /// Toggles the black hole effect. /// This is used to destroy objects within the zone. /// /// public void ToggleBlackHole(bool toggle) { m_IsBlackHole = toggle; if (m_IsBlackHole) { m_Renderer.enabled = false; if (m_IsPowered) { m_AntiGravitySubTrigger.subTriggerCollider.enabled = false; foreach (var p in m_Particles) { p.Stop(); } ToggleBlackHoles(true); } } else { if (m_IsPowered) { m_Renderer.enabled = true; m_AntiGravitySubTrigger.subTriggerCollider.enabled = true; foreach (var p in m_Particles) { p.Play(); } ToggleBlackHoles(false); } } } void ToggleBlackHoles(bool toggle) { foreach (GameObject g in m_BlackHoleObjects) { g.SetActive(toggle); } } } }