diff --git a/.vsconfig b/.vsconfig new file mode 100644 index 0000000..f019fd0 --- /dev/null +++ b/.vsconfig @@ -0,0 +1,6 @@ +{ + "version": "1.0", + "components": [ + "Microsoft.VisualStudio.Workload.ManagedGame" + ] +} diff --git a/Assets/Scenes/Main.unity b/Assets/Scenes/Main.unity index c3d2559..c1e3ce1 100644 --- a/Assets/Scenes/Main.unity +++ b/Assets/Scenes/Main.unity @@ -10550,7 +10550,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 1 + m_IsActive: 0 --- !u!4 &552049569 Transform: m_ObjectHideFlags: 0 @@ -13843,19 +13843,22 @@ MonoBehaviour: m_EditorClassIdentifier: Assembly-CSharp::_Project.Scripts.LevelGenerating.WallsGenerator floorsCount: 10 lengthWallMin: 20 - lengthWallMax: 40 + lengthWallMax: 70 lengthStartRoom: 10 lengthLiftRoom: 10 widthLiftRoom: 5 lengthFloor: 100 corridorLength: 5 corridorWigth: 4 - _minDepthLiftRoom: 7 + minRoomCount: 7 + _minDepthLiftRoom: 5 + _minDistanceStartLift: 50 GameObjectA: {fileID: 8066171868259172852, guid: e46748b8eb8a6074d9bb87701cebe84b, type: 3} GameObjectB: {fileID: 4641718306729093824, guid: 3905eab8a5f6b3049a83378b1acf1ebc, type: 3} maxRooms: 15 branchChance: 0.7 roomMargin: 2 + liftDoorAnyPosition: 1 --- !u!4 &740097205 Transform: m_ObjectHideFlags: 0 @@ -41319,7 +41322,7 @@ Transform: m_GameObject: {fileID: 1033791869251556189} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalPosition: {x: 0, y: 10, z: 0} m_LocalScale: {x: 0.5, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: diff --git a/Assets/_Project/Materials/New Material 2.mat b/Assets/_Project/Materials/New Material 2.mat new file mode 100644 index 0000000..c1e27d1 --- /dev/null +++ b/Assets/_Project/Materials/New Material 2.mat @@ -0,0 +1,139 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-1320640688500226478 +MonoBehaviour: + m_ObjectHideFlags: 11 + 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: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion + version: 10 +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: New Material 2 + m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 2000 + stringTagMap: + RenderType: Opaque + disabledShaderPasses: + - MOTIONVECTORS + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AddPrecomputedVelocity: 0 + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 0 + - _Metallic: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.005 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _ScreenSpaceReflections: 1 + - _ScreenSpaceReflectionsContributeTransparent: 1 + - _Smoothness: 1 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _WorkflowMode: 1 + - _XRMotionVectorsPass: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 0.053014517, g: 1, b: 0, a: 1} + - _Color: {r: 0.053014494, g: 1, b: 0, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Assets/_Project/Materials/New Material 2.mat.meta b/Assets/_Project/Materials/New Material 2.mat.meta new file mode 100644 index 0000000..c72eab8 --- /dev/null +++ b/Assets/_Project/Materials/New Material 2.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b4555641771ab6d4c81ed310ee2dc560 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Project/Prefabs/GameObjectC.prefab b/Assets/_Project/Prefabs/GameObjectC.prefab new file mode 100644 index 0000000..a843c55 --- /dev/null +++ b/Assets/_Project/Prefabs/GameObjectC.prefab @@ -0,0 +1,146 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &3836052751326043178 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7087884702110966620} + - component: {fileID: 69987631208049368} + - component: {fileID: 3695676037745970672} + - component: {fileID: 6869080578825851964} + m_Layer: 3 + m_Name: Cube + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7087884702110966620 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3836052751326043178} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0.5, y: 0, z: 0.5} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 1 + m_Children: [] + m_Father: {fileID: 610399867894923410} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &69987631208049368 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3836052751326043178} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &3695676037745970672 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3836052751326043178} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: b4555641771ab6d4c81ed310ee2dc560, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_MaskInteraction: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &6869080578825851964 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3836052751326043178} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &4641718306729093824 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 610399867894923410} + m_Layer: 0 + m_Name: GameObjectC + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &610399867894923410 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4641718306729093824} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 10, y: 7.01, z: -0.52} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 1 + m_Children: + - {fileID: 7087884702110966620} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/_Project/Prefabs/GameObjectC.prefab.meta b/Assets/_Project/Prefabs/GameObjectC.prefab.meta new file mode 100644 index 0000000..65ce460 --- /dev/null +++ b/Assets/_Project/Prefabs/GameObjectC.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 222a3fec38ee4e94da1bdcccb5c71aae +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Project/Scripts/LevelGenerating/FloorData.cs b/Assets/_Project/Scripts/LevelGenerating/FloorData.cs new file mode 100644 index 0000000..a8486b2 --- /dev/null +++ b/Assets/_Project/Scripts/LevelGenerating/FloorData.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; +using System.Linq; + +namespace _Project.Scripts.LevelGenerating +{ + public enum FloorKind + { + Generated, // обычный этаж: раскладка -> роли -> стены -> наполнение + Special // готовая локация (крыша с боссом), собрана вручную, генератор не нужен + } + + // ===================================================================== + // Данные ОДНОГО этажа. Заменяет статический LevelManager.Rooms. + // Все координаты и размеры комнат - в МОДУЛЯХ (1 модуль = 0.5 м), см. LevelGrid. + // + // НЕ ЗАБЫТЬ: + // - Для сохранения в файл этот класс НЕ годится напрямую: Room использует свойства, + // которые JsonUtility не сериализует. Для диска нужны отдельные структуры (FloorSave) + // с публичными полями, а FloorData остаётся рабочим объектом генерации. + // - Parent / CorridorOf живут в WallsGenerator (нужны только при генерации раскладки). + // ===================================================================== + public class FloorData + { + public int Index; // номер этажа (0-based) + public FloorKind Kind; + public int Seed; // пока не используется (сид "пох"), поле на будущее + public string SpecialId; // для Special: id префаба локации, например "BossRoof" + public int SizeModules; // сторона карты этажа в модулях (для Generated) + + public readonly List Rooms = new List(); + public readonly Dictionary Depth = new Dictionary(); + + public Room Start; // стартовая комната (вход с предыдущего этажа) + public Room Lift; // комната с лифтом (выход на следующий этаж) + + // ---------- Выборки ---------- + + public IEnumerable Corridors => Rooms.Where(r => r.rt == RoomType.Corridor); + + // Всё, кроме коридоров (старт, лифт и обычные комнаты) + public IEnumerable PlayableRooms => Rooms.Where(r => r.rt != RoomType.Corridor); + + // Комнаты, которым ещё не назначена роль + public IEnumerable UnassignedRooms => Rooms.Where(r => r.rt == RoomType.Undefined); + + public IEnumerable OfType(RoomType type) => Rooms.Where(r => r.rt == type); + + public int GetDepth(Room room) => Depth.TryGetValue(room, out int d) ? d : -1; + + // ---------- Изменение ---------- + + // Добавить комнату (не коридор) вместе с глубиной + public void AddRoom(Room room, int depth) + { + Rooms.Add(room); + Depth[room] = depth; + + if (room.rt == RoomType.Start) Start = room; + else if (room.rt == RoomType.Lift) Lift = room; + } + + // Коридоры глубины не имеют + public void AddCorridor(Room corridor) => Rooms.Add(corridor); + + public void Remove(Room room) + { + Rooms.Remove(room); + Depth.Remove(room); + if (Start == room) Start = null; + if (Lift == room) Lift = null; + } + + public void Clear() + { + Rooms.Clear(); + Depth.Clear(); + Start = null; + Lift = null; + } + + // ---------- Фабрики ---------- + + public static FloorData CreateGenerated(int index, int sizeModules) => + new FloorData { Index = index, Kind = FloorKind.Generated, SizeModules = sizeModules }; + + public static FloorData CreateSpecial(int index, string specialId) => + new FloorData { Index = index, Kind = FloorKind.Special, SpecialId = specialId }; + } +} \ No newline at end of file diff --git a/Assets/_Project/Scripts/LevelGenerating/FloorData.cs.meta b/Assets/_Project/Scripts/LevelGenerating/FloorData.cs.meta new file mode 100644 index 0000000..435b1a2 --- /dev/null +++ b/Assets/_Project/Scripts/LevelGenerating/FloorData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c44c2a7978314fbeb585ae58fa82d95d +timeCreated: 1791498732 \ No newline at end of file diff --git a/Assets/_Project/Scripts/LevelGenerating/Room.cs b/Assets/_Project/Scripts/LevelGenerating/Room.cs index 08b0908..60b1245 100644 --- a/Assets/_Project/Scripts/LevelGenerating/Room.cs +++ b/Assets/_Project/Scripts/LevelGenerating/Room.cs @@ -3,9 +3,31 @@ using UnityEngine; namespace _Project.Scripts.LevelGenerating { + // ===================================================================== + // НЕ ЗАБЫТЬ (напоминание себе): + // Все координаты и размеры в Room хранятся в МОДУЛЯХ (1 модуль = 0.5 м), а не в метрах. + // - В инспекторе WallsGenerator размеры задаются в метрах и переводятся в модули (x2). + // - В мировые координаты переводить ТОЛЬКО через LevelGrid.ToWorld(...). + // - Размеры комнат всегда ЧЁТНЫЕ в модулях (целые метры), иначе центровка по коридору + // даст дробные позиции. Это обеспечивает WallsGenerator (метод Even). + // ===================================================================== + public static class LevelGrid + { + public const float Module = 0.5f; + public const int PerMeter = 2; + + public static int FromMeters(int meters) => meters * PerMeter; + + public static Vector3 ToWorld(Vector2Int cell, float y = 0f) => + new Vector3(cell.x * Module, y, cell.y * Module); + + public static Vector3 ToWorld(Vector2 cell, float y = 0f) => + new Vector3(cell.x * Module, y, cell.y * Module); + } + public class Room { - public Room(Vector2 position, int width, int length, RoomType rt):this() + public Room(Vector2Int position, int width, int length, RoomType rt) : this() { Position = position; Width = width; @@ -18,9 +40,8 @@ namespace _Project.Scripts.LevelGenerating exits = new List(); } - public Room(Vector2 position, int width, int length, RoomType rt, EntranceToRoomType entrance):this() + public Room(Vector2Int position, int width, int length, RoomType rt, EntranceToRoomType entrance) : this() { - Position = position; Width = width; Length = length; @@ -28,14 +49,15 @@ namespace _Project.Scripts.LevelGenerating this.entrance = entrance; } - public Room(int width, int length, RoomType rt):this() + public Room(int width, int length, RoomType rt) : this() { Width = width; Length = length; this.rt = rt; } - public Room(Vector2 position, int width, int length, RoomType rt, bool isWallable, EntranceToRoomType entrance, List exits) + public Room(Vector2Int position, int width, int length, RoomType rt, bool isWallable, + EntranceToRoomType entrance, List exits) { Position = position; Width = width; @@ -46,14 +68,14 @@ namespace _Project.Scripts.LevelGenerating this.exits = exits; } - public int Width { get; set; }//width - по оси z - public int Length { get; set; }//length- по оси x - public Vector2 Position; + public int Width { get; set; } // по оси Z, в модулях + public int Length { get; set; } // по оси X, в модулях + public Vector2Int Position; // левый нижний угол, в модулях public RoomType rt { get; set; } - public EntranceToRoomType entrance { get; set; } - public bool isWallable { get; set; } - public List exits { get; set; } - + public EntranceToRoomType entrance { get; set; } + public bool isWallable { get; set; } // по факту: комнату ужали об границу карты (стоит переименовать) + public List exits { get; set; } + + public Vector2 Center => new Vector2(Position.x + Length / 2f, Position.y + Width / 2f); } - } \ No newline at end of file diff --git a/Assets/_Project/Scripts/LevelGenerating/RoomType.cs b/Assets/_Project/Scripts/LevelGenerating/RoomType.cs index 1638d42..5365909 100644 --- a/Assets/_Project/Scripts/LevelGenerating/RoomType.cs +++ b/Assets/_Project/Scripts/LevelGenerating/RoomType.cs @@ -14,7 +14,7 @@ namespace _Project.Scripts.LevelGenerating SwitchboardRoom, BoilerRoom } - + public enum ExitsInRoomType { XAxisP, diff --git a/Assets/_Project/Scripts/LevelGenerating/WallsGenerator.cs b/Assets/_Project/Scripts/LevelGenerating/WallsGenerator.cs index 34a1632..ed24b20 100644 --- a/Assets/_Project/Scripts/LevelGenerating/WallsGenerator.cs +++ b/Assets/_Project/Scripts/LevelGenerating/WallsGenerator.cs @@ -5,8 +5,18 @@ using Random = UnityEngine.Random; namespace _Project.Scripts.LevelGenerating { + // ===================================================================== + // НЕ ЗАБЫТЬ: + // 1. Поля инспектора - в МЕТРАХ. Внутри генератора всё в МОДУЛЯХ (0.5 м), см. ReadSettings(). + // 2. Размеры комнат только чётные в модулях (целые метры): метод Even(). + // 3. Данные этажа лежат в FloorData (_data). Статический LevelManager.Rooms больше не используется. + // 4. TODO: переименовать Room.isWallable (сейчас значит "комнату ужали об край карты"). + // 5. TODO: спавн маркеров в Generate() временный. Он переедет в стадию "оболочка" (стены, пол). + // 6. Этаж с боссом (крыша) сюда не попадает: это FloorKind.Special, собирается вручную. + // ===================================================================== public class WallsGenerator : MonoBehaviour { + // ---- Настройки в метрах ---- [SerializeField] private int floorsCount; [SerializeField] private int lengthWallMin; [SerializeField] private int lengthWallMax; @@ -16,21 +26,30 @@ namespace _Project.Scripts.LevelGenerating [SerializeField] private int lengthFloor; [SerializeField] private int corridorLength; [SerializeField] private int corridorWigth; + [SerializeField] private int minRoomCount; [SerializeField] private int _minDepthLiftRoom; + [SerializeField] private float _minDistanceStartLift; // метры [SerializeField] private GameObject GameObjectA; [SerializeField] private GameObject GameObjectB; - [Header("Генерация")] [SerializeField] private int maxRooms = 15; + [Header("Генерация")] + [SerializeField] private int maxRooms = 15; [SerializeField, Range(0f, 1f)] private float branchChance = 0.7f; - [SerializeField] private float roomMargin = 2f; // зазор между комнатами + [SerializeField] private float roomMargin = 2f; // зазор между комнатами, метры + [SerializeField] private bool liftDoorAnyPosition = false; // true, если дверь лифта может быть не по центру + // ---- Те же настройки в модулях (заполняет ReadSettings) ---- + private int _wallMin, _wallMax, _startSize, _liftLen, _liftWid, _floor, _corrLen, _corrWid, _margin; + private float _minDist; + + // ---- Состояние генерации ---- + private FloorData _data; private int _curRooms; - private readonly Dictionary _depth = new Dictionary(); private readonly Dictionary _parent = new Dictionary(); - [SerializeField] private bool liftDoorAnyPosition = false; // true, если дверь в префабе может быть не по центру private readonly Dictionary _corridorOf = new Dictionary(); - private LevelManager levelManager; + + public FloorData Data => _data; private static readonly ExitsInRoomType[] AllDirs = { @@ -38,176 +57,235 @@ namespace _Project.Scripts.LevelGenerating ExitsInRoomType.ZAxisP, ExitsInRoomType.ZAxisN }; + private static readonly RoomType[] ClippedRoles = { RoomType.Laboratory, RoomType.Office, RoomType.Penthouse }; + private static readonly RoomType[] FullRoles = { RoomType.Storage, RoomType.SwitchboardRoom, RoomType.BoilerRoom }; + void Start() { - levelManager = LevelManager.Instance; - GenerateFloor(); + ReadSettings(); + Generate(0); // позже этим будет управлять оркестратор } - bool PlaceElevator() -{ - while (true) - { - if (TryAttachLift(true, roomMargin)) return true; // норма: глубина >= минимума - if (TryAttachLift(false, roomMargin)) return true; // без требования глубины - if (TryAttachLift(false, 0f)) return true; // без зазора - if (!PruneRandomLeaf()) return false; // освобождаем место и повторяем - } -} - -private ExitsInRoomType[] LiftEntryDirs() -{ - if (lengthLiftRoom > widthLiftRoom) return new[] { ExitsInRoomType.ZAxisP, ExitsInRoomType.ZAxisN }; - if (widthLiftRoom > lengthLiftRoom) return new[] { ExitsInRoomType.XAxisP, ExitsInRoomType.XAxisN }; - return AllDirs; -} - -// Сдвиги лифта вдоль стены, чтобы коридор всё равно попал в стену -private IEnumerable LiftShifts(ExitsInRoomType dir) -{ - yield return 0; - if (!liftDoorAnyPosition) yield break; - - bool xDir = dir == ExitsInRoomType.XAxisP || dir == ExitsInRoomType.XAxisN; - int cross = xDir ? widthLiftRoom : lengthLiftRoom; - int max = (cross - corridorWigth) / 2; - for (int s = 1; s <= max; s++) { yield return s; yield return -s; } -} - -bool TryAttachLift(bool requireMinDepth, float margin) -{ - var start = levelManager.GetRooms().First(r => r.rt == RoomType.Start); - - var parents = _depth.Keys - .OrderByDescending(r => _depth[r]) - .ThenByDescending(r => (r.Position - start.Position).sqrMagnitude) - .ToList(); - - foreach (var parent in parents) - { - if (requireMinDepth && _depth[parent] + 1 < _minDepthLiftRoom) continue; - - foreach (var dir in LiftEntryDirs().OrderBy(_ => Random.value)) + void ReadSettings() { - if (parent.exits.Contains(dir)) continue; - - foreach (int shift in LiftShifts(dir)) - { - Room lift = new Room { Length = lengthLiftRoom, Width = widthLiftRoom }; - PlaceByEntry(EntryPoint(parent, dir), dir, lift); - - bool xDir = dir == ExitsInRoomType.XAxisP || dir == ExitsInRoomType.XAxisN; - lift.Position += xDir ? new Vector2(0, shift) : new Vector2(shift, 0); - - if (!CanPlaceLift(parent, lift, dir, margin)) continue; - - lift.rt = RoomType.Lift; - lift.exits = new List(); - lift.entrance = ToEntrance(dir); - lift.isWallable = false; - - levelManager.AddRoom(lift); - _depth[lift] = _depth[parent] + 1; - _parent[lift] = (parent, dir); - _corridorOf[lift] = CreateCorridor(parent, dir); - parent.exits.Add(dir); - _curRooms++; - return true; - } + _wallMin = LevelGrid.FromMeters(lengthWallMin); + _wallMax = LevelGrid.FromMeters(lengthWallMax); + _startSize = LevelGrid.FromMeters(lengthStartRoom); + _liftLen = LevelGrid.FromMeters(lengthLiftRoom); + _liftWid = LevelGrid.FromMeters(widthLiftRoom); + _floor = LevelGrid.FromMeters(lengthFloor); + _corrLen = LevelGrid.FromMeters(corridorLength); + _corrWid = LevelGrid.FromMeters(corridorWigth); + _margin = Mathf.RoundToInt(roomMargin * LevelGrid.PerMeter); + _minDist = _minDistanceStartLift * LevelGrid.PerMeter; } - } - return false; -} -bool CanPlaceLift(Room parent, Room lift, ExitsInRoomType dir, float margin) -{ - if (!InBounds(lift)) return false; - if (!IsAreaFree(CorridorRect(parent, dir))) return false; - - Rect area = RoomRect(lift); - float m = Mathf.Min(margin, corridorLength); - area.xMin -= m; area.yMin -= m; area.xMax += m; area.yMax += m; - return IsAreaFree(area, parent); -} - -// Удаляет случайную тупиковую комнату вместе с её коридором -bool PruneRandomLeaf() -{ - var parentsSet = new HashSet(_parent.Values.Select(v => v.parent)); - var leaves = _parent.Keys.Where(r => !parentsSet.Contains(r)).ToList(); - if (leaves.Count == 0) return false; - - Room leaf = leaves[Random.Range(0, leaves.Count)]; - var (p, dir) = _parent[leaf]; - - LevelManager.Rooms.Remove(leaf); - if (_corridorOf.TryGetValue(leaf, out var cor)) - { - LevelManager.Rooms.Remove(cor); - _corridorOf.Remove(leaf); - } - p.exits.Remove(dir); - _depth.Remove(leaf); - _parent.Remove(leaf); - _curRooms--; - return true; -} - -// Позиция комнаты заданного размера относительно точки входа -private static void PlaceByEntry(Vector2 e, ExitsInRoomType dir, Room n) -{ - switch (dir) - { - case ExitsInRoomType.XAxisP: n.Position = new Vector2(e.x, e.y - n.Width / 2f); break; - case ExitsInRoomType.XAxisN: n.Position = new Vector2(e.x - n.Length, e.y - n.Width / 2f); break; - case ExitsInRoomType.ZAxisP: n.Position = new Vector2(e.x - n.Length / 2f, e.y); break; - default: n.Position = new Vector2(e.x - n.Length / 2f, e.y - n.Width); break; - } -} - - private bool InBounds(Room r) => - r.Position.x >= 0 && r.Position.y >= 0 && - r.Position.x + r.Length <= lengthFloor && - r.Position.y + r.Width <= lengthFloor; - -// Комнаты проверяются с зазором, коридоры без него (иначе зазор заденет собственный коридор лифта) - - void GenerateFloor() + void OnValidate() + { + lengthWallMax = Mathf.Max(lengthWallMax, lengthWallMin); + minRoomCount = Mathf.Min(minRoomCount, maxRooms + 1); // +1 за лифт + + if (lengthFloor < lengthStartRoom + corridorLength + Mathf.Max(lengthLiftRoom, widthLiftRoom)) + Debug.LogWarning("lengthFloor слишком мал для старта + коридора + лифта"); + } + + // ---------- Генерация этажа ---------- + + void ResetState(int floorIndex) + { + _data = FloorData.CreateGenerated(floorIndex, _floor); + _parent.Clear(); + _corridorOf.Clear(); + _curRooms = 0; + } + + public FloorData Generate(int floorIndex) { - const int maxAttempts = 50; bool ok = false; - for (int i = 0; i < maxAttempts && !ok; i++) + // Фаза 1: все ограничения, без обрезки веток + for (int i = 0; i < 50 && !ok; i++) { - LevelManager.Rooms.Clear(); - _depth.Clear(); - _parent.Clear(); - _corridorOf.Clear(); - _curRooms = 0; + ResetState(floorIndex); + CreateStartRoom(); + ok = TryAttachLift(true, _margin, _minDist) && _curRooms >= minRoomCount; + } + // Фаза 2 (аварийная): лифт обязателен, остальное ослабляется + for (int i = 0; i < 20 && !ok; i++) + { + ResetState(floorIndex); CreateStartRoom(); ok = PlaceElevator(); + if (ok) Debug.LogWarning("Этаж собран в аварийном режиме: ограничения ослаблены"); } - - if (!ok) Debug.LogWarning("Лифт не удалось разместить, этаж собран без него"); - Instantiate(GameObjectA, new Vector3(0, -1, 0), Quaternion.identity); - - foreach (var room in levelManager.GetRooms()) + if (!ok) Debug.LogError("Лифт не помещается: проверь размеры карты, лифта и коридора"); + + AssignRoles(); + + SpawnMarkers(); // временно + return _data; + } + + // Роли для комнат без назначения: ужатые об край карты и обычные берут разные пулы + void AssignRoles() + { + foreach (var r in _data.UnassignedRooms.ToList()) { - float x = room.Position.x; - float z = room.Position.y; // Vector2.y -> Z в 3D - float length = room.Length; - float width = room.Width; - - Instantiate(GameObjectB, new Vector3(x, 0, z), Quaternion.identity); - Instantiate(GameObjectB, new Vector3(x + length, 0, z), Quaternion.identity); - Instantiate(GameObjectB, new Vector3(x + length, 0, z + width), Quaternion.identity); - Instantiate(GameObjectB, new Vector3(x, 0, z + width), Quaternion.identity); + var pool = r.isWallable ? ClippedRoles : FullRoles; + r.rt = pool[Random.Range(0, pool.Length)]; } } - // ---------- Вспомогательные ---------- + // Временная визуализация, переедет в стадию "оболочка" + void SpawnMarkers() + { + Instantiate(GameObjectA, new Vector3(0, -1, 0), Quaternion.identity); + + foreach (var room in _data.Rooms) + { + var p = room.Position; + Instantiate(GameObjectB, LevelGrid.ToWorld(new Vector2Int(p.x, p.y)), Quaternion.identity); + Instantiate(GameObjectB, LevelGrid.ToWorld(new Vector2Int(p.x + room.Length, p.y)), Quaternion.identity); + Instantiate(GameObjectB, LevelGrid.ToWorld(new Vector2Int(p.x + room.Length, p.y + room.Width)), Quaternion.identity); + Instantiate(GameObjectB, LevelGrid.ToWorld(new Vector2Int(p.x, p.y + room.Width)), Quaternion.identity); + } + } + + // ---------- Лифт ---------- + + bool PlaceElevator() + { + while (true) + { + if (TryAttachLift(true, _margin, _minDist)) return true; + if (TryAttachLift(false, _margin, _minDist)) return true; + if (TryAttachLift(false, _margin, 0f)) return true; + if (TryAttachLift(false, 0, 0f)) return true; + if (!PruneRandomLeaf()) return false; // последнее средство: освобождаем место + } + } + + // Вход в лифт только в длинную стену + private ExitsInRoomType[] LiftEntryDirs() + { + if (_liftLen > _liftWid) return new[] { ExitsInRoomType.ZAxisP, ExitsInRoomType.ZAxisN }; + if (_liftWid > _liftLen) return new[] { ExitsInRoomType.XAxisP, ExitsInRoomType.XAxisN }; + return AllDirs; + } + + // Сдвиги лифта вдоль стены (в модулях), если дверь в префабе не обязательно по центру + private IEnumerable LiftShifts(ExitsInRoomType dir) + { + yield return 0; + if (!liftDoorAnyPosition) yield break; + + bool xDir = dir == ExitsInRoomType.XAxisP || dir == ExitsInRoomType.XAxisN; + int cross = xDir ? _liftWid : _liftLen; + int max = (cross - _corrWid) / 2; + for (int s = 1; s <= max; s++) { yield return s; yield return -s; } + } + + bool TryAttachLift(bool requireMinDepth, int margin, float minDistance) + { + var start = _data.Start; + if (start == null) return false; + + var parents = _data.Depth.Keys + .OrderByDescending(r => _data.Depth[r]) + .ThenByDescending(r => (r.Center - start.Center).sqrMagnitude) + .ToList(); + + foreach (var parent in parents) + { + int liftDepth = _data.Depth[parent] + 1; + if (requireMinDepth && liftDepth < _minDepthLiftRoom) continue; + + foreach (var dir in LiftEntryDirs().OrderBy(_ => Random.value)) + { + if (parent.exits.Contains(dir)) continue; + + bool xDir = dir == ExitsInRoomType.XAxisP || dir == ExitsInRoomType.XAxisN; + + foreach (int shift in LiftShifts(dir)) + { + Room lift = new Room { Length = _liftLen, Width = _liftWid }; + PlaceByEntry(EntryPoint(parent, dir), dir, lift); + lift.Position += xDir ? new Vector2Int(0, shift) : new Vector2Int(shift, 0); + + if (minDistance > 0f && Vector2.Distance(lift.Center, start.Center) < minDistance) + continue; + + if (!CanPlaceLift(parent, lift, dir, margin)) continue; + + lift.rt = RoomType.Lift; // тип нужно выставить ДО AddRoom, чтобы FloorData запомнил Lift + lift.exits = new List(); + lift.entrance = ToEntrance(dir); + lift.isWallable = false; + + _data.AddRoom(lift, liftDepth); + _parent[lift] = (parent, dir); + _corridorOf[lift] = CreateCorridor(parent, dir); + parent.exits.Add(dir); + _curRooms++; + return true; + } + } + } + return false; + } + + bool CanPlaceLift(Room parent, Room lift, ExitsInRoomType dir, int margin) + { + if (!InBounds(lift)) return false; + if (!IsAreaFree(CorridorRect(parent, dir))) return false; + + return IsAreaFree(Inflate(RoomRect(lift), Mathf.Min(margin, _corrLen)), parent); + } + + // Удаляет случайную тупиковую комнату вместе с её коридором + bool PruneRandomLeaf() + { + var parentsSet = new HashSet(_parent.Values.Select(v => v.parent)); + var leaves = _parent.Keys.Where(r => !parentsSet.Contains(r)).ToList(); + if (leaves.Count == 0) return false; + + Room leaf = leaves[Random.Range(0, leaves.Count)]; + var (p, dir) = _parent[leaf]; + + _data.Remove(leaf); + if (_corridorOf.TryGetValue(leaf, out var cor)) + { + _data.Remove(cor); + _corridorOf.Remove(leaf); + } + p.exits.Remove(dir); + _parent.Remove(leaf); + _curRooms--; + return true; + } + + // ---------- Геометрия (всё в модулях) ---------- + + private static void PlaceByEntry(Vector2Int e, ExitsInRoomType dir, Room n) + { + switch (dir) + { + case ExitsInRoomType.XAxisP: n.Position = new Vector2Int(e.x, e.y - n.Width / 2); break; + case ExitsInRoomType.XAxisN: n.Position = new Vector2Int(e.x - n.Length, e.y - n.Width / 2); break; + case ExitsInRoomType.ZAxisP: n.Position = new Vector2Int(e.x - n.Length / 2, e.y); break; + default: n.Position = new Vector2Int(e.x - n.Length / 2, e.y - n.Width); break; + } + } + + private bool InBounds(Room r) => + r.Position.x >= 0 && r.Position.y >= 0 && + r.Position.x + r.Length <= _floor && + r.Position.y + r.Width <= _floor; + + // Чётное значение (целые метры), чтобы центровка (a - b) / 2 была целой + private static int Even(int v) => v & ~1; private static ExitsInRoomType Opposite(ExitsInRoomType d) { @@ -231,55 +309,58 @@ private static void PlaceByEntry(Vector2 e, ExitsInRoomType dir, Room n) } } - private static Rect RoomRect(Room r) => new Rect(r.Position.x, r.Position.y, r.Length, r.Width); + private static RectInt RoomRect(Room r) => new RectInt(r.Position.x, r.Position.y, r.Length, r.Width); + + private static RectInt Inflate(RectInt r, int m) => + new RectInt(r.x - m, r.y - m, r.width + 2 * m, r.height + 2 * m); // Прямоугольник коридора, ведущего из prev в направлении dir - private Rect CorridorRect(Room prev, ExitsInRoomType dir) + private RectInt CorridorRect(Room prev, ExitsInRoomType dir) { switch (dir) { case ExitsInRoomType.XAxisP: - return new Rect(prev.Position.x + prev.Length, - prev.Position.y + (prev.Width - corridorWigth) / 2f, - corridorLength, corridorWigth); + return new RectInt(prev.Position.x + prev.Length, + prev.Position.y + (prev.Width - _corrWid) / 2, + _corrLen, _corrWid); case ExitsInRoomType.XAxisN: - return new Rect(prev.Position.x - corridorLength, - prev.Position.y + (prev.Width - corridorWigth) / 2f, - corridorLength, corridorWigth); + return new RectInt(prev.Position.x - _corrLen, + prev.Position.y + (prev.Width - _corrWid) / 2, + _corrLen, _corrWid); case ExitsInRoomType.ZAxisP: - return new Rect(prev.Position.x + (prev.Length - corridorWigth) / 2f, - prev.Position.y + prev.Width, - corridorWigth, corridorLength); + return new RectInt(prev.Position.x + (prev.Length - _corrWid) / 2, + prev.Position.y + prev.Width, + _corrWid, _corrLen); default: // ZAxisN - return new Rect(prev.Position.x + (prev.Length - corridorWigth) / 2f, - prev.Position.y - corridorLength, - corridorWigth, corridorLength); + return new RectInt(prev.Position.x + (prev.Length - _corrWid) / 2, + prev.Position.y - _corrLen, + _corrWid, _corrLen); } } // Центр стены новой комнаты, куда упирается коридор - private Vector2 EntryPoint(Room prev, ExitsInRoomType dir) + private Vector2Int EntryPoint(Room prev, ExitsInRoomType dir) { switch (dir) { case ExitsInRoomType.XAxisP: - return new Vector2(prev.Position.x + prev.Length + corridorLength, - prev.Position.y + prev.Width / 2f); + return new Vector2Int(prev.Position.x + prev.Length + _corrLen, + prev.Position.y + prev.Width / 2); case ExitsInRoomType.XAxisN: - return new Vector2(prev.Position.x - corridorLength, - prev.Position.y + prev.Width / 2f); + return new Vector2Int(prev.Position.x - _corrLen, + prev.Position.y + prev.Width / 2); case ExitsInRoomType.ZAxisP: - return new Vector2(prev.Position.x + prev.Length / 2f, - prev.Position.y + prev.Width + corridorLength); + return new Vector2Int(prev.Position.x + prev.Length / 2, + prev.Position.y + prev.Width + _corrLen); default: - return new Vector2(prev.Position.x + prev.Length / 2f, - prev.Position.y - corridorLength); + return new Vector2Int(prev.Position.x + prev.Length / 2, + prev.Position.y - _corrLen); } } - private bool IsAreaFree(Rect rect, Room ignore = null) + private bool IsAreaFree(RectInt rect, Room ignore = null) { - foreach (var other in levelManager.GetRooms()) + foreach (var other in _data.Rooms) { if (other == ignore) continue; if (rect.Overlaps(RoomRect(other))) return false; @@ -291,20 +372,25 @@ private static void PlaceByEntry(Vector2 e, ExitsInRoomType dir, Room n) Room CreateCorridor(Room prev, ExitsInRoomType dir) { - Rect r = CorridorRect(prev, dir); + RectInt r = CorridorRect(prev, dir); - Room corridor = new Room(r.position, - Mathf.RoundToInt(r.width), Mathf.RoundToInt(r.height), + // Конструктор Room принимает (width = размер по Z, length = размер по X). + // RectInt: width = размер по X, height = размер по Z - поэтому аргументы переставлены. + Room corridor = new Room(r.position, r.height, r.width, RoomType.Corridor, false, ToEntrance(dir), new List { dir }); - LevelManager.Rooms.Add(corridor); + _data.AddCorridor(corridor); return corridor; } - + + // ---------- Размещение комнаты ---------- + + // Задаёт размер и позицию комнаты от точки входа: вдоль оси выхода комната растёт на полную + // длину, поперёк - симметрично (центр на коридоре). Если не влезает, ужимает. private bool FitRoom(Room prev, Room n, ExitsInRoomType dir, out bool shrunk) { - Vector2 e = EntryPoint(prev, dir); + Vector2Int e = EntryPoint(prev, dir); int origLen = n.Length, origWid = n.Width; switch (dir) @@ -312,28 +398,28 @@ private static void PlaceByEntry(Vector2 e, ExitsInRoomType dir, Room n) case ExitsInRoomType.XAxisP: case ExitsInRoomType.XAxisN: { - float maxAlong = dir == ExitsInRoomType.XAxisP ? lengthFloor - e.x : e.x; - float maxCross = 2f * Mathf.Min(e.y, lengthFloor - e.y); - n.Length = Mathf.Min(n.Length, Mathf.FloorToInt(maxAlong)); - n.Width = Mathf.Min(n.Width, Mathf.FloorToInt(maxCross)); - float x = dir == ExitsInRoomType.XAxisP ? e.x : e.x - n.Length; - n.Position = new Vector2(x, e.y - n.Width / 2f); + int maxAlong = dir == ExitsInRoomType.XAxisP ? _floor - e.x : e.x; + int maxCross = 2 * Mathf.Min(e.y, _floor - e.y); + n.Length = Even(Mathf.Min(n.Length, maxAlong)); + n.Width = Even(Mathf.Min(n.Width, maxCross)); + int x = dir == ExitsInRoomType.XAxisP ? e.x : e.x - n.Length; + n.Position = new Vector2Int(x, e.y - n.Width / 2); break; } default: // Z { - float maxAlong = dir == ExitsInRoomType.ZAxisP ? lengthFloor - e.y : e.y; - float maxCross = 2f * Mathf.Min(e.x, lengthFloor - e.x); - n.Width = Mathf.Min(n.Width, Mathf.FloorToInt(maxAlong)); - n.Length = Mathf.Min(n.Length, Mathf.FloorToInt(maxCross)); - float y = dir == ExitsInRoomType.ZAxisP ? e.y : e.y - n.Width; - n.Position = new Vector2(e.x - n.Length / 2f, y); + int maxAlong = dir == ExitsInRoomType.ZAxisP ? _floor - e.y : e.y; + int maxCross = 2 * Mathf.Min(e.x, _floor - e.x); + n.Width = Even(Mathf.Min(n.Width, maxAlong)); + n.Length = Even(Mathf.Min(n.Length, maxCross)); + int y = dir == ExitsInRoomType.ZAxisP ? e.y : e.y - n.Width; + n.Position = new Vector2Int(e.x - n.Length / 2, y); break; } } shrunk = n.Length != origLen || n.Width != origWid; - return n.Length >= lengthWallMin && n.Width >= lengthWallMin; + return n.Length >= _wallMin && n.Width >= _wallMin; } bool TryPlaceRoom(Room prevRoom, ExitsInRoomType dir) @@ -349,19 +435,14 @@ private static void PlaceByEntry(Vector2 e, ExitsInRoomType dir, Room n) newRoom.isWallable = shrunk; // Пересечения: комната (с зазором, без prevRoom) и коридор к ней - Rect roomArea = RoomRect(newRoom); - float m = Mathf.Min(roomMargin, corridorLength); - roomArea.xMin -= m; roomArea.yMin -= m; - roomArea.xMax += m; roomArea.yMax += m; - + RectInt roomArea = Inflate(RoomRect(newRoom), Mathf.Min(_margin, _corrLen)); if (!IsAreaFree(roomArea, prevRoom)) return false; if (!IsAreaFree(CorridorRect(prevRoom, dir))) return false; // Всё хорошо - фиксируем - levelManager.AddRoom(newRoom); - _depth[newRoom] = _depth[prevRoom] + 1; + _data.AddRoom(newRoom, _data.Depth[prevRoom] + 1); _parent[newRoom] = (prevRoom, dir); - _corridorOf[newRoom] = CreateCorridor(prevRoom, dir); // вместо отдельного CreateCorridor(...) + _corridorOf[newRoom] = CreateCorridor(prevRoom, dir); _curRooms++; if (!prevRoom.exits.Contains(dir)) prevRoom.exits.Add(dir); @@ -377,13 +458,10 @@ private static void PlaceByEntry(Vector2 e, ExitsInRoomType dir, Room n) Room GenerateRandomRoom() { - // Range(int,int) не включает верхнюю границу, поэтому +1 + // Размер - целые метры (в модулях всегда чётный). Range(int,int) не включает верхнюю границу, поэтому +1 int a = Random.Range(lengthWallMin, lengthWallMax + 1); int b = Random.Range(lengthWallMin, lengthWallMax + 1); - Room room = new Room(); - room.Length = a; - room.Width = b; - return room; + return new Room { Length = LevelGrid.FromMeters(a), Width = LevelGrid.FromMeters(b) }; } // ---------- Стартовая комната ---------- @@ -394,71 +472,70 @@ private static void PlaceByEntry(Vector2 e, ExitsInRoomType dir, Room n) int xR, zR; ExitsInRoomType firstDir; - Room room = new Room(lengthStartRoom, lengthStartRoom, RoomType.Start); + Room room = new Room(_startSize, _startSize, RoomType.Start); room.exits = new List(); room.entrance = EntranceToRoomType.Null; if (randValue < 0.25f) // 1 квадрант { - xR = Random.Range(0, lengthFloor / 2 - lengthStartRoom); - if (xR + lengthStartRoom <= lengthFloor / 4) + xR = Random.Range(0, _floor / 2 - _startSize); + if (xR + _startSize <= _floor / 4) { firstDir = ExitsInRoomType.XAxisP; - zR = Random.Range(0, lengthFloor / 2 - lengthStartRoom); + zR = Random.Range(0, _floor / 2 - _startSize); } else { firstDir = ExitsInRoomType.ZAxisP; - zR = Random.Range(0, lengthFloor / 4 - lengthStartRoom); + zR = Random.Range(0, _floor / 4 - _startSize); } } else if (randValue < 0.5f) // 2 квадрант { - xR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom); - if (xR + lengthStartRoom <= lengthFloor * 3 / 4) + xR = Random.Range(_floor / 2, _floor - _startSize); + if (xR + _startSize <= _floor * 3 / 4) { firstDir = ExitsInRoomType.ZAxisP; - zR = Random.Range(0, lengthFloor / 4 - lengthStartRoom); + zR = Random.Range(0, _floor / 4 - _startSize); } else { firstDir = ExitsInRoomType.XAxisN; - zR = Random.Range(0, lengthFloor / 2 - lengthStartRoom); + zR = Random.Range(0, _floor / 2 - _startSize); } } else if (randValue < 0.75f) // 3 квадрант { - xR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom); - if (xR + lengthStartRoom <= lengthFloor * 3 / 4) + xR = Random.Range(_floor / 2, _floor - _startSize); + if (xR + _startSize <= _floor * 3 / 4) { firstDir = ExitsInRoomType.ZAxisN; - zR = Random.Range(lengthFloor * 3 / 4, lengthFloor - lengthStartRoom); + zR = Random.Range(_floor * 3 / 4, _floor - _startSize); } else { firstDir = ExitsInRoomType.XAxisN; - zR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom); + zR = Random.Range(_floor / 2, _floor - _startSize); } } else // 4 квадрант { - xR = Random.Range(0, lengthFloor / 2 - lengthStartRoom); - if (xR + lengthStartRoom <= lengthFloor / 4) + xR = Random.Range(0, _floor / 2 - _startSize); + if (xR + _startSize <= _floor / 4) { firstDir = ExitsInRoomType.XAxisP; - zR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom); + zR = Random.Range(_floor / 2, _floor - _startSize); } else { firstDir = ExitsInRoomType.ZAxisN; - zR = Random.Range(lengthFloor * 3 / 4, lengthFloor - lengthStartRoom); + zR = Random.Range(_floor * 3 / 4, _floor - _startSize); } } - room.Position = new Vector2(xR, zR); - LevelManager.Rooms.Add(room); - _depth[room] = 0; - _curRooms = 1; // стартовая комната считается + room.Position = new Vector2Int(xR, zR); + _data.AddRoom(room, 0); // rt = Start, поэтому FloorData запомнит _data.Start + _curRooms = 1; // стартовая комната считается // Предпочтительное направление, а при неудаче - остальные if (TryPlaceRoom(room, firstDir)) return; @@ -470,33 +547,27 @@ private static void PlaceByEntry(Vector2 e, ExitsInRoomType dir, Room n) void OnDrawGizmos() { - var rooms = LevelManager.Rooms; - if (rooms == null) return; + if (_data == null) return; - foreach (var r in rooms) + foreach (var r in _data.Rooms) { switch (r.rt) { - case RoomType.Start: - Gizmos.color = Color.green; - break; - case RoomType.Corridor: - Gizmos.color = Color.yellow; - break; - case RoomType.Lift: - Gizmos.color = Color.rebeccaPurple; - break; - default: - Gizmos.color = Color.cyan; - break; - + case RoomType.Start: Gizmos.color = Color.green; break; + case RoomType.Corridor: Gizmos.color = Color.yellow; break; + case RoomType.Lift: Gizmos.color = Color.rebeccaPurple; break; + case RoomType.Laboratory: Gizmos.color = Color.white; break; + case RoomType.Office: Gizmos.color = Color.blue; break; + case RoomType.Penthouse: Gizmos.color = Color.magenta; break; + case RoomType.Storage: Gizmos.color = new Color(1f, 0.5f, 0f); break; + case RoomType.SwitchboardRoom: Gizmos.color = Color.red; break; + case RoomType.BoilerRoom: Gizmos.color = Color.gray; break; + default: Gizmos.color = Color.cyan; break; } - - // Gizmos.color = r.rt == RoomType.Start ? Color.green - // : r.rt == RoomType.Corridor ? Color.yellow : Color.cyan; - Vector3 c = new Vector3(r.Position.x + r.Length / 2f, 0, r.Position.y + r.Width / 2f); - Gizmos.DrawWireCube(c, new Vector3(r.Length, 0.1f, r.Width)); + + Vector3 c = LevelGrid.ToWorld(r.Center); + Gizmos.DrawWireCube(c, new Vector3(r.Length * LevelGrid.Module, 0.1f, r.Width * LevelGrid.Module)); } } } -} \ No newline at end of file +} diff --git a/Souljack.slnx b/Souljack.slnx new file mode 100644 index 0000000..20919c7 --- /dev/null +++ b/Souljack.slnx @@ -0,0 +1,3 @@ + + +