Dungeon.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. using DunGen.Generation;
  2. using DunGen.Graph;
  3. using DunGen.Tags;
  4. using System;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.Collections.ObjectModel;
  8. using System.Linq;
  9. using UnityEngine;
  10. namespace DunGen
  11. {
  12. public delegate void DungeonTileInstantiatedDelegate(Dungeon dungeon, Tile newTile, int currentTileCount, int totalTileCount);
  13. public class Dungeon : MonoBehaviour
  14. {
  15. public static event DungeonTileInstantiatedDelegate TileInstantiated;
  16. #region Nestes Types
  17. public sealed class Branch
  18. {
  19. public int Index { get; }
  20. public ReadOnlyCollection<Tile> Tiles { get; }
  21. public Branch(int index, List<Tile> tiles)
  22. {
  23. Index = index;
  24. Tiles = new ReadOnlyCollection<Tile>(tiles);
  25. }
  26. }
  27. #endregion
  28. /// <summary>
  29. /// World-space bounding box of the entire dungeon
  30. /// </summary>
  31. public Bounds Bounds { get; protected set; }
  32. /// <summary>
  33. /// The dungeon flow asset used to generate this dungeon
  34. /// </summary>
  35. public DungeonFlow DungeonFlow
  36. {
  37. get => dungeonFlow;
  38. set => dungeonFlow = value;
  39. }
  40. /// <summary>
  41. /// Should we render debug information about the dungeon
  42. /// </summary>
  43. public bool DebugRender = false;
  44. public ReadOnlyCollection<Tile> AllTiles { get; }
  45. public ReadOnlyCollection<Tile> MainPathTiles { get; }
  46. public ReadOnlyCollection<Tile> BranchPathTiles { get; }
  47. public ReadOnlyCollection<GameObject> Doors { get; }
  48. public ReadOnlyCollection<DoorwayConnection> Connections { get; }
  49. public ReadOnlyCollection<Branch> Branches { get; }
  50. public DungeonGraph ConnectionGraph { get; private set; }
  51. [SerializeField]
  52. private DungeonFlow dungeonFlow;
  53. [SerializeField]
  54. private List<Tile> allTiles = new List<Tile>();
  55. [SerializeField]
  56. private List<Tile> mainPathTiles = new List<Tile>();
  57. [SerializeField]
  58. private List<Tile> branchPathTiles = new List<Tile>();
  59. [SerializeField]
  60. private List<GameObject> doors = new List<GameObject>();
  61. [SerializeField]
  62. private List<DoorwayConnection> connections = new List<DoorwayConnection>();
  63. [SerializeField]
  64. private Tile attachmentTile;
  65. [SerializeField]
  66. private List<Branch> branches = new List<Branch>();
  67. public Dungeon()
  68. {
  69. AllTiles = new ReadOnlyCollection<Tile>(allTiles);
  70. MainPathTiles = new ReadOnlyCollection<Tile>(mainPathTiles);
  71. BranchPathTiles = new ReadOnlyCollection<Tile>(branchPathTiles);
  72. Doors = new ReadOnlyCollection<GameObject>(doors);
  73. Connections = new ReadOnlyCollection<DoorwayConnection>(connections);
  74. Branches = new ReadOnlyCollection<Branch>(branches);
  75. }
  76. private void Start()
  77. {
  78. // If there are already tiles and the connection graph isn't initialised yet,
  79. // this script is likely already present in the scene (from generating the dungeon in-editor).
  80. // We just need to finalise the dungeon info from data we already have available
  81. if (allTiles.Count > 0 && ConnectionGraph == null)
  82. FinaliseDungeonInfo();
  83. }
  84. public IEnumerable<Tile> FindTilesWithTag(Tag tag) => allTiles.Where(t => t.Tags.HasTag(tag));
  85. public IEnumerable<Tile> FindTilesWithAnyTag(params Tag[] tags) => allTiles.Where(t => t.Tags.HasAnyTag(tags));
  86. public IEnumerable<Tile> FindTilesWithAllTags(params Tag[] tags) => allTiles.Where(t => t.Tags.HasAllTags(tags));
  87. internal void AddAdditionalDoor(Door door)
  88. {
  89. if (door != null)
  90. doors.Add(door.gameObject);
  91. }
  92. internal void PreGenerateDungeon(DungeonGenerator dungeonGenerator)
  93. {
  94. DungeonFlow = dungeonGenerator.DungeonFlow;
  95. }
  96. internal void PostGenerateDungeon(DungeonGenerator dungeonGenerator)
  97. {
  98. FinaliseDungeonInfo();
  99. }
  100. private void FinaliseDungeonInfo()
  101. {
  102. var additionalTiles = new List<Tile>();
  103. if (attachmentTile != null)
  104. additionalTiles.Add(attachmentTile);
  105. ConnectionGraph = new DungeonGraph(this, additionalTiles);
  106. Bounds = UnityUtil.CombineBounds(allTiles.Select(x => x.Placement.Bounds).ToArray());
  107. GatherBranches();
  108. }
  109. /// <summary>
  110. /// Gathers all branches into lists for easy access
  111. /// </summary>
  112. private void GatherBranches()
  113. {
  114. var branchTiles = new Dictionary<int, List<Tile>>();
  115. // Gather branch tiles
  116. foreach (var branchTile in branchPathTiles)
  117. {
  118. int branchIndex = branchTile.Placement.BranchId;
  119. if(!branchTiles.TryGetValue(branchIndex, out var branchTileList))
  120. {
  121. branchTileList = new List<Tile>();
  122. branchTiles[branchIndex] = branchTileList;
  123. }
  124. branchTileList.Add(branchTile);
  125. }
  126. // Create branch objects
  127. foreach (var kvp in branchTiles)
  128. {
  129. int index = kvp.Key;
  130. var tiles = kvp.Value;
  131. branches.Add(new Branch(index, tiles));
  132. }
  133. }
  134. public void Clear(Action<Tile> destroyTileDelegate)
  135. {
  136. // Destroy all tiles
  137. foreach (var tile in allTiles)
  138. destroyTileDelegate(tile);
  139. // Destroy anything else attached to this dungeon
  140. for (int i = 0; i < transform.childCount; i++)
  141. {
  142. GameObject child = transform.GetChild(i).gameObject;
  143. UnityUtil.Destroy(child);
  144. }
  145. allTiles.Clear();
  146. mainPathTiles.Clear();
  147. branchPathTiles.Clear();
  148. doors.Clear();
  149. connections.Clear();
  150. branches.Clear();
  151. attachmentTile = null;
  152. }
  153. public Doorway GetConnectedDoorway(Doorway doorway)
  154. {
  155. foreach (var conn in connections)
  156. if (conn.A == doorway)
  157. return conn.B;
  158. else if (conn.B == doorway)
  159. return conn.A;
  160. return null;
  161. }
  162. public IEnumerator FromProxy(DungeonProxy proxyDungeon,
  163. DungeonGenerator generator,
  164. TileInstanceSource tileInstanceSource,
  165. Func<bool> shouldSkipFrame)
  166. {
  167. Clear(tileInstanceSource.DespawnTile);
  168. var proxyToTileMap = new Dictionary<TileProxy, Tile>();
  169. // We're attaching to a previous dungeon
  170. if (generator.AttachmentSettings != null &&
  171. generator.AttachmentSettings.TileProxy != null)
  172. {
  173. // We need to manually inject the dummy TileProxy used to connect to a Tile in the previous dungeon
  174. var attachmentProxy = generator.AttachmentSettings.TileProxy;
  175. attachmentTile = generator.AttachmentSettings.GetAttachmentTile();
  176. proxyToTileMap[attachmentProxy] = attachmentTile;
  177. // We also need to manually process the doorway in the other dungeon
  178. var usedDoorwayProxy = attachmentProxy.UsedDoorways.First();
  179. var usedDoorway = attachmentTile.AllDoorways[usedDoorwayProxy.Index];
  180. usedDoorway.ProcessDoorwayObjects(true, generator.RandomStream);
  181. attachmentTile.UsedDoorways.Add(usedDoorway);
  182. attachmentTile.UnusedDoorways.Remove(usedDoorway);
  183. }
  184. foreach (var tileProxy in proxyDungeon.AllTiles)
  185. {
  186. // Instantiate & re-position tile
  187. var tileObj = tileInstanceSource.SpawnTile(tileProxy.PrefabTile, tileProxy.Placement.Position, tileProxy.Placement.Rotation);
  188. // Add tile to lists
  189. var tile = tileObj.GetComponent<Tile>();
  190. tile.Dungeon = this;
  191. tile.Placement = new TilePlacementData(tileProxy.Placement);
  192. tile.Prefab = tileProxy.Prefab;
  193. proxyToTileMap[tileProxy] = tile;
  194. allTiles.Add(tile);
  195. // Now that the tile is actually attached to the root object, we need to update our transform to match
  196. tile.Placement.SetPositionAndRotation(tileObj.transform.position, tileObj.transform.rotation);
  197. if (tile.Placement.IsOnMainPath)
  198. mainPathTiles.Add(tile);
  199. else
  200. branchPathTiles.Add(tile);
  201. // Place trigger volume
  202. if (generator.TriggerPlacement != TriggerPlacementMode.None)
  203. {
  204. tile.AddTriggerVolume(generator.TriggerPlacement == TriggerPlacementMode.TwoDimensional);
  205. tile.gameObject.layer = generator.TileTriggerLayer;
  206. }
  207. // Process doorways
  208. var allDoorways = tileObj.GetComponentsInChildren<Doorway>();
  209. foreach (var doorway in allDoorways)
  210. {
  211. if (tile.AllDoorways.Contains(doorway))
  212. continue;
  213. doorway.Tile = tile;
  214. doorway.placedByGenerator = true;
  215. doorway.HideConditionalObjects = false;
  216. tile.AllDoorways.Add(doorway);
  217. }
  218. foreach (var doorwayProxy in tileProxy.UsedDoorways)
  219. {
  220. var doorway = allDoorways[doorwayProxy.Index];
  221. tile.UsedDoorways.Add(doorway);
  222. doorway.ProcessDoorwayObjects(true, generator.RandomStream);
  223. }
  224. foreach (var doorwayProxy in tileProxy.UnusedDoorways)
  225. {
  226. var doorway = allDoorways[doorwayProxy.Index];
  227. tile.UnusedDoorways.Add(doorway);
  228. doorway.ProcessDoorwayObjects(false, generator.RandomStream);
  229. }
  230. // Let the user know a new tile has been instantiated
  231. TileInstantiated?.Invoke(this, tile, allTiles.Count, proxyDungeon.AllTiles.Count);
  232. if (shouldSkipFrame != null && shouldSkipFrame())
  233. yield return null;
  234. }
  235. // Add doorway connections
  236. foreach (var proxyConn in proxyDungeon.Connections)
  237. {
  238. var tileA = proxyToTileMap[proxyConn.A.TileProxy];
  239. var tileB = proxyToTileMap[proxyConn.B.TileProxy];
  240. var doorA = tileA.AllDoorways[proxyConn.A.Index];
  241. var doorB = tileB.AllDoorways[proxyConn.B.Index];
  242. doorA.ConnectedDoorway = doorB;
  243. doorB.ConnectedDoorway = doorA;
  244. var conn = new DoorwayConnection(doorA, doorB);
  245. connections.Add(conn);
  246. SpawnDoorPrefab(doorA, doorB, generator.RandomStream);
  247. }
  248. }
  249. private void SpawnDoorPrefab(Doorway a, Doorway b, RandomStream randomStream)
  250. {
  251. // This door already has a prefab instance placed, exit early
  252. if (a.HasDoorPrefabInstance || b.HasDoorPrefabInstance)
  253. return;
  254. // Add door prefab
  255. Doorway chosenDoor;
  256. bool doorwayAHasEntries = a.ConnectorPrefabWeights.HasAnyViableEntries();
  257. bool doorwayBHasEntries = b.ConnectorPrefabWeights.HasAnyViableEntries();
  258. // No doorway has a prefab to place, exit early
  259. if (!doorwayAHasEntries && !doorwayBHasEntries)
  260. return;
  261. // If both doorways have door prefabs..
  262. if (doorwayAHasEntries && doorwayBHasEntries)
  263. {
  264. // ..A is selected if its priority is greater than or equal to B..
  265. if (a.DoorPrefabPriority >= b.DoorPrefabPriority)
  266. chosenDoor = a;
  267. // .. otherwise, B is chosen..
  268. else
  269. chosenDoor = b;
  270. }
  271. // ..if only one doorway has a prefab, use that one
  272. else
  273. chosenDoor = (doorwayAHasEntries) ? a : b;
  274. GameObject doorPrefab = chosenDoor.ConnectorPrefabWeights.GetRandom(randomStream);
  275. if (doorPrefab != null)
  276. {
  277. GameObject door = Instantiate(doorPrefab, chosenDoor.transform);
  278. door.transform.localPosition = chosenDoor.DoorPrefabPositionOffset;
  279. if (chosenDoor.AvoidRotatingDoorPrefab)
  280. door.transform.rotation = Quaternion.Euler(chosenDoor.DoorPrefabRotationOffset);
  281. else
  282. door.transform.localRotation = Quaternion.Euler(chosenDoor.DoorPrefabRotationOffset);
  283. doors.Add(door);
  284. DungeonUtil.AddAndSetupDoorComponent(this, door, chosenDoor);
  285. a.SetUsedPrefab(door);
  286. b.SetUsedPrefab(door);
  287. }
  288. }
  289. public void OnDrawGizmos()
  290. {
  291. if (DebugRender)
  292. DebugDraw();
  293. }
  294. public void DebugDraw()
  295. {
  296. Color mainPathStartColour = Color.red;
  297. Color mainPathEndColour = Color.green;
  298. Color branchPathStartColour = Color.blue;
  299. Color branchPathEndColour = new Color(0.5f, 0, 0.5f);
  300. float boundsBoxOpacity = 0.75f;
  301. foreach (var tile in allTiles)
  302. {
  303. Bounds bounds = tile.Placement.Bounds;
  304. bounds.size = bounds.size * 1.01f;
  305. Color tileColour = (tile.Placement.IsOnMainPath) ?
  306. Color.Lerp(mainPathStartColour, mainPathEndColour, tile.Placement.NormalizedDepth) :
  307. Color.Lerp(branchPathStartColour, branchPathEndColour, tile.Placement.NormalizedDepth);
  308. tileColour.a = boundsBoxOpacity;
  309. Gizmos.color = tileColour;
  310. Gizmos.DrawCube(bounds.center, bounds.size);
  311. }
  312. }
  313. }
  314. }