이제 던전 노드 그래프 SO를 기반으로 던전 빌더 클래스를 만들어보자. 던전 빌더는 레벨에 따라 무작위로 방을 선택하고 각 방을 다른 방과 겹치지 않도록 만들어서 던전을 만들 수 있도록 한다.
/// <summary>
/// Get room node by roomNodeType
/// </summary>
public RoomNodeSO GetRoomNode(RoomNodeTypeSO roomNodeType)
{
foreach (RoomNodeSO node in roomNodeList)
{
if (node.roomNodeType == roomNodeType)
{
return node;
}
}
return null;
}
/// <summary>
/// Get child room nodes for supplied parent room node
/// </summary>
public IEnumerable<RoomNodeSO> GetChildRoomNodes(RoomNodeSO parentRoomNode)
{
foreach (string childNodeID in parentRoomNode.childRoomNodeIDList)
{
yield return GetRoomNode(childNodeID);
}
}
#region DUNGEON BUILD SETTINGS
public const int maxDungeonRebuildAttemptsForRoomGraph = 1000;
public const int maxDungeonBuildAttempts = 10;
#endregion
#region Header MATERIALS
[Space(10)]
[Header("MATERIALS")]
#endregion
#region Tooltip
[Tooltip("Dimmed Material")]
#endregion
public Material dimmedMaterial;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
[DisallowMultipleComponent]
public class DungeonBuilder : SingletonMonoBehaviour<DungeonBuilder>
{
public Dictionary<string, Room> dungeonBuilderRoomDictionary = new Dictionary<string, Room>();
private Dictionary<string, RoomTemplateSO> roomTemplateDictionary = new Dictionary<string, RoomTemplateSO>();
private List<RoomTemplateSO> roomTemplateList = null;
private RoomNodeTypeListSO roomNodeTypeList;
private bool dungeonBuildSuccessful;
protected override void Awake()
{
base.Awake();
// Load the room node type list
LoadRoomNodeTypeList();
// set Dimmed marterial to fully visible
GameResources.Instance.dimmedMaterial.SetFloat("Alpha_Slider", 1f);
}
/// <summary>
/// Load the room node type list
/// </summary>
private void LoadRoomNodeTypeList()
{
roomNodeTypeList = GameResources.Instance.roomNodeTypeList;
}
/// <summary>
/// Generate random dungeon, returns true if dungeon built, flase is failed
/// </summary>
public bool GenerateDungeon(DungeonLevelSO currentDungeonLevel)
{
roomTemplateList = currentDungeonLevel.roomTemplateList;
// Load the scriptable object room templates into the dictionary
LoadRoomTemplatesIntoDictionary();
dungeonBuildSuccessful = false;
int dungeonBuildAttempts = 0;
while (!dungeonBuildSuccessful && dungeonBuildAttempts < Settings.maxDungeonBuildAttempts)
{
dungeonBuildAttempts++;
// Select a random room node graph from the list
RoomNodeGraphSO roomNodeGraph = SelectRandomRoomNodeGraph(currentDungeonLevel.roomNodeGraphList);
int dungeonRebuildAttemptsForNodeGraph = 0;
dungeonBuildSuccessful = false;
// Loop until dungeon successfully built or more than max attempts for node graph
while (!dungeonBuildSuccessful && dungeonRebuildAttemptsForNodeGraph <= Settings.maxDungeonRebuildAttemptsForRoomGraph)
{
// Clear dungeon room gameobjects and dungeon room dictionary
ClearDungeon();
dungeonRebuildAttemptsForNodeGraph++;
// Attempt To Build A Random Dungeon For The Selected room node graph
dungeonBuildSuccessful = AttemptToBuildRandomDungeon(roomNodeGraph);
}
if (dungeonBuildSuccessful)
{
// Instantiate Room Gameobjects
InstantiateRoomGameobjects();
}
}
}
/// <summary>
/// Load the room templates into the dictionary
/// </summary>
private void LoadRoomTemplatesIntoDictionary()
{
// Clear room template dictionary
roomTemplateDictionary.Clear();
// Load room template list into dictionary
foreach (RoomTemplateSO roomTemplate in roomTemplateList)
{
if(!roomTemplateDictionary.ContainsKey(roomTemplate.guid))
{
roomTemplateDictionary.Add(roomTemplate.guid, roomTemplate);
}
else
{
Debug.Log("Duplicate Room Template Key In " + roomTemplateList);
}
}
}
/// <summary>
/// Attempt to randomly build the dungeon for the specified room nodeGraph. Returns true if a
/// successful random layout was generated, else returns false if a problem was encountered and
/// another attempt is required
/// </summary>
private bool AttemptToBuildRandomDungeon(RoomNodeGraphSO roomNodeGraph)
{
// Create Open Room Node Queue
Queue<RoomNodeSO> openRoomNodeQueue = new Queue<RoomNodeSO>();
// Add Entrance Node To Room Node Queue From Room Node Graph
RoomNodeSO entranceNode = roomNodeGraph.GetRoomNode(roomNodeTypeList.list.Find(x => x.isEntrance));
if (entranceNode != null)
{
openRoomNodeQueue.Enqueue(entranceNode);
}
else
{
Debug.Log("No Entrance Node");
return false; // Dungeon Not Built
}
// Start with no room overlaps
bool noRoomOverlaps = true;
// Process open room nodes queue
noRoomOverlaps = ProcessRoomsInOpenRoomNodeQueue(roomNodeGraph, openRoomNodeQueue, noRoomOverlaps);
// If all the room nodes have been processed and there hasn't been a room overlap then return true
if (openRoomNodeQueue.Count == 0 && noRoomOverlaps)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// Select a random room node graph from the list of room node graphs
/// </summary>
private RoomNodeGraphSO SelectRandomRoomNodeGraph(List<RoomNodeGraphSO> roomNodeGraphList)
{
if (roomNodeGraphList.Count > 0)
{
return roomNodeGraphList[UnityEngine.Random.Range(0, roomNodeGraphList.Count)];
}
else
{
Debug.Log("No room node graphs in list");
return null;
}
}
/// <summary>
/// Clear dungeon room gameobjects and dungeon room dictionary
/// </summary>
private void ClearDungeon()
{
// Destroy instantiated dungeon gameobjects and clear dungeon manager room dictionary
if (dungeonBuilderRoomDictionary.Count > 0)
{
foreach (KeyValuePair<string, Room> keyvaluepair in dungeonBuilderRoomDictionary)
{
Room room = keyvaluepair.Value;
if (room.instatntiatedRoom != null)
{
Destroy(room.instatntiatedRoom.gameObject);
}
}
dungeonBuilderRoomDictionary.Clear();
}
}
}