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);
}
}
}
}