AdjacentRoomCulling.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace DunGen
  5. {
  6. [AddComponentMenu("DunGen/Culling/Adjacent Room Culling")]
  7. public class AdjacentRoomCulling : MonoBehaviour
  8. {
  9. public delegate void VisibilityChangedDelegate(Tile tile, bool visible);
  10. /// <summary>
  11. /// How deep from the current room should tiles be considered visibile
  12. /// 0 = Only the current tile
  13. /// 1 = The current tile and all its neighbours
  14. /// 2 = The current tile, all its neighbours, and all THEIR neighbours
  15. /// etc...
  16. /// </summary>
  17. public int AdjacentTileDepth = 1;
  18. /// <summary>
  19. /// If true, tiles behind a closed door will be culled, even if they're within <see cref="AdjacentTileDepth"/>
  20. /// </summary>
  21. public bool CullBehindClosedDoors = true;
  22. /// <summary>
  23. /// If set, this transform will be used as the vantage point that rooms should be culled from.
  24. /// Useful for third person games where you want to cull from the character's position, not the camera
  25. /// </summary>
  26. public Transform TargetOverride = null;
  27. /// <summary>
  28. /// Whether culling should handle any components that start disabled
  29. /// </summary>
  30. public bool IncludeDisabledComponents = false;
  31. /// <summary>
  32. /// A set of override values for specific renderers.
  33. /// By default, this script will overwrite any renderer.enabled values we might set in
  34. /// gameplay code. This property lets us tell the culling that we want to override the
  35. /// visibility values its setting
  36. /// </summary>
  37. [NonSerialized]
  38. public Dictionary<Renderer, bool> OverrideRendererVisibilities = new Dictionary<Renderer, bool>();
  39. /// <summary>
  40. /// A set of override values for specific lights.
  41. /// By default, this script will overwrite any light.enabled values we might set in
  42. /// gameplay code. This property lets us tell the culling that we want to override the
  43. /// visibility values its setting
  44. /// </summary>
  45. [NonSerialized]
  46. public Dictionary<Light, bool> OverrideLightVisibilities = new Dictionary<Light, bool>();
  47. /// <summary>
  48. /// True when a dungeon has been assigned and we're ready to start culling
  49. /// </summary>
  50. public bool Ready { get; protected set; }
  51. public event VisibilityChangedDelegate TileVisibilityChanged;
  52. protected List<Tile> allTiles;
  53. protected List<Door> allDoors;
  54. protected List<Tile> oldVisibleTiles;
  55. protected List<Tile> visibleTiles;
  56. protected Dictionary<Tile, bool> tileVisibilities;
  57. protected Dictionary<Tile, List<Renderer>> tileRenderers;
  58. protected Dictionary<Tile, List<Light>> lightSources;
  59. protected Dictionary<Tile, List<ReflectionProbe>> reflectionProbes;
  60. protected Dictionary<Door, List<Renderer>> doorRenderers;
  61. protected Transform targetTransform { get { return (TargetOverride != null) ? TargetOverride : transform; } }
  62. private bool dirty;
  63. private DungeonGenerator generator;
  64. private Tile currentTile;
  65. private Queue<Tile> tilesToSearch;
  66. private List<Tile> searchedTiles;
  67. private Dungeon dungeon;
  68. protected virtual void OnEnable()
  69. {
  70. var runtimeDungeon = UnityUtil.FindObjectByType<RuntimeDungeon>();
  71. if (runtimeDungeon != null)
  72. {
  73. generator = runtimeDungeon.Generator;
  74. generator.OnGenerationStatusChanged += OnDungeonGenerationStatusChanged; ;
  75. if (generator.Status == GenerationStatus.Complete)
  76. SetDungeon(generator.CurrentDungeon);
  77. }
  78. }
  79. protected virtual void OnDisable()
  80. {
  81. if (generator != null)
  82. generator.OnGenerationStatusChanged -= OnDungeonGenerationStatusChanged;
  83. ClearDungeon();
  84. }
  85. public virtual void SetDungeon(Dungeon newDungeon)
  86. {
  87. if (Ready)
  88. ClearDungeon();
  89. dungeon = newDungeon;
  90. if (dungeon == null)
  91. return;
  92. allTiles = new List<Tile>(dungeon.AllTiles);
  93. allDoors = new List<Door>(GetAllDoorsInDungeon(dungeon));
  94. oldVisibleTiles = new List<Tile>(allTiles.Count);
  95. visibleTiles = new List<Tile>(allTiles.Count);
  96. tileVisibilities = new Dictionary<Tile, bool>();
  97. tileRenderers = new Dictionary<Tile, List<Renderer>>();
  98. lightSources = new Dictionary<Tile, List<Light>>();
  99. reflectionProbes = new Dictionary<Tile, List<ReflectionProbe>>();
  100. doorRenderers = new Dictionary<Door, List<Renderer>>();
  101. UpdateRendererLists();
  102. foreach (var tile in allTiles)
  103. SetTileVisibility(tile, false);
  104. foreach (var door in allDoors)
  105. {
  106. door.OnDoorStateChanged += OnDoorStateChanged;
  107. SetDoorVisibility(door, false);
  108. }
  109. Ready = true;
  110. dirty = true;
  111. }
  112. public virtual bool IsTileVisible(Tile tile)
  113. {
  114. bool visibility;
  115. if (tileVisibilities.TryGetValue(tile, out visibility))
  116. return visibility;
  117. else
  118. return false;
  119. }
  120. protected IEnumerable<Door> GetAllDoorsInDungeon(Dungeon dungeon)
  121. {
  122. foreach (var doorObj in dungeon.Doors)
  123. {
  124. if (doorObj == null)
  125. continue;
  126. var door = doorObj.GetComponent<Door>();
  127. if (door != null)
  128. yield return door;
  129. }
  130. }
  131. protected virtual void ClearDungeon()
  132. {
  133. if (!Ready)
  134. return;
  135. foreach (var door in allDoors)
  136. {
  137. SetDoorVisibility(door, true);
  138. door.OnDoorStateChanged -= OnDoorStateChanged;
  139. }
  140. foreach (var tile in allTiles)
  141. SetTileVisibility(tile, true);
  142. Ready = false;
  143. }
  144. protected virtual void OnDoorStateChanged(Door door, bool isOpen)
  145. {
  146. dirty = true;
  147. }
  148. protected virtual void OnDungeonGenerationStatusChanged(DungeonGenerator generator, GenerationStatus status)
  149. {
  150. if (status == GenerationStatus.Complete)
  151. SetDungeon(generator.CurrentDungeon);
  152. else if (status == GenerationStatus.Failed)
  153. ClearDungeon();
  154. }
  155. protected virtual void LateUpdate()
  156. {
  157. if (!Ready)
  158. return;
  159. var oldTile = currentTile;
  160. // If currentTile doesn't exist, we need to first look for a dungeon,
  161. // then search every tile to find one that encompasses this GameObject
  162. if (currentTile == null)
  163. currentTile = FindCurrentTile();
  164. // If currentTile does exist, but we're not in it, we can perform a
  165. // breadth-first search radiating from currentTile. Assuming the player
  166. // is likely to be in an adjacent room, this should be much quicker than
  167. // testing every tile in the dungeon
  168. else if (!currentTile.Bounds.Contains(targetTransform.position))
  169. currentTile = SearchForNewCurrentTile();
  170. if (currentTile != oldTile)
  171. dirty = true;
  172. if (dirty)
  173. RefreshVisibility();
  174. dirty = false;
  175. }
  176. protected virtual void RefreshVisibility()
  177. {
  178. var temp = visibleTiles;
  179. visibleTiles = oldVisibleTiles;
  180. oldVisibleTiles = temp;
  181. UpdateVisibleTiles();
  182. // Hide any tiles that are no longer visible
  183. foreach (var tile in oldVisibleTiles)
  184. if (!visibleTiles.Contains(tile))
  185. SetTileVisibility(tile, false);
  186. // Show tiles that are newly visible
  187. foreach (var tile in visibleTiles)
  188. if (!oldVisibleTiles.Contains(tile))
  189. SetTileVisibility(tile, true);
  190. oldVisibleTiles.Clear();
  191. RefreshDoorVisibilities();
  192. }
  193. protected virtual void RefreshDoorVisibilities()
  194. {
  195. foreach (var door in allDoors)
  196. {
  197. bool visible = visibleTiles.Contains(door.DoorwayA.Tile) || visibleTiles.Contains(door.DoorwayB.Tile);
  198. SetDoorVisibility(door, visible);
  199. }
  200. }
  201. protected virtual void SetDoorVisibility(Door door, bool visible)
  202. {
  203. List<Renderer> renderers;
  204. if (doorRenderers.TryGetValue(door, out renderers))
  205. {
  206. for (int i = renderers.Count - 1; i >= 0; i--)
  207. {
  208. var renderer = renderers[i];
  209. if (renderer == null)
  210. {
  211. renderers.RemoveAt(i);
  212. continue;
  213. }
  214. // Check for overridden renderer visibility
  215. bool visibleOverride;
  216. if (OverrideRendererVisibilities.TryGetValue(renderer, out visibleOverride))
  217. renderer.enabled = visibleOverride;
  218. else
  219. renderer.enabled = visible;
  220. }
  221. }
  222. }
  223. protected virtual void UpdateVisibleTiles()
  224. {
  225. visibleTiles.Clear();
  226. if (currentTile != null)
  227. visibleTiles.Add(currentTile);
  228. int processTileStart = 0;
  229. // Add neighbours down to RoomDepth (0 = just tiles containing characters, 1 = plus adjacent tiles, etc)
  230. for (int i = 0; i < AdjacentTileDepth; i++)
  231. {
  232. int processTileEnd = visibleTiles.Count;
  233. for (int t = processTileStart; t < processTileEnd; t++)
  234. {
  235. var tile = visibleTiles[t];
  236. // Get all connections to adjacent tiles
  237. foreach (var doorway in tile.UsedDoorways)
  238. {
  239. var adjacentTile = doorway.ConnectedDoorway.Tile;
  240. // Skip the tile if it's already visible
  241. if (visibleTiles.Contains(adjacentTile))
  242. continue;
  243. // No need to add adjacent rooms to the visible list when the door between them is closed
  244. if (CullBehindClosedDoors)
  245. {
  246. var door = doorway.DoorComponent;
  247. if (door != null && door.ShouldCullBehind)
  248. continue;
  249. }
  250. visibleTiles.Add(adjacentTile);
  251. }
  252. }
  253. processTileStart = processTileEnd;
  254. }
  255. }
  256. protected virtual void SetTileVisibility(Tile tile, bool visible)
  257. {
  258. tileVisibilities[tile] = visible;
  259. // Renderers
  260. List<Renderer> renderers;
  261. if (tileRenderers.TryGetValue(tile, out renderers))
  262. {
  263. for (int i = renderers.Count - 1; i >= 0; i--)
  264. {
  265. var renderer = renderers[i];
  266. if (renderer == null)
  267. {
  268. renderers.RemoveAt(i);
  269. continue;
  270. }
  271. // Check for overridden renderer visibility
  272. bool visibleOverride;
  273. if (OverrideRendererVisibilities.TryGetValue(renderer, out visibleOverride))
  274. renderer.enabled = visibleOverride;
  275. else
  276. renderer.enabled = visible;
  277. }
  278. }
  279. // Lights
  280. List<Light> lights;
  281. if (lightSources.TryGetValue(tile, out lights))
  282. {
  283. for (int i = lights.Count - 1; i >= 0; i--)
  284. {
  285. var light = lights[i];
  286. if (light == null)
  287. {
  288. lights.RemoveAt(i);
  289. continue;
  290. }
  291. // Check for overridden renderer visibility
  292. bool visibleOverride;
  293. if (OverrideLightVisibilities.TryGetValue(light, out visibleOverride))
  294. light.enabled = visibleOverride;
  295. else
  296. light.enabled = visible;
  297. }
  298. }
  299. // Reflection Probes
  300. List<ReflectionProbe> probes;
  301. if (reflectionProbes.TryGetValue(tile, out probes))
  302. {
  303. for (int i = probes.Count - 1; i >= 0; i--)
  304. {
  305. var probe = probes[i];
  306. if (probe == null)
  307. {
  308. probes.RemoveAt(i);
  309. continue;
  310. }
  311. probe.enabled = visible;
  312. }
  313. }
  314. if (TileVisibilityChanged != null)
  315. TileVisibilityChanged(tile, visible);
  316. }
  317. public virtual void UpdateRendererLists()
  318. {
  319. foreach (var tile in allTiles)
  320. {
  321. // Renderers
  322. List<Renderer> renderers;
  323. if (!tileRenderers.TryGetValue(tile, out renderers))
  324. tileRenderers[tile] = renderers = new List<Renderer>();
  325. foreach (var renderer in tile.GetComponentsInChildren<Renderer>())
  326. if (IncludeDisabledComponents || (renderer.enabled && renderer.gameObject.activeInHierarchy))
  327. renderers.Add(renderer);
  328. // Lights
  329. List<Light> lights;
  330. if (!lightSources.TryGetValue(tile, out lights))
  331. lightSources[tile] = lights = new List<Light>();
  332. foreach (var light in tile.GetComponentsInChildren<Light>())
  333. if (IncludeDisabledComponents || (light.enabled && light.gameObject.activeInHierarchy))
  334. lights.Add(light);
  335. // Reflection Probes
  336. List<ReflectionProbe> probes;
  337. if (!reflectionProbes.TryGetValue(tile, out probes))
  338. reflectionProbes[tile] = probes = new List<ReflectionProbe>();
  339. foreach (var probe in tile.GetComponentsInChildren<ReflectionProbe>())
  340. if (IncludeDisabledComponents || (probe.enabled && probe.gameObject.activeInHierarchy))
  341. probes.Add(probe);
  342. }
  343. foreach (var door in allDoors)
  344. {
  345. List<Renderer> renderers = new List<Renderer>();
  346. doorRenderers[door] = renderers;
  347. foreach (var r in door.GetComponentsInChildren<Renderer>(true))
  348. if (IncludeDisabledComponents || (r.enabled && r.gameObject.activeInHierarchy))
  349. renderers.Add(r);
  350. }
  351. }
  352. protected Tile FindCurrentTile()
  353. {
  354. if (dungeon == null)
  355. return null;
  356. foreach (var tile in dungeon.AllTiles)
  357. {
  358. if (tile.Bounds.Contains(targetTransform.position))
  359. return tile;
  360. }
  361. return null;
  362. }
  363. protected Tile SearchForNewCurrentTile()
  364. {
  365. if (tilesToSearch == null)
  366. tilesToSearch = new Queue<Tile>();
  367. if (searchedTiles == null)
  368. searchedTiles = new List<Tile>();
  369. // Add all tiles adjacent to currentTile to the search queue
  370. foreach (var door in currentTile.UsedDoorways)
  371. {
  372. var adjacentTile = door.ConnectedDoorway.Tile;
  373. if (!tilesToSearch.Contains(adjacentTile))
  374. tilesToSearch.Enqueue(adjacentTile);
  375. }
  376. // Breadth-first search to find the tile which contains the player
  377. while (tilesToSearch.Count > 0)
  378. {
  379. var tile = tilesToSearch.Dequeue();
  380. if (tile.Bounds.Contains(targetTransform.position))
  381. {
  382. tilesToSearch.Clear();
  383. searchedTiles.Clear();
  384. return tile;
  385. }
  386. else
  387. {
  388. searchedTiles.Add(tile);
  389. foreach (var door in tile.UsedDoorways)
  390. {
  391. var adjacentTile = door.ConnectedDoorway.Tile;
  392. if (!tilesToSearch.Contains(adjacentTile) &&
  393. !searchedTiles.Contains(adjacentTile))
  394. tilesToSearch.Enqueue(adjacentTile);
  395. }
  396. }
  397. }
  398. searchedTiles.Clear();
  399. return null;
  400. }
  401. }
  402. }