Skip to content

Instantly share code, notes, and snippets.

@crappygraphix
Last active May 21, 2021 22:46
Show Gist options
  • Save crappygraphix/6a0598af0f1d00991b467f7632c82d4b to your computer and use it in GitHub Desktop.
Save crappygraphix/6a0598af0f1d00991b467f7632c82d4b 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();
}
}
@modality
Copy link

This is awesome, and the one thing I was missing in this package. Thank you for sharing!

@crappygraphix
Copy link
Author

This is awesome, and the one thing I was missing in this package. Thank you for sharing!

Glad to hear it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment