/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.Game { using UnityEngine; /// /// Specifies a location that the object can spawn. /// public class SpawnPoint : MonoBehaviour { /// /// Specifies the shape in which the spawn point should randomly be determined. /// public enum SpawnShape { Point, // The spawn point will be determined at the transform position. Sphere, // The spawn point will be determined within a random sphere. Box // The spawn point will be determined within a box. } [Tooltip("An index value used to group multiple sets of spawn points. A value of -1 will ignore the grouping.")] [SerializeField] protected int m_Grouping = -1; [Tooltip("Specifies the shape in which the spawn point should randomly be determined.")] [SerializeField] protected SpawnShape m_Shape; [Tooltip("The size of the spawn shape.")] [SerializeField] protected float m_Size; [Tooltip("Should the object be spawned randomly within the shape?")] [SerializeField] protected bool m_RandomShapeSpawn = true; [Tooltip("Specifies the height of the ground check.")] [SerializeField] protected float m_GroundSnapHeight; [Tooltip("Should the character spawn with a random y direction?")] [SerializeField] protected bool m_RandomDirection; [Tooltip("Should a check be performed to determine if there are any objects obstructing the spawn point?")] [SerializeField] protected bool m_CheckForObstruction; [Tooltip("The maximum number of collision points which the spawn points should check against.")] [SerializeField] protected int m_MaxCollisionCount = 20; [Tooltip("The layers which can obstruct the spawn point.")] [SerializeField] protected LayerMask m_ObstructionLayers = ~(1 << LayerManager.Default | 1 << LayerManager.IgnoreRaycast | 1 << LayerManager.TransparentFX | 1 << LayerManager.SubCharacter | 1 << LayerManager.Overlay | 1 << LayerManager.VisualEffect); [Tooltip("If checking for obstruction, specifies how many times the location should be determined before it is decided that there are no valid spawn locations.")] [SerializeField] protected int m_PlacementAttempts = 10; #if UNITY_EDITOR [Tooltip("The color to draw the editor gizmo in (editor only).")] [SerializeField] protected Color m_GizmoColor = new Color(1, 0, 0, 0.3f); #endif public int Grouping { get { return m_Grouping; } set { if (m_Grouping != value) { // The SpawnPointManager needs to be aware of the change so it can update its internal mapping. if (Application.isPlaying) { SpawnPointManager.UpdateSpawnPointGrouping(this, value); } m_Grouping = value; } } } public SpawnShape Shape { get { return m_Shape; } set { m_Shape = value; } } public float Size { get { return m_Size; } set { m_Size = value; } } public float GroundSnapHeight { get { return m_GroundSnapHeight; } set { m_GroundSnapHeight = value; } } public bool RandomDirection { get { return m_RandomDirection; } set { m_RandomDirection = value; } } public bool CheckForObstruction { get { return m_CheckForObstruction; } set { m_CheckForObstruction = value; } } public int PlacementAttempts { get { return m_PlacementAttempts; } set { m_PlacementAttempts = value; } } #if UNITY_EDITOR public Color GizmoColor { get { return m_GizmoColor; } set { m_GizmoColor = value; } } #endif private Transform m_Transform; private Collider[] m_ObstructionColliders; /// /// Initialize the default values. /// private void Awake() { m_Transform = transform; if (m_CheckForObstruction) { m_ObstructionColliders = new Collider[m_MaxCollisionCount]; } } /// /// Adds the spawn point to the manager. /// private void OnEnable() { SpawnPointManager.AddSpawnPoint(this); } /// /// Gets the position and rotation of the spawn point. If false is returned then the point wasn't successfully retrieved. /// /// The object that is spawning. /// The position of the spawn point. /// The rotation of the spawn point. /// True if the spawn point was successfully retrieved. public virtual bool GetPlacement(GameObject spawningObject, ref Vector3 position, ref Quaternion rotation) { position = RandomPosition(0); // Ensure the spawn point is clear of any obstructing objects. if (m_CheckForObstruction) { var attempt = 0; var success = false; while (attempt < m_PlacementAttempts) { if (m_Shape == SpawnShape.Point) { // A point will always succeed. success = true; } else if (m_Shape == SpawnShape.Sphere) { // Ignore any collisions with itself. var overlapCount = Physics.OverlapSphereNonAlloc(position, m_Size / 2, m_ObstructionColliders, m_ObstructionLayers, QueryTriggerInteraction.Ignore); if (spawningObject != null) { for (int i = overlapCount - 1; i > -1; --i) { if (!m_ObstructionColliders[i].transform.IsChildOf(spawningObject.transform)) { break; } overlapCount--; } } success = overlapCount == 0; if (success) { break; } } else { // Box. var extents = Vector3.zero; extents.x = extents.z = m_Size / 2; extents.y = m_GroundSnapHeight / 2; var boxPosition = m_Transform.TransformPoint(extents); // Ignore any collisions with itself. var overlapCount = Physics.OverlapBoxNonAlloc(boxPosition, extents, m_ObstructionColliders, m_Transform.rotation, m_ObstructionLayers, QueryTriggerInteraction.Ignore); for (int i = overlapCount - 1; i > -1; --i) { if (!m_ObstructionColliders[i].transform.IsChildOf(spawningObject.transform)) { break; } overlapCount--; } success = overlapCount == 0; if (success) { break; } } ++attempt; position = RandomPosition(attempt); } // No valid position was found - return false. if (!success) { return false; } } // If the ground snap height is positive then the position should be located on the ground. if (m_GroundSnapHeight > 0) { RaycastHit raycastHit; if (Physics.Raycast(position + m_Transform.up * m_GroundSnapHeight, -m_Transform.up, out raycastHit, m_GroundSnapHeight + 0.2f, m_ObstructionLayers, QueryTriggerInteraction.Ignore)) { position = raycastHit.point + m_Transform.up * 0.01f; } } // Optionally rotate a random spawn direction. if (m_RandomDirection) { rotation = Quaternion.Euler(m_Transform.up * Random.Range(0, 360)); } else { rotation = m_Transform.rotation; } return true; } /// /// Retruns a position based on the spawn shape. /// /// The attempt to position the object. /// A position within the spawn spape. private Vector3 RandomPosition(int attempt) { // Always first try to position in the center. if (attempt == 0 || !m_RandomShapeSpawn) { return m_Transform.position; } var localPosition = Vector3.zero; if (m_Shape == SpawnShape.Sphere) { localPosition = Random.insideUnitSphere * m_Size; localPosition.y = 0; } else if (m_Shape == SpawnShape.Box) { var halfSize = m_Size / 2; localPosition.x = Random.Range(-halfSize, halfSize); localPosition.z = Random.Range(-halfSize, halfSize); } return m_Transform.TransformPoint(localPosition); } /// /// Removes the spawn point from the manager. /// private void OnDisable() { SpawnPointManager.RemoveSpawnPoint(this); } } }