#if UNITY_EDITOR using FIMSpace.FEditor; using UnityEditor; #endif using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; namespace FIMSpace.Generating.Planning { [AddComponentMenu("FImpossible Creations/PGG/Building Plan Generator", 1)] public class BuildingPlanGenerator : MonoBehaviour { public bool GenrateOnGameStart = false; public bool RandomSeed = true; public int Seed = 0; [Space(3)] public BuildPlanPreset BuildingFloorPreset; [Range(0f, 0.49f)] [HideInInspector] public float WallsSeparation = 0f; private PlanHelper planHelper; [HideInInspector] public List Generated = new List(); [HideInInspector] public UnityEvent RunAfterGenerating; [HideInInspector] public bool LimitSize = false; [HideInInspector] public Vector2Int SizeLimitX = new Vector2Int(-10, 10); [HideInInspector] public Vector2Int SizeLimitZ = new Vector2Int(-10, 10); [HideInInspector] public bool UseGuides = false; public List PlanGuides = new List(); [HideInInspector] public bool _Editor_drawAdd = false; [System.Serializable] public class PlanPathGuide { public Vector2Int Start = new Vector2Int(-3, -6); public EPlanGuideDirecion StartDir = EPlanGuideDirecion.Back; public Vector2Int End = new Vector2Int(3, 6); public EPlanGuideDirecion EndDir = EPlanGuideDirecion.Forward; [Range(1, 5)] public int CellsSpace = 1; [Range(0f, 1f)] public float ChangeDirCost = .35f; } public struct RoomGuide { int MinDistanceTo; float MinDistance; } private void Start() { if (GenrateOnGameStart) Generate(WallsSeparation); } public PlanGeneratingHelpContainer GenerateScheme(float wallsSeparation) { ClearGenerated(); if (RandomSeed) Seed = FGenerators.GetRandom(-99999, 99999); if (BuildingFloorPreset == null) return null; // Generating building floor plan FGenerators.SetSeed(Seed); planHelper = new PlanHelper(BuildingFloorPreset); if (LimitSize) planHelper.SetLimits(SizeLimitX, SizeLimitZ); if (UseGuides) { for (int i = 0; i < PlanGuides.Count; i++) { var g = PlanGuides[i]; planHelper.GeneratePathFindedCorridor(g.Start, g.End, g.StartDir.GetDirection2D(), g.EndDir.GetDirection2D(), g.CellsSpace, g.ChangeDirCost); } } planHelper.GenerateCorridors(BuildingFloorPreset.RootChunkSetup.InternalSetup.TargetBranches.GetRandom() - 1, wallsSeparation); planHelper.GenerateRooms(wallsSeparation); // Preparing corridors grid -------------------------------------- PlanGeneratingHelpContainer planContainer = new PlanGeneratingHelpContainer(); planContainer.grid = IGeneration.GetEmptyFieldGraph(); planContainer.guides = new List(); // First generating corridors with ID = -1 -> building up grid for corridors and spawning for (int i = 0; i < planHelper.InteriorRects.Count; i++) { PlanHelper.HelperRect room = planHelper.InteriorRects[i]; if (room.TypeID != -1) continue; planContainer.planRect = room; room.GenerateGraphCells(planContainer.grid); List restr = room.GetRestrictionsList(); for (int cr = 0; cr < restr.Count; cr++) { for (int rcl = 0; rcl < restr[cr].Cells.Count; rcl++) planContainer.guides.Add(restr[cr].Cells[rcl].GenerateGuide(planHelper.PlanPreset.RootChunkSetup.FieldSetup, restr[cr])); } } List fromCorridorGuides = new List(); // Reserving places for corridor door wall holes for (int c = 0; c < planHelper.ConnectionRects.Count; c++) { var connection = planHelper.ConnectionRects[c]; if (connection.Found == false) continue; if (connection.Connection1.TypeID == -1 || connection.Connection2.TypeID == -1) { var sett = BuildingFloorPreset.Settings[connection.Connection1.IndividualID]; if (connection.Connection1.TypeID == -1) sett = planHelper.PlanPreset.CorridorSetup; var guide = connection.GenerateGuide(sett.FieldSetup, sett, false); var checkCell = planContainer.grid.GetCell(guide.gridPosition.x, guide.gridPosition.y, guide.gridPosition.z, false); // Detecting that connection grid cell is on the other side of corridor (inside other room door cell) if (checkCell == null || checkCell.InTargetGridArea == false) { fromCorridorGuides.Add(guide); var sett2 = BuildingFloorPreset.Settings[connection.Connection2.IndividualID]; if (connection.Connection2.TypeID == -1) sett2 = planHelper.PlanPreset.CorridorSetup; guide = connection.GenerateGuide(sett2.FieldSetup, sett2, true, 1.1f); //guide = connection.GenerateGuide(EHelperGuideType.Doors, true, 1.1f); planContainer.guides.Add(guide); } //else //{ // fromCorridorGuides.Add(guide); // //UnityEngine.Debug.Log("guide pos " + guide.gridPosition); //} //UnityEngine.Debug.Log("guide pos " + guide.gridPosition); //corridors.guides.Add(guide); } } // Preparing interior rooms graphs ----------------------------------------------------------------- planContainer.interiors = new List(); // First generate all interior containers to communicate between structures later for (int i = 0; i < planHelper.InteriorRects.Count; i++) { var room = planHelper.InteriorRects[i]; if (room.TypeID == -1) continue; PlanGeneratingHelpContainer interior = new PlanGeneratingHelpContainer(); interior.planRect = room; interior.grid = IGeneration.GetEmptyFieldGraph(); room.GenerateGraphCells(interior.grid); interior.guides = new List(); List restr = room.GetRestrictionsList(); for (int cr = 0; cr < restr.Count; cr++) { for (int rcl = 0; rcl < restr[cr].Cells.Count; rcl++) interior.guides.Add(restr[cr].Cells[rcl].GenerateGuide(room.SettingsRef.FieldSetup, restr[cr])); } planContainer.interiors.Add(interior); } // Generating door connections between rooms and between room and corridor from room side for (int i = 0; i < planContainer.interiors.Count; i++) { PlanGeneratingHelpContainer interior = planContainer.interiors[i]; var room = interior.planRect; // Checking each room for connections // Reserving places for doors for (int c = 0; c < planHelper.ConnectionRects.Count; c++) { var connection = planHelper.ConnectionRects[c]; if (connection.Found == false) continue; if (connection.Connection1.TypeID != room.TypeID && connection.Connection2.TypeID != room.TypeID) continue; if (connection.Connection1.TypeID == -1) continue; if (connection.Connection1.TypeID == room.TypeID) // Reserving doors for first room side { SpawnInstruction guide; // From room to room or from room to corridor if (connection.Connection1.TypeID != -1) { var sett = planHelper.PlanPreset.Settings[connection.Connection1.IndividualID]; guide = connection.GenerateGuide(sett.FieldSetup, sett); interior.guides.Add(guide); } // From room to another room on other side without corridor connection if (connection.Connection1.TypeID != -1 && connection.Connection2.TypeID != -1) { PlanGeneratingHelpContainer connectionInterior = new PlanGeneratingHelpContainer() { guides = null }; // Getting counter connection room for (int s = 0; s < planContainer.interiors.Count; s++) if (planContainer.interiors[s].planRect.IndividualID == connection.Connection2.IndividualID) { connectionInterior = planContainer.interiors[s]; if (connectionInterior.guides != null) { var sett2 = planHelper.PlanPreset.Settings[connection.Connection2.IndividualID]; guide = connection.GenerateGuide(sett2.FieldSetup, sett2, true); connectionInterior.guides.Add(guide); } } } } } } lastGenerated = planContainer; return planContainer; } PlanGeneratingHelpContainer lastGenerated = null; public void Generate(float wallsSeparation) { //if (RandomSeed) Seed = FGenerators.GetRandom(-99999, 99999); //ClearGenerated(); if (BuildingFloorPreset == null) return; PlanGeneratingHelpContainer scheme = GenerateScheme(wallsSeparation); if (scheme == null) return; // Spawning corridor AddToGenerated(IGeneration.GenerateFieldObjects(BuildingFloorPreset.RootChunkSetup.FieldSetup, scheme.grid, GenerateTransformContainer(scheme), true, scheme.guides, null, true).Instantiated); // Generating Interiors for (int i = 0; i < scheme.interiors.Count; i++) { if (scheme.interiors[i].planRect.TypeID == -1) continue; var sch = BuildingFloorPreset.Settings[scheme.interiors[i].planRect.IndividualID]; if (sch == null) { UnityEngine.Debug.Log("No Scheme! " + i); continue; } var setup = sch.FieldSetup; if (setup == null) { UnityEngine.Debug.Log("No Field Setup! " + i + " in " + BuildingFloorPreset.Settings[scheme.interiors[i].planRect.IndividualID].GetName()); continue; } if (sch.InjectMods != null) if (sch.InjectMods.Count > 0) setup.SetTemporaryInjections(sch.InjectMods); AddToGenerated( IGeneration.GenerateFieldObjects ( setup, scheme.interiors[i].grid, GenerateTransformContainer(scheme.interiors[i]), true, scheme.interiors[i].guides, scheme.interiors[i].planRect.totalSepOffset, true ).Instantiated); if (sch.InjectMods != null) if (sch.InjectMods.Count > 0) setup.ClearTemporaryInjections(); } if (RunAfterGenerating != null) RunAfterGenerating.Invoke(); lastGenerated = scheme; } public void ClearGenerated() { for (int i = 0; i < Generated.Count; i++) if (Generated[i] != null) FGenerators.DestroyObject(Generated[i]); Generated.Clear(); } public void AddToGenerated(List list) { if (list == null) return; for (int i = 0; i < list.Count; i++) if (!Generated.Contains(list[i])) Generated.Add(list[i]); } public Transform GenerateTransformContainer(PlanGeneratingHelpContainer targetContainer) { // TODO: On build no parenting? GameObject cnt = new GameObject(); if (targetContainer.planRect.SettingsRef != null) { cnt.name = targetContainer.planRect.SettingsRef.GetName(); if (targetContainer.planRect.TypeID == -1) cnt.name = "Corridor-" + cnt.name; } cnt.transform.SetParent(transform); cnt.transform.localPosition = Vector3.zero; cnt.transform.localRotation = Quaternion.identity; Generated.Add(cnt); return cnt.transform; } #if UNITY_EDITOR public bool DrawDebugGrid = false; private void OnDrawGizmosSelected() { if (DrawDebugGrid == false) return; if (planHelper == null) return; if (planHelper.PlanPreset == null) return; if (planHelper.PlanPreset.RootChunkSetup == null) return; if (planHelper.PlanPreset.RootChunkSetup.FieldSetup == null) return; Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one); Handles.matrix = Gizmos.matrix; Vector3 csize = planHelper.PlanPreset.RootChunkSetup.FieldSetup.GetCellUnitSize(); csize.y *= 0.1f; float size = csize.x; for (int i = 0; i < planHelper.InteriorRects.Count; i++) { var room = planHelper.InteriorRects[i]; var roomCells = room.GenerateGraphCells(); if (room.SettingsRef != null) if (room.SettingsRef.FieldSetup != null) size = room.SettingsRef.FieldSetup.CellSize; foreach (var cell in roomCells) { Gizmos.color = planHelper.PlanPreset.GetIDColor(room.TypeID, 0.3f); Gizmos.DrawWireCube(cell.WorldPos(size), new Vector3(size, 0.1f, size)); Handles.Label(cell.WorldPos(size), new GUIContent(cell.PosXZ.ToString()), EditorStyles.centeredGreyMiniLabel); } } if (lastGenerated != null) if (lastGenerated.guides != null) { Gizmos.color = new Color(1f, 1f, 0.1f, 0.7f); //for (int i = 0; i < lastGenerated.guides.Count; i++) //{ // var guide = lastGenerated.guides[i]; // Gizmos.DrawWireCube(guide.gridPosition * (int)size + guide.desiredDirection * (int)(size * 0.4f), new Vector3(size * 0.5f, 0.1f, size * 0.5f)); //} for (int i = 0; i < lastGenerated.interiors.Count; i++) { for (int g = 0; g < lastGenerated.interiors[i].guides.Count; g++) { var guide = lastGenerated.interiors[i].guides[i]; Gizmos.DrawWireCube(guide.gridPosition * (int)size + guide.desiredDirection * (int)(size * 0.4f), new Vector3(size * 0.5f, 0.1f, size * 0.5f)); } } } if (UseGuides) { for (int i = 0; i < PlanGuides.Count; i++) { var g = PlanGuides[i]; Gizmos.color = new Color(0.4f, 0.4f, 0.4f, 0.5f); Vector3 pos = new Vector3(g.Start.x, 0, g.Start.y) * size - csize; Vector3 d = g.StartDir.GetDirection() * size; Gizmos.DrawRay(pos, d); Gizmos.DrawLine(pos + d, Vector3.Lerp(pos, pos + d, 0.7f) + Vector3.right * 0.12f * size); Gizmos.DrawLine(pos + d, Vector3.Lerp(pos, pos + d, 0.7f) - Vector3.right * 0.12f * size); Gizmos.DrawCube(pos, csize); pos = new Vector3(g.End.x, 0, g.End.y) * size - csize; d = g.EndDir.GetDirection() * size; Gizmos.DrawRay(pos, d); Gizmos.DrawLine(pos + d, Vector3.Lerp(pos, pos + d, 0.7f) + Vector3.right * 0.12f * size); Gizmos.DrawLine(pos + d, Vector3.Lerp(pos, pos + d, 0.7f) - Vector3.right * 0.12f * size); Gizmos.DrawCube(pos, csize); } } if (LimitSize) { Gizmos.color = new Color(0.1f, 0.1f, 0.1f, 0.85f); Vector3 off = new Vector3(csize.x * 0.5f, 0f, csize.z * -0.5f); Gizmos.DrawLine(new Vector3(SizeLimitX.x, 0, SizeLimitZ.x) * size + off, new Vector3(SizeLimitX.y, 0, SizeLimitZ.x) * size + off); Gizmos.DrawLine(new Vector3(SizeLimitX.y, 0, SizeLimitZ.x) * size + off, new Vector3(SizeLimitX.y, 0, SizeLimitZ.y) * size + off); Gizmos.DrawLine(new Vector3(SizeLimitX.y, 0, SizeLimitZ.y) * size + off, new Vector3(SizeLimitX.x, 0, SizeLimitZ.y) * size + off); Gizmos.DrawLine(new Vector3(SizeLimitX.x, 0, SizeLimitZ.y) * size + off, new Vector3(SizeLimitX.x, 0, SizeLimitZ.x) * size + off); } Gizmos.matrix = Matrix4x4.identity; Handles.matrix = Matrix4x4.identity; } #endif /// /// Helper container to make generating algorithm more readable /// public class PlanGeneratingHelpContainer { public PlanHelper.HelperRect planRect; public FGenGraph grid; public List guides; public List interiors; } } #if UNITY_EDITOR [UnityEditor.CanEditMultipleObjects] [UnityEditor.CustomEditor(typeof(BuildingPlanGenerator))] public class ExampleBuildingPlanGeneratorEditor : UnityEditor.Editor { public BuildingPlanGenerator Get { get { if (_get == null) _get = (BuildingPlanGenerator)target; return _get; } } private BuildingPlanGenerator _get; bool displayEvent = false; SerializedProperty sp_add; private void OnEnable() { sp_add = serializedObject.FindProperty("LimitSize"); } public override void OnInspectorGUI() { EditorGUILayout.HelpBox("This component is using old algorithms, it may be deleted in future versions!", MessageType.Warning); EditorGUILayout.HelpBox("This component is not supporting non-rectangle shapes for rooms!", MessageType.None); FGUI_Inspector.LastGameObjectSelected = Get.gameObject; serializedObject.Update(); DrawPropertiesExcluding(serializedObject, "PlanGuides"); if (Get.BuildingFloorPreset != null) { EditorGUILayout.PropertyField(serializedObject.FindProperty("WallsSeparation")); } GUILayout.Space(4); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Preview")) { Get.GenerateScheme(Get.WallsSeparation); SceneView.RepaintAll(); } if (GUILayout.Button("Generate")) Get.Generate(Get.WallsSeparation); EditorGUILayout.EndHorizontal(); if (Get.Generated != null) if (Get.Generated.Count > 0) if (GUILayout.Button("Clear Generated")) Get.ClearGenerated(); displayEvent = EditorGUILayout.Foldout(displayEvent, "Event After Generating", true); if (displayEvent) EditorGUILayout.PropertyField(serializedObject.FindProperty("RunAfterGenerating")); GUILayout.Space(5); EditorGUILayout.BeginVertical(FGUI_Resources.BGInBoxStyle); GUILayout.Space(3); string ff = Get._Editor_drawAdd ? "▼" : "▲"; if (GUILayout.Button(ff + " Additional Parameters " + ff, FGUI_Resources.HeaderStyle)) Get._Editor_drawAdd = !Get._Editor_drawAdd; GUILayout.Space(3); if (Get._Editor_drawAdd) { SerializedProperty sp = sp_add.Copy(); EditorGUILayout.PropertyField(sp); sp.Next(false); if (Get.LimitSize) EditorGUILayout.PropertyField(sp); sp.Next(false); if (Get.LimitSize) EditorGUILayout.PropertyField(sp); sp.Next(false); GUILayout.Space(7); /*EditorGUILayout.PropertyField(sp); //Fill fully*/ EditorGUILayout.PropertyField(sp); sp.Next(false); EditorGUILayout.PropertyField(sp); sp.Next(false); if (Get.UseGuides) { GUILayout.Space(3); EditorGUILayout.PropertyField(sp); } } EditorGUILayout.EndVertical(); serializedObject.ApplyModifiedProperties(); if (Get.BuildingFloorPreset == null) { EditorGUILayout.HelpBox("No Build Plan Preset Assigned!", MessageType.Warning); } else { if (Get.BuildingFloorPreset.RootChunkSetup != null) if (Get.BuildingFloorPreset.RootChunkSetup.FieldSetup == null) EditorGUILayout.HelpBox("Corridor Preset is not assigned inside Build Plan!", MessageType.Warning); } } } #endif }