Skip to content

Instantly share code, notes, and snippets.

@modality
Forked from crappygraphix/RoomThemer.cs
Created May 21, 2021 20:27
Show Gist options
  • Save modality/6abbd6d175e1218c5abfc26b7a2a7ba4 to your computer and use it in GitHub Desktop.
Save modality/6abbd6d175e1218c5abfc26b7a2a7ba4 to your computer and use it in GitHub Desktop.
Volume Based Randomized Themer
using UnityEngine;
using System.Collections.Generic;
using DungeonArchitect;
using DungeonArchitect.Graphs;
using DungeonArchitect.Utils;
using DungeonArchitect.Flow.Domains.Tilemap;
using DungeonArchitect.Builders.GridFlow;
/// <summary>
/// Spawns various theme override volumes around rooms and corridors.
/// We treat each node as a room and process the tiles under each node to
/// generate volumes.
/// </summary>
public class RoomThemer : DungeonEventListener
{
public Graph startRoomTheme;
public Graph goalRoomTheme;
public Graph[] roomThemes;
/// <summary>
/// The template required to clone and duplicate a theme override volume.
/// Supply the reference of the theme override volume prefab here.
/// </summary>
public Volume themeOverrideVolumePrefab;
Dictionary<IntVector2, Graph> themeDictionary = new Dictionary<IntVector2, Graph>();
// Dictionary<NodeCoord, (farthestSouthWest, farthestNorthEast)>
Dictionary<IntVector2, (Vector3, Vector3)> roomMinMaxDictionary = new Dictionary<IntVector2, (Vector3, Vector3)>();
// Show the volumes we've generated, for debugging purposes.
[SerializeField]
List<GameObject> managedVolumes = new List<GameObject>();
Vector3 gridSize = new Vector3(4, 4, 4);
// Gonna use this for consistentcy
PMRandom random;
public override void OnPostDungeonLayoutBuild(Dungeon dungeon, DungeonModel model)
{
DestroyManagedVolumes();
// Make sure we are working with the grid flow builder
var gridFlowModel = model as GridFlowDungeonModel;
if (gridFlowModel == null) return;
var cfg = GetComponent<GridFlowDungeonConfig>();
gridSize = cfg.gridSize;
random = new PMRandom(cfg.Seed);
BuildDictionaries(gridFlowModel);
Vector3 halfGrid = new Vector3(gridSize.x * 0.5f, gridSize.y * 0.5f, gridSize.z * 0.5f);
// Group all the tiles by their parent node.
foreach (var cell in gridFlowModel.tilemap.Cells)
{
if (cell.CellType != FlowTilemapCellType.Empty) // ignore empty tiles
{
var worldPosition = TileWorldPosition(cell.TileCoord);
// if the node isn't already in the lookup add the lookup and tile position.
if(!roomMinMaxDictionary.ContainsKey(cell.NodeCoord))
{
roomMinMaxDictionary.Add(cell.NodeCoord, (worldPosition, worldPosition));
continue;
}
roomMinMaxDictionary[cell.NodeCoord] = CalcMinMax(roomMinMaxDictionary[cell.NodeCoord], worldPosition);
}
}
// Generate the volumes
foreach(var kv in roomMinMaxDictionary)
{
var lookup = kv.Key;
var southWest = kv.Value.Item1;
var northEast = kv.Value.Item2;
var halfWidth = (northEast.x - southWest.x) * 0.5f;
var halfDepth = (northEast.z - southWest.z) * 0.5f;
var center = new Vector3(southWest.x + halfWidth, gridSize.y, southWest.z + halfDepth);
// Fixed height, and we shift by the grid size to ensure full encapsulation. Otherwise the volume would be based on tile centers.
var scale = new Vector3(halfWidth * 2f, 10f, halfDepth * 2f);
// Spawn the volume object.
var volumeObject = Instantiate(themeOverrideVolumePrefab.gameObject) as GameObject;
volumeObject.transform.position = center;
volumeObject.transform.localScale = scale;
var volume = volumeObject.GetComponent<ThemeOverrideVolume>();
volume.dungeon = dungeon; // Let the volume know that it belongs to this dungeon
volume.overrideTheme = themeDictionary[lookup]; // Assign the theme we'd like this volume to override
// Save a reference to the volume so we can destroy it when it is rebuilt the next time (or we will end up with duplicate volumes on rebuilds)
managedVolumes.Add(volumeObject);
}
}
// Clean up resources.
public override void OnDungeonDestroyed(Dungeon dungeon)
{
DestroyManagedVolumes();
themeDictionary.Clear();
roomMinMaxDictionary.Clear();
}
// Clean up resources.
public override void OnPostDungeonBuild(Dungeon dungeon, DungeonModel model)
{
themeDictionary.Clear();
roomMinMaxDictionary.Clear();
}
void BuildDictionaries(GridFlowDungeonModel gridFlowModel)
{
themeDictionary.Clear();
foreach (var node in gridFlowModel.layoutGraph.Nodes)
{
if (node.pathIndex >= 0) // ignore empty nodes
{
// if starter node then use starter theme
if (node.items.Exists(x => x.markerName == "SpawnPoint"))
{
themeDictionary.Add(new IntVector2((int)node.coord.x, (int)node.coord.y), startRoomTheme);
continue;
}
// if goal node then use goal theme
if (node.items.Exists(x => x.markerName == "LevelGoal"))
{
themeDictionary.Add(new IntVector2((int)node.coord.x, (int)node.coord.y), goalRoomTheme);
continue;
}
// Record a random theme for lookup later.
themeDictionary.Add(new IntVector2((int)node.coord.x, (int)node.coord.y), GetRandomTheme());
}
}
}
(Vector3, Vector3) CalcMinMax((Vector3, Vector3) old, Vector3 p)
{
var southWest = old.Item1;
var northEast = old.Item2;
if (southWest.x > p.x) southWest.x = p.x;
if (southWest.z > p.z) southWest.z = p.z;
// Need to add grid size so we go to the farthest corner.
if (northEast.x < p.x + gridSize.x) northEast.x = p.x + gridSize.x;
if (northEast.z < p.z + gridSize.z) northEast.z = p.z + gridSize.z;
return (southWest, northEast);
}
Vector3 TileWorldPosition(IntVector2 tilePosition)
{
return new Vector3(gridSize.x * tilePosition.x, 0, gridSize.z * tilePosition.y);
}
Graph GetRandomTheme()
{
if (roomThemes.Length == 0)
{
return null;
}
// Pick a random theme from the supplied theme list
return roomThemes[random.UniformRandom.Range(0, roomThemes.Length - 1)];
}
void DestroyManagedVolumes()
{
foreach (var volume in managedVolumes)
{
if (Application.isPlaying)
{
Destroy(volume);
}
else
{
DestroyImmediate(volume);
}
}
managedVolumes.Clear();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment