using System; using System.Collections.Generic; using UnityEngine; namespace DunGen { [AddComponentMenu("DunGen/Culling/Adjacent Room Culling")] public class AdjacentRoomCulling : MonoBehaviour { public delegate void VisibilityChangedDelegate(Tile tile, bool visible); /// /// How deep from the current room should tiles be considered visibile /// 0 = Only the current tile /// 1 = The current tile and all its neighbours /// 2 = The current tile, all its neighbours, and all THEIR neighbours /// etc... /// public int AdjacentTileDepth = 1; /// /// If true, tiles behind a closed door will be culled, even if they're within /// public bool CullBehindClosedDoors = true; /// /// If set, this transform will be used as the vantage point that rooms should be culled from. /// Useful for third person games where you want to cull from the character's position, not the camera /// public Transform TargetOverride = null; /// /// Whether culling should handle any components that start disabled /// public bool IncludeDisabledComponents = false; /// /// A set of override values for specific renderers. /// By default, this script will overwrite any renderer.enabled values we might set in /// gameplay code. This property lets us tell the culling that we want to override the /// visibility values its setting /// [NonSerialized] public Dictionary OverrideRendererVisibilities = new Dictionary(); /// /// A set of override values for specific lights. /// By default, this script will overwrite any light.enabled values we might set in /// gameplay code. This property lets us tell the culling that we want to override the /// visibility values its setting /// [NonSerialized] public Dictionary OverrideLightVisibilities = new Dictionary(); /// /// True when a dungeon has been assigned and we're ready to start culling /// public bool Ready { get; protected set; } public event VisibilityChangedDelegate TileVisibilityChanged; protected List allTiles; protected List allDoors; protected List oldVisibleTiles; protected List visibleTiles; protected Dictionary tileVisibilities; protected Dictionary> tileRenderers; protected Dictionary> lightSources; protected Dictionary> reflectionProbes; protected Dictionary> doorRenderers; protected Transform targetTransform { get { return (TargetOverride != null) ? TargetOverride : transform; } } private bool dirty; private DungeonGenerator generator; private Tile currentTile; private Queue tilesToSearch; private List searchedTiles; private Dungeon dungeon; protected virtual void OnEnable() { var runtimeDungeon = UnityUtil.FindObjectByType(); if (runtimeDungeon != null) { generator = runtimeDungeon.Generator; generator.OnGenerationStatusChanged += OnDungeonGenerationStatusChanged; ; if (generator.Status == GenerationStatus.Complete) SetDungeon(generator.CurrentDungeon); } } protected virtual void OnDisable() { if (generator != null) generator.OnGenerationStatusChanged -= OnDungeonGenerationStatusChanged; ClearDungeon(); } public virtual void SetDungeon(Dungeon newDungeon) { if (Ready) ClearDungeon(); dungeon = newDungeon; if (dungeon == null) return; allTiles = new List(dungeon.AllTiles); allDoors = new List(GetAllDoorsInDungeon(dungeon)); oldVisibleTiles = new List(allTiles.Count); visibleTiles = new List(allTiles.Count); tileVisibilities = new Dictionary(); tileRenderers = new Dictionary>(); lightSources = new Dictionary>(); reflectionProbes = new Dictionary>(); doorRenderers = new Dictionary>(); UpdateRendererLists(); foreach (var tile in allTiles) SetTileVisibility(tile, false); foreach (var door in allDoors) { door.OnDoorStateChanged += OnDoorStateChanged; SetDoorVisibility(door, false); } Ready = true; dirty = true; } public virtual bool IsTileVisible(Tile tile) { bool visibility; if (tileVisibilities.TryGetValue(tile, out visibility)) return visibility; else return false; } protected IEnumerable GetAllDoorsInDungeon(Dungeon dungeon) { foreach (var doorObj in dungeon.Doors) { if (doorObj == null) continue; var door = doorObj.GetComponent(); if (door != null) yield return door; } } protected virtual void ClearDungeon() { if (!Ready) return; foreach (var door in allDoors) { SetDoorVisibility(door, true); door.OnDoorStateChanged -= OnDoorStateChanged; } foreach (var tile in allTiles) SetTileVisibility(tile, true); Ready = false; } protected virtual void OnDoorStateChanged(Door door, bool isOpen) { dirty = true; } protected virtual void OnDungeonGenerationStatusChanged(DungeonGenerator generator, GenerationStatus status) { if (status == GenerationStatus.Complete) SetDungeon(generator.CurrentDungeon); else if (status == GenerationStatus.Failed) ClearDungeon(); } protected virtual void LateUpdate() { if (!Ready) return; var oldTile = currentTile; // If currentTile doesn't exist, we need to first look for a dungeon, // then search every tile to find one that encompasses this GameObject if (currentTile == null) currentTile = FindCurrentTile(); // If currentTile does exist, but we're not in it, we can perform a // breadth-first search radiating from currentTile. Assuming the player // is likely to be in an adjacent room, this should be much quicker than // testing every tile in the dungeon else if (!currentTile.Bounds.Contains(targetTransform.position)) currentTile = SearchForNewCurrentTile(); if (currentTile != oldTile) dirty = true; if (dirty) RefreshVisibility(); dirty = false; } protected virtual void RefreshVisibility() { var temp = visibleTiles; visibleTiles = oldVisibleTiles; oldVisibleTiles = temp; UpdateVisibleTiles(); // Hide any tiles that are no longer visible foreach (var tile in oldVisibleTiles) if (!visibleTiles.Contains(tile)) SetTileVisibility(tile, false); // Show tiles that are newly visible foreach (var tile in visibleTiles) if (!oldVisibleTiles.Contains(tile)) SetTileVisibility(tile, true); oldVisibleTiles.Clear(); RefreshDoorVisibilities(); } protected virtual void RefreshDoorVisibilities() { foreach (var door in allDoors) { bool visible = visibleTiles.Contains(door.DoorwayA.Tile) || visibleTiles.Contains(door.DoorwayB.Tile); SetDoorVisibility(door, visible); } } protected virtual void SetDoorVisibility(Door door, bool visible) { List renderers; if (doorRenderers.TryGetValue(door, out renderers)) { for (int i = renderers.Count - 1; i >= 0; i--) { var renderer = renderers[i]; if (renderer == null) { renderers.RemoveAt(i); continue; } // Check for overridden renderer visibility bool visibleOverride; if (OverrideRendererVisibilities.TryGetValue(renderer, out visibleOverride)) renderer.enabled = visibleOverride; else renderer.enabled = visible; } } } protected virtual void UpdateVisibleTiles() { visibleTiles.Clear(); if (currentTile != null) visibleTiles.Add(currentTile); int processTileStart = 0; // Add neighbours down to RoomDepth (0 = just tiles containing characters, 1 = plus adjacent tiles, etc) for (int i = 0; i < AdjacentTileDepth; i++) { int processTileEnd = visibleTiles.Count; for (int t = processTileStart; t < processTileEnd; t++) { var tile = visibleTiles[t]; // Get all connections to adjacent tiles foreach (var doorway in tile.UsedDoorways) { var adjacentTile = doorway.ConnectedDoorway.Tile; // Skip the tile if it's already visible if (visibleTiles.Contains(adjacentTile)) continue; // No need to add adjacent rooms to the visible list when the door between them is closed if (CullBehindClosedDoors) { var door = doorway.DoorComponent; if (door != null && door.ShouldCullBehind) continue; } visibleTiles.Add(adjacentTile); } } processTileStart = processTileEnd; } } protected virtual void SetTileVisibility(Tile tile, bool visible) { tileVisibilities[tile] = visible; // Renderers List renderers; if (tileRenderers.TryGetValue(tile, out renderers)) { for (int i = renderers.Count - 1; i >= 0; i--) { var renderer = renderers[i]; if (renderer == null) { renderers.RemoveAt(i); continue; } // Check for overridden renderer visibility bool visibleOverride; if (OverrideRendererVisibilities.TryGetValue(renderer, out visibleOverride)) renderer.enabled = visibleOverride; else renderer.enabled = visible; } } // Lights List lights; if (lightSources.TryGetValue(tile, out lights)) { for (int i = lights.Count - 1; i >= 0; i--) { var light = lights[i]; if (light == null) { lights.RemoveAt(i); continue; } // Check for overridden renderer visibility bool visibleOverride; if (OverrideLightVisibilities.TryGetValue(light, out visibleOverride)) light.enabled = visibleOverride; else light.enabled = visible; } } // Reflection Probes List probes; if (reflectionProbes.TryGetValue(tile, out probes)) { for (int i = probes.Count - 1; i >= 0; i--) { var probe = probes[i]; if (probe == null) { probes.RemoveAt(i); continue; } probe.enabled = visible; } } if (TileVisibilityChanged != null) TileVisibilityChanged(tile, visible); } public virtual void UpdateRendererLists() { foreach (var tile in allTiles) { // Renderers List renderers; if (!tileRenderers.TryGetValue(tile, out renderers)) tileRenderers[tile] = renderers = new List(); foreach (var renderer in tile.GetComponentsInChildren()) if (IncludeDisabledComponents || (renderer.enabled && renderer.gameObject.activeInHierarchy)) renderers.Add(renderer); // Lights List lights; if (!lightSources.TryGetValue(tile, out lights)) lightSources[tile] = lights = new List(); foreach (var light in tile.GetComponentsInChildren()) if (IncludeDisabledComponents || (light.enabled && light.gameObject.activeInHierarchy)) lights.Add(light); // Reflection Probes List probes; if (!reflectionProbes.TryGetValue(tile, out probes)) reflectionProbes[tile] = probes = new List(); foreach (var probe in tile.GetComponentsInChildren()) if (IncludeDisabledComponents || (probe.enabled && probe.gameObject.activeInHierarchy)) probes.Add(probe); } foreach (var door in allDoors) { List renderers = new List(); doorRenderers[door] = renderers; foreach (var r in door.GetComponentsInChildren(true)) if (IncludeDisabledComponents || (r.enabled && r.gameObject.activeInHierarchy)) renderers.Add(r); } } protected Tile FindCurrentTile() { if (dungeon == null) return null; foreach (var tile in dungeon.AllTiles) { if (tile.Bounds.Contains(targetTransform.position)) return tile; } return null; } protected Tile SearchForNewCurrentTile() { if (tilesToSearch == null) tilesToSearch = new Queue(); if (searchedTiles == null) searchedTiles = new List(); // Add all tiles adjacent to currentTile to the search queue foreach (var door in currentTile.UsedDoorways) { var adjacentTile = door.ConnectedDoorway.Tile; if (!tilesToSearch.Contains(adjacentTile)) tilesToSearch.Enqueue(adjacentTile); } // Breadth-first search to find the tile which contains the player while (tilesToSearch.Count > 0) { var tile = tilesToSearch.Dequeue(); if (tile.Bounds.Contains(targetTransform.position)) { tilesToSearch.Clear(); searchedTiles.Clear(); return tile; } else { searchedTiles.Add(tile); foreach (var door in tile.UsedDoorways) { var adjacentTile = door.ConnectedDoorway.Tile; if (!tilesToSearch.Contains(adjacentTile) && !searchedTiles.Contains(adjacentTile)) tilesToSearch.Enqueue(adjacentTile); } } } searchedTiles.Clear(); return null; } } }