Created a basic procedural generator. I'm going to rework it as a lot should be improved upon.

This commit is contained in:
That-One-Nerd 2024-12-09 10:17:13 -05:00
parent 27c119eb87
commit 8d66a06213
23 changed files with 675 additions and 0 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
[*.cs]
# IDE0051: Remove unused private members
dotnet_diagnostic.IDE0051.severity = none

8
Assets/Prefabs.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f8c99dcb732bee84cbb9b20584570e82
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 93def57754ce4bd4bad5d5f3a4ecbf9a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3151970cdb0c552418f774a4073b25e6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,234 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1513129216089456175
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 422348202299819723}
- component: {fileID: 2357044271177384697}
m_Layer: 0
m_Name: Door D
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &422348202299819723
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1513129216089456175}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: -4, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 2101345830686511700}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &2357044271177384697
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1513129216089456175}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8ab941518ca8a4642828187df902b1e0, type: 3}
m_Name:
m_EditorClassIdentifier:
Tag: vertical
--- !u!1 &1912872275816598801
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 646947909993633888}
- component: {fileID: 1012738459284175318}
m_Layer: 0
m_Name: Door R
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &646947909993633888
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1912872275816598801}
m_LocalRotation: {x: 0, y: 0, z: 0.7071068, w: 0.7071068}
m_LocalPosition: {x: 5, y: -0.0000011920928, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 2101345830686511700}
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 90}
--- !u!114 &1012738459284175318
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1912872275816598801}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8ab941518ca8a4642828187df902b1e0, type: 3}
m_Name:
m_EditorClassIdentifier:
Tag: horizontal
--- !u!1 &2101345829801090270
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2101345829801090264}
- component: {fileID: 2101345829801090271}
m_Layer: 0
m_Name: Door L
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2101345829801090264
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2101345829801090270}
m_LocalRotation: {x: 0, y: 0, z: -0.7071068, w: 0.7071068}
m_LocalPosition: {x: -5, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 2101345830686511700}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: -90}
--- !u!114 &2101345829801090271
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2101345829801090270}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8ab941518ca8a4642828187df902b1e0, type: 3}
m_Name:
m_EditorClassIdentifier:
Tag: horizontal
--- !u!1 &2101345830686511723
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2101345830686511700}
- component: {fileID: 2101345830686511701}
m_Layer: 0
m_Name: debug.test1
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2101345830686511700
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2101345830686511723}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 7175683798590047672}
- {fileID: 422348202299819723}
- {fileID: 2101345829801090264}
- {fileID: 646947909993633888}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &2101345830686511701
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2101345830686511723}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6268a80b1893dea4da88a977847d0df1, type: 3}
m_Name:
m_EditorClassIdentifier:
Identifier: debug.test1
Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 5, y: 4, z: 0}
Doors:
- {fileID: 5516082331950511926}
- {fileID: 2357044271177384697}
- {fileID: 2101345829801090271}
- {fileID: 1012738459284175318}
--- !u!1 &9158382049854172236
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7175683798590047672}
- component: {fileID: 5516082331950511926}
m_Layer: 0
m_Name: Door U
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7175683798590047672
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9158382049854172236}
m_LocalRotation: {x: 0, y: 0, z: 1, w: 0}
m_LocalPosition: {x: 0, y: 4, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 2101345830686511700}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 180}
--- !u!114 &5516082331950511926
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9158382049854172236}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8ab941518ca8a4642828187df902b1e0, type: 3}
m_Name:
m_EditorClassIdentifier:
Tag: vertical

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e460ac35ac4587e498e7133f9e3962cf
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -118,6 +118,8 @@ NavMeshSettings:
manualTileSize: 0
tileSize: 256
accuratePlacement: 0
maxJobWorkers: 0
preserveTilesOutsideBounds: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
@ -204,3 +206,79 @@ Transform:
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &754085557
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 754085558}
m_Layer: 0
m_Name: Root
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &754085558
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 754085557}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1726388856
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1726388858}
- component: {fileID: 1726388857}
m_Layer: 0
m_Name: Generator
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1726388857
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1726388856}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 97bc65bc89d5f1949914183b68f0e48b, type: 3}
m_Name:
m_EditorClassIdentifier:
DungeonRoot: {fileID: 754085558}
StartingRoomId: debug.test1
RoomCountRange: {x: 20, y: 50}
--- !u!4 &1726388858
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1726388856}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

