| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 |
- using DunGen.Tags;
- using System.Collections.Generic;
- using System.Linq;
- using UnityEngine;
- using UnityEngine.Serialization;
- namespace DunGen
- {
- /// <summary>
- /// A component to handle doorway placement and behaviour
- /// </summary>
- [AddComponentMenu("DunGen/Doorway")]
- public class Doorway : MonoBehaviour, ISerializationCallbackReceiver
- {
- public const int CurrentFileVersion = 1;
- public bool HasSocketAssigned { get { return socket != null; } }
- /// <summary>
- /// The socket this doorway uses. Allows you to use different sized doorways and have them connect correctly
- /// </summary>
- public DoorwaySocket Socket
- {
- get
- {
- if (socket == null)
- {
- if (cachedDefaultSocket == null)
- cachedDefaultSocket = DunGenSettings.Instance.DefaultSocket;
- return cachedDefaultSocket;
- }
- else
- return socket;
- }
- }
- /// <summary>
- /// When placing a door prefab, the doorway with the higher priority will have their prefab used
- /// </summary>
- public int DoorPrefabPriority;
- /// <summary>
- /// If true, the chosen Door prefab will not be oriented to match the rotation of the doorway it is placed on
- /// </summary>
- public bool AvoidRotatingDoorPrefab;
- /// <summary>
- /// An optional position offset to apply when spawning a door (connector) prefab, relative to the doorway's transform
- /// </summary>
- public Vector3 DoorPrefabPositionOffset;
- /// <summary>
- /// An optional rotation offset to apply when spawning a door (connector) prefab, relative to the doorway's transform
- /// </summary>
- public Vector3 DoorPrefabRotationOffset;
- /// <summary>
- /// When this doorway is in use, a prefab will be picked at random from this list and is spawned at the doorway location - one per doorways pair (connection)
- /// </summary>
- public List<GameObjectWeight> ConnectorPrefabWeights = new List<GameObjectWeight>();
- /// <summary>
- /// When this doorway is in use, objects in this list will remain in the scene, otherwise, they are destroyed
- /// </summary>
- [FormerlySerializedAs("AddWhenInUse")]
- public List<GameObject> ConnectorSceneObjects = new List<GameObject>();
- /// <summary>
- /// If true, the chosen Blocker prefab will not be oriented to match the rotation of the doorway it is placed on
- /// </summary>
- public bool AvoidRotatingBlockerPrefab;
- /// <summary>
- /// An optional position offset to apply when spawning a blocker prefab, relative to the doorway's transform
- /// </summary>
- public Vector3 BlockerPrefabPositionOffset;
- /// <summary>
- /// An optional rotation offset to apply when spawning a blocker prefab, relative to the doorway's transform
- /// </summary>
- public Vector3 BlockerPrefabRotationOffset;
- /// <summary>
- /// When this doorway is NOT in use, a prefab will be picked at random from this list and is spawned at the doorway location - one per doorway
- /// </summary>
- public List<GameObjectWeight> BlockerPrefabWeights = new List<GameObjectWeight>();
- /// <summary>
- /// When this doorway is NOT in use, objects in this list will remain in the scene, otherwise, they are destroyed
- /// </summary>
- [FormerlySerializedAs("AddWhenNotInUse")]
- public List<GameObject> BlockerSceneObjects = new List<GameObject>();
- /// <summary>
- /// A collection of tags for this doorway. These can be used in code with DoorwayPairFinder.CustomConnectionRules for custom connection logic
- /// </summary>
- public TagContainer Tags = new TagContainer();
- /// <summary>
- /// The Tile that this doorway belongs to
- /// </summary>
- public Tile Tile { get { return tile; } internal set { tile = value; } }
- /// <summary>
- /// The ID of the key used to unlock this door
- /// </summary>
- public int? LockID;
- /// <summary>
- /// Gets the lock status of the door
- /// </summary>
- public bool IsLocked { get { return LockID.HasValue; } }
- /// <summary>
- /// Does this doorway have a prefab object placed as a door?
- /// </summary>
- public bool HasDoorPrefabInstance { get { return doorPrefabInstance != null; } }
- /// <summary>
- /// The prefab that has been placed as a door for this doorway
- /// </summary>
- public GameObject UsedDoorPrefabInstance { get { return doorPrefabInstance; } }
- /// <summary>
- /// The Door component that has been assigned to the door prefab instance (if any)
- /// </summary>
- public Door DoorComponent { get { return doorComponent; } }
- /// <summary>
- /// The dungeon that this doorway belongs to
- /// </summary>
- public Dungeon Dungeon { get; internal set; }
- /// <summary>
- /// The doorway that this is connected to
- /// </summary>
- public Doorway ConnectedDoorway { get { return connectedDoorway; } internal set { connectedDoorway = value; } }
- /// <summary>
- /// Allows for hiding of any GameObject in the "AddWhenInUse" and "AddWhenNotInUse" lists - used to remove clutter at design-time; should not be used at runtime
- /// </summary>
- public bool HideConditionalObjects
- {
- get { return hideConditionalObjects; }
- set
- {
- hideConditionalObjects = value;
- foreach (var obj in ConnectorSceneObjects)
- if (obj != null)
- obj.SetActive(!hideConditionalObjects);
- foreach (var obj in BlockerSceneObjects)
- if (obj != null)
- obj.SetActive(!hideConditionalObjects);
- }
- }
- #region Legacy Properties
- #pragma warning disable 0414
- [SerializeField]
- [FormerlySerializedAs("SocketGroup")]
- private DoorwaySocketType socketGroup_obsolete = (DoorwaySocketType)(-1);
- [SerializeField]
- [FormerlySerializedAs("DoorPrefabs")]
- private List<GameObject> doorPrefabs_obsolete = new List<GameObject>();
- [SerializeField]
- [FormerlySerializedAs("BlockerPrefabs")]
- private List<GameObject> blockerPrefabs_obsolete = new List<GameObject>();
- #pragma warning restore 0414
- #endregion
- [SerializeField]
- private DoorwaySocket socket = null;
- [SerializeField]
- private GameObject doorPrefabInstance;
- [SerializeField]
- private Door doorComponent;
- [SerializeField]
- private Tile tile;
- [SerializeField]
- private Doorway connectedDoorway;
- [SerializeField]
- private bool hideConditionalObjects;
- [SerializeField]
- private GameObject spawnedBlockerPrefab;
- [SerializeField]
- private int fileVersion;
- private DoorwaySocket cachedDefaultSocket;
- internal bool placedByGenerator;
- private void OnValidate()
- {
- if (socket == null)
- socket = DunGenSettings.Instance.DefaultSocket;
- }
- internal void SetUsedPrefab(GameObject doorPrefab)
- {
- this.doorPrefabInstance = doorPrefab;
- if (doorPrefab != null)
- doorComponent = doorPrefab.GetComponent<Door>();
- }
- internal void RemoveUsedPrefab()
- {
- if (doorPrefabInstance != null)
- UnityUtil.Destroy(doorPrefabInstance);
- doorPrefabInstance = null;
- }
- #if UNITY_EDITOR
- private void OnDrawGizmos()
- {
- if (!placedByGenerator)
- DebugDraw();
- }
- internal void DebugDraw()
- {
- Vector2 size = Socket.Size;
- Vector2 halfSize = size * 0.5f;
- bool isValidPlacement = true;
- Color doorwayColour = Color.white;
- isValidPlacement = ValidateTransform(out var localTileBounds, out bool isAxisAligned, out bool isEdgePositioned);
- if (isValidPlacement)
- doorwayColour = EditorConstants.DoorRectColourValid;
- else if (!isAxisAligned)
- doorwayColour = EditorConstants.DoorRectColourError;
- else
- doorwayColour = EditorConstants.DoorRectColourWarning;
- // Draw Forward Vector
- float lineLength = Mathf.Min(size.x, size.y);
- Gizmos.color = EditorConstants.DoorDirectionColour;
- Gizmos.DrawLine(transform.position + transform.up * halfSize.y, transform.position + transform.up * halfSize.y + transform.forward * lineLength);
- // Draw Up Vector
- Gizmos.color = EditorConstants.DoorUpColour;
- Gizmos.DrawLine(transform.position + transform.up * halfSize.y, transform.position + transform.up * size.y);
- // Draw Rectangle
- Gizmos.color = doorwayColour;
- Vector3 topLeft = transform.position - (transform.right * halfSize.x) + (transform.up * size.y);
- Vector3 topRight = transform.position + (transform.right * halfSize.x) + (transform.up * size.y);
- Vector3 bottomLeft = transform.position - (transform.right * halfSize.x);
- Vector3 bottomRight = transform.position + (transform.right * halfSize.x);
- Gizmos.DrawLine(topLeft, topRight);
- Gizmos.DrawLine(topRight, bottomRight);
- Gizmos.DrawLine(bottomRight, bottomLeft);
- Gizmos.DrawLine(bottomLeft, topLeft);
- // Draw position correction line
- if (!isValidPlacement)
- {
- GetTileRoot(out var _, out var tile);
- // Projected position is meaningless if the Doorway isn't attached to a Tile
- if (tile != null)
- {
- Vector3 projectedPosition = ProjectPositionToTileBounds(localTileBounds);
- Gizmos.color = Color.red;
- Gizmos.DrawLine(transform.position, projectedPosition);
- }
- }
- }
- #endif
- private void GetTileRoot(out GameObject tileRoot, out Tile tileComponent)
- {
- tileComponent = GetComponentInParent<Tile>();
- #if UNITY_EDITOR
- // We might need to walk up the transform hierarchy manually
- if (tileComponent == null)
- {
- Transform current = transform;
- while (current != null)
- {
- tileComponent = current.GetComponent<Tile>();
- if (tileComponent != null)
- break;
- current = current.parent;
- }
- }
- #endif
- if (tileComponent != null)
- tileRoot = tileComponent.gameObject;
- else
- tileRoot = transform.root.gameObject;
- }
- public bool ValidateTransform(out Bounds localTileBounds, out bool isAxisAligned, out bool isEdgePositioned)
- {
- GetTileRoot(out var tileRoot, out var tile);
- // The Doorway isn't attached to a Tile, it can never be valid
- if(tileRoot == null || tile == null)
- {
- localTileBounds = new Bounds();
- isAxisAligned = false;
- isEdgePositioned = false;
- return false;
- }
- isAxisAligned = true;
- isEdgePositioned = true;
- if (tile != null && tile.OverrideAutomaticTileBounds)
- localTileBounds = tile.TileBoundsOverride;
- else
- localTileBounds = tile.Placement.LocalBounds;
- if (!UnityUtil.IsVectorAxisAligned(transform.forward))
- isAxisAligned = false;
- Vector3 projectedPosition = ProjectPositionToTileBounds(localTileBounds);
- if ((projectedPosition - transform.position).magnitude > 0.1f)
- isEdgePositioned = false;
- return isAxisAligned && isEdgePositioned;
- }
- public void TrySnapToCorrectedTransform()
- {
- if (ValidateTransform(out var localTileBounds, out _, out _))
- return;
- Vector3 correctedForward = UnityUtil.GetCardinalDirection(transform.forward, out _);
- transform.forward = correctedForward;
- transform.position = ProjectPositionToTileBounds(localTileBounds);
- }
- public Vector3 ProjectPositionToTileBounds(Bounds localTileBounds)
- {
- GetTileRoot(out var tileRoot, out var tile);
- var worldSpaceBounds = tileRoot.transform.TransformBounds(localTileBounds);
- Vector3 correctedForward = UnityUtil.GetCardinalDirection(transform.forward, out var magnitude);
- Vector3 offsetFromBoundsCenter = transform.position - worldSpaceBounds.center;
- // Calculate correction distance along forward vector (snap to edge)
- float currentForwardDistance = Vector3.Dot(correctedForward, offsetFromBoundsCenter);
- float extentForwardDistance = Vector3.Dot(magnitude < 0 ? -correctedForward : correctedForward, worldSpaceBounds.extents);
- float forwardCorrectionDistance = extentForwardDistance - currentForwardDistance;
- Vector3 targetPosition = transform.position;
- targetPosition += correctedForward * forwardCorrectionDistance;
- // Once we're positioned on the correct side of the bounding box based on the forward vector
- // of the doorway, clamp the position to keep it restrained within the bounds along the other axes
- targetPosition = UnityUtil.ClampVector(targetPosition, worldSpaceBounds.min, worldSpaceBounds.max);
- return targetPosition;
- }
- internal void ResetInstanceData()
- {
- if (spawnedBlockerPrefab != null)
- DestroyImmediate(spawnedBlockerPrefab);
- if(doorPrefabInstance != null)
- DestroyImmediate(doorPrefabInstance);
- connectedDoorway = null;
- }
- internal void ProcessDoorwayObjects(bool isDoorwayInUse, RandomStream randomStream)
- {
- foreach (var obj in BlockerSceneObjects)
- {
- if (obj != null)
- obj.SetActive(!isDoorwayInUse);
- }
- foreach (var obj in ConnectorSceneObjects)
- {
- if (obj != null)
- obj.SetActive(isDoorwayInUse);
- }
- if (isDoorwayInUse)
- {
- if (spawnedBlockerPrefab != null)
- DestroyImmediate(spawnedBlockerPrefab);
- }
- else
- {
- // If there is at least one blocker prefab, select one and spawn it as a child of the doorway
- if (BlockerPrefabWeights.HasAnyViableEntries())
- {
- spawnedBlockerPrefab = GameObject.Instantiate(BlockerPrefabWeights.GetRandom(randomStream)) as GameObject;
- spawnedBlockerPrefab.transform.parent = gameObject.transform;
- spawnedBlockerPrefab.transform.localPosition = BlockerPrefabPositionOffset;
- spawnedBlockerPrefab.transform.localScale = Vector3.one;
- if (AvoidRotatingBlockerPrefab)
- spawnedBlockerPrefab.transform.rotation = Quaternion.Euler(BlockerPrefabRotationOffset);
- else
- spawnedBlockerPrefab.transform.localRotation = Quaternion.Euler(BlockerPrefabRotationOffset);
- }
- }
- }
- #region ISerializationCallbackReceiver Implementation
- public void OnBeforeSerialize()
- {
- fileVersion = CurrentFileVersion;
- }
- public void OnAfterDeserialize()
- {
- // Convert old object lists to weighted lists
- if (fileVersion < 1)
- {
- foreach (var obj in doorPrefabs_obsolete)
- ConnectorPrefabWeights.Add(new GameObjectWeight(obj));
- foreach (var obj in blockerPrefabs_obsolete)
- BlockerPrefabWeights.Add(new GameObjectWeight(obj));
- doorPrefabs_obsolete.Clear();
- blockerPrefabs_obsolete.Clear();
- }
- }
- #endregion
- }
- }
|