diff --git a/Runtime/Grid2D/Common/Diagnostics/Checks/NotEnoughDoors.cs b/Runtime/Grid2D/Common/Diagnostics/Checks/NotEnoughDoors.cs new file mode 100644 index 00000000..25d95468 --- /dev/null +++ b/Runtime/Grid2D/Common/Diagnostics/Checks/NotEnoughDoors.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Edgar.GraphBasedGenerator.Grid2D; +using UnityEngine; + +namespace Edgar.Unity.Diagnostics +{ + public class NotEnoughDoors + { + public Result Run(LevelDescriptionBase levelDescription) + { + var roomTemplates = new List(); + var levelDescriptionGrid2D = levelDescription.GetLevelDescription(); + foreach (var room in levelDescription.GetGraph().Vertices) + { + var roomDescription = levelDescriptionGrid2D.GetRoomDescription(room); + if (!roomDescription.IsCorridor) + { + roomTemplates.AddRange(roomDescription.RoomTemplates); + } + } + roomTemplates = roomTemplates.Distinct().ToList(); + + var result = new Result + { + Summary = "" + }; + + var summaries = new List() + { + DoorsOnAllSides(roomTemplates, result), + }; + + var summariesWithoutNulls = summaries.Where(x => x != null).ToList(); + if (summariesWithoutNulls.Count == 0) + { + return result; + } + + var sb = new StringBuilder(); + sb.AppendLine("For the performance of the generator, it is important to add many door positions to individual room templates."); + sb.AppendLine("It is even more critical if you want to generate levels with cycles."); + sb.AppendLine("A good starting point is to use the Simple door mode that provides many door positions."); + sb.AppendLine(""); + sb.AppendLine("We detected potential issues related to doors below:"); + sb.AppendLine(""); + + result.IsPotentialProblem = true; + + result.Summary = sb.ToString(); + result.Summary += string.Join("\n", summariesWithoutNulls); + + return result; + } + + private string DoorsOnAllSides(List roomTemplates, Result result) + { + var hasAllDirectionsDoors = false; + foreach (var roomTemplate in roomTemplates) + { + var doors = roomTemplate.Doors.GetDoors(roomTemplate.Outline); + var distinctDirections = doors + .Select(x => x.Line.GetDirection()) + .Distinct() + .Count(); + + if (distinctDirections >= 4) + { + hasAllDirectionsDoors = true; + break; + } + } + + if (hasAllDirectionsDoors) + { + return null; + } + + result.MissingDoorsOnAllSides = true; + + var sb = new StringBuilder(); + sb.AppendLine("There are no room templates that have doors on all 4 sides."); + sb.AppendLine("Such room templates are useful for the generator because they are often very versatile and can be used for rooms with many neighbors."); + sb.AppendLine("They are also very useful if you want to have cycles in your level graph."); + + return sb.ToString(); + } + + public class Result : IDiagnosticResult + { + public string Name => "Not enough doors"; + + public string Summary { get; set; } + + public bool IsPotentialProblem { get; set; } + + public bool MissingDoorsOnAllSides { get; set; } + } + } +} \ No newline at end of file diff --git a/Runtime/Grid2D/Common/Diagnostics/Checks/NotEnoughDoors.cs.meta b/Runtime/Grid2D/Common/Diagnostics/Checks/NotEnoughDoors.cs.meta new file mode 100644 index 00000000..bee0d821 --- /dev/null +++ b/Runtime/Grid2D/Common/Diagnostics/Checks/NotEnoughDoors.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 878fd06d72ce4741a1dafd026e3c8a37 +timeCreated: 1702625812 \ No newline at end of file diff --git a/Runtime/Grid2D/Common/Diagnostics/Diagnostics.cs b/Runtime/Grid2D/Common/Diagnostics/Diagnostics.cs index ba3e8a76..5daa2b82 100644 --- a/Runtime/Grid2D/Common/Diagnostics/Diagnostics.cs +++ b/Runtime/Grid2D/Common/Diagnostics/Diagnostics.cs @@ -32,6 +32,7 @@ public static List Run(LevelDescriptionGrid2D levelDescriptio results.Add(new WrongPositionGameObjects().Run(levelDescription)); results.Add(new OddCycles().Run(levelDescription)); results.Add(new CorridorTypes().Run(levelDescription)); + results.Add(new NotEnoughDoors().Run(levelDescription)); return results; } diff --git a/Tests/Runtime/Scenes/TimeoutDiagnostics/Level graphs/NotEnoughDoorsNoFourSides.asset b/Tests/Runtime/Scenes/TimeoutDiagnostics/Level graphs/NotEnoughDoorsNoFourSides.asset new file mode 100644 index 00000000..0b1a35a4 --- /dev/null +++ b/Tests/Runtime/Scenes/TimeoutDiagnostics/Level graphs/NotEnoughDoorsNoFourSides.asset @@ -0,0 +1,139 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-7877986336414098987 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8f692f83df7c1b5468af64edd22287dc, type: 3} + m_Name: + m_EditorClassIdentifier: + From: {fileID: -3829538508782324871} + To: {fileID: 3289652443752732884} +--- !u!114 &-7307270355954133241 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8f692f83df7c1b5468af64edd22287dc, type: 3} + m_Name: + m_EditorClassIdentifier: + From: {fileID: -3649007993993380384} + To: {fileID: 3289652443752732884} +--- !u!114 &-3829538508782324871 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8074dc491ddc34f4e83332d9eae76980, type: 3} + m_Name: + m_EditorClassIdentifier: + Position: {x: 240, y: 208} + Name: Room + IndividualRoomTemplates: [] + RoomTemplateSets: [] +--- !u!114 &-3649007993993380384 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8074dc491ddc34f4e83332d9eae76980, type: 3} + m_Name: + m_EditorClassIdentifier: + Position: {x: 352, y: 144} + Name: Room + IndividualRoomTemplates: [] + RoomTemplateSets: [] +--- !u!114 &-3110113770019691134 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8074dc491ddc34f4e83332d9eae76980, type: 3} + m_Name: + m_EditorClassIdentifier: + Position: {x: 464, y: 208} + Name: Room + IndividualRoomTemplates: [] + RoomTemplateSets: [] +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b8400baee4cafd34091c865089a167b2, type: 3} + m_Name: NotEnoughDoorsNoFourSides + m_EditorClassIdentifier: + Connections: + - {fileID: -7307270355954133241} + - {fileID: -7877986336414098987} + - {fileID: 792699257021706418} + CorridorIndividualRoomTemplates: [] + CorridorRoomTemplateSets: [] + DefaultIndividualRoomTemplates: + - {fileID: 3020086594291796898, guid: 0e63bd7168493a94e9ffd4b0566f2965, type: 3} + DefaultRoomTemplateSets: [] + Rooms: + - {fileID: -3829538508782324871} + - {fileID: 3289652443752732884} + - {fileID: -3110113770019691134} + - {fileID: -3649007993993380384} + EditorData: + PanOffset: {x: 0, y: 0} + Zoom: 1 + RoomType: Edgar.Unity.Room + ConnectionType: Edgar.Unity.Connection +--- !u!114 &792699257021706418 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8f692f83df7c1b5468af64edd22287dc, type: 3} + m_Name: + m_EditorClassIdentifier: + From: {fileID: 3289652443752732884} + To: {fileID: -3110113770019691134} +--- !u!114 &3289652443752732884 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8074dc491ddc34f4e83332d9eae76980, type: 3} + m_Name: + m_EditorClassIdentifier: + Position: {x: 352, y: 208} + Name: Room + IndividualRoomTemplates: [] + RoomTemplateSets: [] diff --git a/Tests/Runtime/Scenes/TimeoutDiagnostics/Level graphs/NotEnoughDoorsNoFourSides.asset.meta b/Tests/Runtime/Scenes/TimeoutDiagnostics/Level graphs/NotEnoughDoorsNoFourSides.asset.meta new file mode 100644 index 00000000..5caa8777 --- /dev/null +++ b/Tests/Runtime/Scenes/TimeoutDiagnostics/Level graphs/NotEnoughDoorsNoFourSides.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a94d731b80196b34bae9d1eaba013383 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Scenes/TimeoutDiagnostics/TimeoutDiagnostic.unity b/Tests/Runtime/Scenes/TimeoutDiagnostics/TimeoutDiagnostic.unity index de42109f..88f1e8a4 100644 --- a/Tests/Runtime/Scenes/TimeoutDiagnostics/TimeoutDiagnostic.unity +++ b/Tests/Runtime/Scenes/TimeoutDiagnostics/TimeoutDiagnostic.unity @@ -729,6 +729,82 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &931995039 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 931995041} + - component: {fileID: 931995040} + m_Layer: 0 + m_Name: NotEnoughDoorsNoFourSides + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &931995040 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 931995039} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 11725bdbb93631241b38951f78cae60c, type: 3} + m_Name: + m_EditorClassIdentifier: + version: 3 + EnableDiagnostics: 0 + FixedLevelGraphConfig: + LevelGraph: {fileID: 11400000, guid: a94d731b80196b34bae9d1eaba013383, type: 2} + UseCorridors: 0 + GeneratorConfig: + RootGameObject: {fileID: 0} + Timeout: 1 + RepeatModeOverride: 0 + MinimumRoomDistance: 1 + RoomTemplatePrefabMode: 0 + PostProcessConfig: + InitializeSharedTilemaps: 1 + TilemapLayersStructure: 0 + TilemapLayersHandler: {fileID: 0} + TilemapLayersExample: {fileID: 0} + TilemapMaterial: {fileID: 0} + CopyTilesToSharedTilemaps: 1 + CenterGrid: 1 + DisableRoomTemplatesRenderers: 1 + DisableRoomTemplatesColliders: 1 + CustomPostProcessTasks: [] + OtherConfig: + UseRandomSeed: 1 + RandomGeneratorSeed: 0 + GenerateOnStart: 1 + AdvancedConfig: + ThrowExceptionsImmediately: 0 + UseRandomSeed: 0 + RandomGeneratorSeed: 0 + GenerateOnStart: 0 + ThrowExceptionsImmediately: 0 + DisableCustomPostProcessing: 0 +--- !u!4 &931995041 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 931995039} + 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: 12 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1020620959 GameObject: m_ObjectHideFlags: 0 diff --git a/Tests/Runtime/TimeoutDiagnosticTests.cs b/Tests/Runtime/TimeoutDiagnosticTests.cs index 47fbea5c..b824d06a 100644 --- a/Tests/Runtime/TimeoutDiagnosticTests.cs +++ b/Tests/Runtime/TimeoutDiagnosticTests.cs @@ -146,6 +146,19 @@ public void CorridorTypesHorizontal() Assert.That(result.CorridorTypes, Is.EquivalentTo(new List() { CorridorTypes.CorridorType.Horizontal })); Assert.That(result.IsPotentialProblem, Is.True); } + + [Test] + public void NotEnoughDoorsNoFourSides() + { + var dungeonGeneratorGameObject = GameObject.Find("NotEnoughDoorsNoFourSides"); + var dungeonGenerator = dungeonGeneratorGameObject.GetComponent(); + + var exception = Assert.Throws(() => dungeonGenerator.Generate()); + var result = GetResult(exception); + + Assert.That(result.MissingDoorsOnAllSides, Is.True); + Assert.That(result.IsPotentialProblem, Is.True); + } private TResult GetResult(TimeoutException exception, bool allowOnlySingle = true) where TResult : class {