projectEli/Assets/NeoFPS/Core/Gameplay/SpawnPoint.cs

324 lines
9.9 KiB
C#
Raw Normal View History

2022-11-07 01:28:33 +00:00
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using NeoSaveGames.Serialization;
using NeoCC;
namespace NeoFPS
{
[HelpURL("https://docs.neofps.com/manual/fpcharactersref-mb-spawnpoint.html")]
public class SpawnPoint : MonoBehaviour
{
[SerializeField, Tooltip("Should the spawn point be registered immediately on awake? It can be hard to enforce an order of registration for multiple spawn points with this.")]
private bool m_RegisterOnAwake = true;
[SerializeField, Tooltip("How long before the spawn point can be used again.")]
private float m_ReuseDelay = 0f;
[SerializeField, Tooltip("The collider volume type for checking if the spawn point is clear or overlapped by another object.")]
private OverlapTest m_OverlapTest = OverlapTest.Box;
[SerializeField, Tooltip("The vertical height of the bounding volume for overlap checks.")]
private float m_BoundsHeight = 2.5f;
[SerializeField, Tooltip("The horizontal dimension of the bounding volume for overlap checks.")]
private float m_BoundsHorizontal = 1.2f;
[SerializeField, Tooltip("Should the character's gravity be reoriented to match the spawn point. If the spawn is tilted on one side, this will make the character's down direction equal to the spawn point's")]
private bool m_ReorientGravity = false;
[SerializeField, Tooltip("A UnityEvent fired when a character is spawned at this point. Allows for simple triggering of spawn audio and visual effects.")]
private UnityEvent m_OnSpawn = new UnityEvent();
#if UNITY_EDITOR
public OrderedSpawnPointGroup group = null;
#endif
private const float k_Tolerance = 0.005f;
private Coroutine m_CooldownCoroutine = null;
private WaitForSeconds m_ReuseYield = null;
public enum OverlapTest
{
Box,
Capsule,
None
}
private Transform m_SpawnTransform = null;
public Transform spawnTransform
{
get
{
if (m_SpawnTransform == null)
m_SpawnTransform = transform;
return m_SpawnTransform;
}
}
public event UnityAction onSpawn
{
add { m_OnSpawn.AddListener (value); }
remove { m_OnSpawn.RemoveListener (value); }
}
public bool cooldownActive
{
get { return m_CooldownCoroutine != null; }
}
private bool m_Registered = false;
public bool registered
{
get { return m_Registered; }
}
public Vector3 up
{
get
{
if (m_ReorientGravity)
return spawnTransform.up;
else
return Vector3.up;
}
}
public Quaternion rotation
{
get
{
if (m_ReorientGravity)
return spawnTransform.rotation;
else
{
var spawnForward = Vector3.ProjectOnPlane(spawnTransform.forward, Vector3.up);
if (spawnForward.sqrMagnitude < 0.0001f)
spawnForward = Vector3.forward;
else
spawnForward.Normalize();
return Quaternion.LookRotation(spawnForward);
//return Quaternion.Euler(0f, spawnTransform.rotation.y, 0f);
}
}
}
#if UNITY_EDITOR
void OnValidate()
{
m_BoundsHeight = Mathf.Clamp(m_BoundsHeight, 1f, 5f);
m_BoundsHorizontal = Mathf.Clamp(m_BoundsHorizontal, 0.5f, 5f);
if (m_ReuseDelay < 0f)
m_ReuseDelay = 0f;
// Check group settings
if (group != null)
{
bool found = false;
for (int i = 0; i < group.spawnPoints.Length; ++i)
{
if (group.spawnPoints[i] == this)
{
found = true;
break;
}
}
if (found)
m_RegisterOnAwake = false;
else
group = null;
}
}
#endif
void Awake ()
{
if (m_RegisterOnAwake)
Register();
m_ReuseYield = new WaitForSeconds (m_ReuseDelay);
}
void OnEnable ()
{
if (m_RegisterOnAwake)
Register();
}
void OnDisable ()
{
Unregister();
m_CooldownCoroutine = null;
}
public void Register()
{
if (!m_Registered)
{
SpawnManager.AddSpawnPoint(this);
m_Registered = true;
}
}
public void Unregister()
{
if (m_Registered)
{
SpawnManager.RemoveSpawnPoint(this);
m_Registered = false;
}
}
public virtual ICharacter SpawnCharacter (ICharacter characterPrototype, IController player, bool force = false, NeoSerializedScene scene = null)
{
// Check if can spawn
if (force)
{
if (m_CooldownCoroutine != null)
{
StopCoroutine(m_CooldownCoroutine);
m_CooldownCoroutine = null;
}
}
else
{
if (!CanSpawnCharacter())
return null;
}
// Get character
ICharacter character = CreateCharacterFromPrototype (characterPrototype, scene);
if (character == null)
return null;
// Place character
Transform ct = character.gameObject.transform;
ct.position = spawnTransform.position;
if (m_ReorientGravity)
{
ct.rotation = spawnTransform.rotation;
var neoCC = ct.GetComponent<INeoCharacterController>();
if (neoCC != null)
{
neoCC.characterGravity.gravity = spawnTransform.rotation * neoCC.gravity;
if (!neoCC.characterGravity.orientUpWithGravity)
neoCC.characterGravity.up = spawnTransform.rotation * neoCC.up;
}
}
else
{
var spawnForward = Vector3.ProjectOnPlane(spawnTransform.forward, Vector3.up);
if (spawnForward.sqrMagnitude < 0.0001f)
spawnForward = Vector3.forward;
else
spawnForward.Normalize();
ct.rotation = Quaternion.LookRotation(spawnForward);
}
// Set controller
character.controller = player;
// Fire event
m_OnSpawn.Invoke ();
// Start cooldown
if (m_ReuseDelay > 0f)
m_CooldownCoroutine = StartCoroutine (Cooldown ());
return character;
}
protected virtual ICharacter CreateCharacterFromPrototype (ICharacter prototype, NeoSerializedScene scene = null)
{
ICharacter result = null;
MonoBehaviour behaviour = prototype as MonoBehaviour;
if (behaviour != null)
{
GameObject go = null;
if (scene != null)
go = scene.InstantiatePrefab(behaviour).gameObject;
else
go = Instantiate(behaviour.gameObject);
//go.SetActive(true);
result = go.GetComponent<ICharacter>();
}
return result;
}
IEnumerator Cooldown ()
{
yield return m_ReuseYield;
m_CooldownCoroutine = null;
}
public virtual bool CanSpawnCharacter()
{
// Check timeout
if (m_CooldownCoroutine != null)
return false;
// Check overlap
switch (m_OverlapTest)
{
case OverlapTest.Box:
{
float halfX = m_BoundsHorizontal * 0.5f;
float halfY = m_BoundsHeight * 0.5f;
if (Physics.CheckBox(
spawnTransform.position + (up * halfY),
new Vector3(halfX - k_Tolerance, halfY - k_Tolerance, halfX - k_Tolerance),
rotation,
PhysicsFilter.Masks.SpawnBlockers,
QueryTriggerInteraction.Ignore
))
return false;
}
break;
case OverlapTest.Capsule:
{
float radius = m_BoundsHorizontal * 0.5f;
float top = m_BoundsHeight - radius;
Vector3 position = spawnTransform.position;
if (Physics.CheckCapsule(
position + (up * radius),
position + (up * top),
radius - k_Tolerance,
PhysicsFilter.Masks.SpawnBlockers,
QueryTriggerInteraction.Ignore
))
return false;
}
break;
}
return true;
}
private void OnDrawGizmos()
{
Color c = CanSpawnCharacter() ? Color.cyan : Color.red;
// Draw spawn forwards direction
ExtendedGizmos.DrawArrowMarkerFlat(spawnTransform.position, rotation, 0f, 1f, c);
switch (m_OverlapTest)
{
case OverlapTest.Box:
ExtendedGizmos.DrawCuboidMarker(spawnTransform.position, m_BoundsHorizontal, m_BoundsHeight, rotation, c);
break;
case OverlapTest.Capsule:
float radius = m_BoundsHorizontal * 0.5f;
float top = m_BoundsHeight - radius;
Vector3 position = spawnTransform.position;
ExtendedGizmos.DrawCapsuleMarker(position + (up * radius), position + (up * top), m_BoundsHorizontal * 0.5f, c);
break;
}
}
}
}