8
Assets/Scripts.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6d99eb921b17dad4e8658805e779f3d2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0a0b20ea41f112d4898b0127993d03da
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,31 @@
using UnityEngine;
public static class GizmosHelper
{
public static void DrawLine(Transform transform, Vector3 from, Vector3 to) => DrawPoints(transform, new Vector3[2]
{
from,
to
});
public static void DrawPoints(Transform transform, Vector3[] points)
{
// Apply rotation and position.
for (int i = 0; i < points.Length; i++)
{
points[i] = transform.rotation * points[i] + transform.position;
}
// Draw points.
for (int i = 0; i < points.Length; i++)
{
Gizmos.DrawLine(points[i], points[(i + 1) % points.Length]);
}
}
public static void DrawRect(Transform transform, Bounds bounds) => DrawPoints(transform, new Vector3[4]
{
new Vector2(bounds.min.x, bounds.min.y),
new Vector2(bounds.max.x, bounds.min.y),
new Vector2(bounds.max.x, bounds.max.y),
new Vector2(bounds.min.x, bounds.max.y)
});
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d17a502c10c86db4d9e753547debbfec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7a850575e29641144b8292c0da8ddc94
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
using UnityEngine;
public abstract class Singleton<T> : MonoBehaviour
where T : Singleton<T>
{
public static T Instance { get; private set; }
private void Awake()
{
if (Instance != null)
{
Debug.LogWarning($"Duplicate instance of {nameof(T)}! Ignoring this one.");
Destroy(this);
return;
}
Instance = (T)this;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c12da831f15ec254b8eb03f5a6cb2e4c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f0c5cb243264b844fbe832fd51d6355b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,72 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class DungeonGenerator : Singleton<DungeonGenerator>
{
// TODO: Add a weighting system.
public Transform DungeonRoot;
public string StartingRoomId;
public Vector2Int RoomCountRange;
private RoomObject[] possibleRooms;
private void Start()
{
possibleRooms = Resources.LoadAll<RoomObject>("Rooms");
Debug.Log($"Loaded {possibleRooms.Length} room assets.");
MakeDungeon();
}
public void MakeDungeon()
{
int rooms = Random.Range(RoomCountRange.x, RoomCountRange.y + 1);
Debug.Log($"{rooms} rooms will be generated.");
List<RoomDoor> emptyDoors = new List<RoomDoor>();
RoomObject activeRoom = GetRoomById(StartingRoomId, true);
emptyDoors.AddRange(activeRoom.Doors);
while (rooms > 0 && emptyDoors.Count > 0)
{
// Pick random empty door and spawn a room there.
RoomDoor door = emptyDoors[Random.Range(0, emptyDoors.Count)];
RoomDoor matchingDoor = GetRoomDoorByTag(door.Tag, true);
if (matchingDoor == null)
{
emptyDoors.Remove(door);
continue;
}
matchingDoor.SetRoomRotationByDoor(door.transform.rotation.eulerAngles);
matchingDoor.SetRoomPositionByDoor(door.transform.position);
List<RoomDoor> extraDoors = new List<RoomDoor>(matchingDoor.Room.Doors);
extraDoors.Remove(matchingDoor);
emptyDoors.AddRange(extraDoors);
rooms--;
}
}
public RoomObject GetRoomById(string id, bool instantiate)
{
RoomObject[] could = possibleRooms.Where(x => x.Identifier.StartsWith(id)).ToArray();
RoomObject chosen = could[Random.Range(0, could.Length)];
if (instantiate) return Instantiate(chosen, DungeonRoot);
else return chosen;
}
public RoomDoor GetRoomDoorByTag(string tag, bool instantiate)
{
RoomObject[] could = possibleRooms.Where(x => x.Doors.Any(y => y.Tag.StartsWith(tag))).ToArray();
if (could.Length == 0) return null;
RoomObject chosen = could[Random.Range(0, could.Length)];
if (instantiate) chosen = Instantiate(chosen, DungeonRoot);
RoomDoor[] couldDoors = chosen.Doors.Where(x => x.Tag.StartsWith(tag)).ToArray();
return couldDoors[Random.Range(0, couldDoors.Length)];
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 97bc65bc89d5f1949914183b68f0e48b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
using UnityEngine;
public class RoomDoor : RoomItem
{
public string Tag;
public void SetRoomRotationByDoor(Vector3 desiredDoorRotation)
{
Room.transform.rotation *= Quaternion.FromToRotation(Room.transform.rotation.eulerAngles, desiredDoorRotation);
}
public void SetRoomPositionByDoor(Vector2 desiredDoorPosition)
{
Vector2 curPos = transform.position;
Vector2 diff = desiredDoorPosition - curPos;
Room.transform.position += (Vector3)diff;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8ab941518ca8a4642828187df902b1e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,35 @@
using UnityEngine;
[ExecuteAlways]
public abstract class RoomItem : MonoBehaviour
{
public RoomObject Room { get; private set; }
private void Awake()
{
Room = GetComponentInParent<RoomObject>();
}
private void Update()
{
if (!Application.isPlaying)
{
EditorUpdate();
return;
}
}
private void EditorUpdate()
{
Awake();
if (Room == null)
{
Debug.LogError("A room item must have a parent room.");
return;
}
}
private void OnDrawGizmosSelected()
{
Room.OnDrawGizmosSelected();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5650535e833d8ce43b11c4b47b4df190
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,56 @@
using UnityEngine;
[ExecuteAlways]
public class RoomObject : MonoBehaviour
{
public string Identifier;
public Bounds Bounds;
public RoomDoor[] Doors;
private void Awake()
{
Doors = GetComponentsInChildren<RoomDoor>();
}
private void Update()
{
if (!Application.isPlaying)
{
EditorUpdate();
return;
}
}
private void EditorUpdate()
{
Awake();
if (Bounds.extents.z != 0 || Bounds.center.z != 0)
{
Bounds.extents = (Vector2)Bounds.extents;
Bounds.size = (Vector2)Bounds.size;
}
}
internal void OnDrawGizmos()
{
// Draw room mesh.
Gizmos.color = new Color(1, 0.5f, 0); // Orange
foreach (RoomDoor roomDoor in Doors)
{
GizmosHelper.DrawLine(transform, roomDoor.transform.localPosition, Bounds.center);
}
}
internal void OnDrawGizmosSelected()
{
// Draw room bounds.
Gizmos.color = Color.white;
GizmosHelper.DrawRect(transform, Bounds);
// Draw each door.
Gizmos.color = Color.green;
Bounds doorBounds = new Bounds(Vector2.zero, new Vector2(2, 1));
foreach (RoomDoor door in Doors)
{
GizmosHelper.DrawRect(door.transform, doorBounds);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6268a80b1893dea4da88a977847d0df1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: