using Mirror; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Rendering; namespace ProceduralEditor { // Rebuilds PortalTestDestination.unity as a small greybox shooter-map prototype: // a walled arena with a central street flanked by two rows of walkable buildings, // in the style of a tactical-shooter blockout pass. public static class SetupPortalTestDestinationScene { const string ScenePath = "Assets/Scenes/PortalTestDestination.unity"; const string MaterialsFolder = "Assets/Materials"; const string SkyMaterialPath = "Assets/Materials/RangeSky.mat"; static readonly Color FloorColor = new Color(0.55f, 0.55f, 0.55f); static readonly Color WallColor = new Color(0.28f, 0.28f, 0.3f); static readonly Color BoxColor = new Color(0.76f, 0.58f, 0.34f); [MenuItem("Tools/Scene/Setup Portal Test Destination Shooter Map")] public static void Setup() { if (EditorApplication.isPlaying) { Debug.LogError("Setup Portal Test Destination: exit Play Mode first — EditorSceneManager.OpenScene() can't run during Play Mode."); return; } var scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single); // Clear out the old studio/pedestal content (Geometry, Lighting rig, post-process // volume, probe volume data, static lighting sky) but keep Main Camera and reconfigure it. foreach (var existingRoot in scene.GetRootGameObjects()) { if (existingRoot.name == "Main Camera") continue; Object.DestroyImmediate(existingRoot); } var floorMat = GetOrCreateMaterial("ShooterProto_Floor", FloorColor); var wallMat = GetOrCreateMaterial("ShooterProto_Wall", WallColor); var boxMat = GetOrCreateMaterial("ShooterProto_Box", BoxColor); SetupCamera(); SetupLighting(); var root = new GameObject("ShooterMapPrototype"); CreateBlock(root.transform, "Floor", new Vector3(0, -0.25f, 0), new Vector3(44, 0.5f, 34), floorMat); // Perimeter walls (floor spans x:[-22,22], z:[-17,17]) CreateBlock(root.transform, "Wall_North", new Vector3(0, 2, 17.5f), new Vector3(46, 4, 1), wallMat); CreateBlock(root.transform, "Wall_South", new Vector3(0, 2, -17.5f), new Vector3(46, 4, 1), wallMat); CreateBlock(root.transform, "Wall_East", new Vector3(22.5f, 2, 0), new Vector3(1, 4, 34), wallMat); CreateBlock(root.transform, "Wall_West", new Vector3(-22.5f, 2, 0), new Vector3(1, 4, 34), wallMat); var buildingsRoot = new GameObject("Buildings").transform; buildingsRoot.SetParent(root.transform); // A central street (x: -4..4) runs the full length of the map between two spawns. // Three buildings line each side of it, each walkable with a door facing the street // and a door facing the back alley, so players can cut through or around them. const float buildingWidth = 6f; const float buildingDepth = 6f; const float wallHeight = 3f; const float wallThickness = 0.5f; const float doorWidth = 2.2f; float[] rowZ = { -9f, 0f, 9f }; for (int i = 0; i < rowZ.Length; i++) { // Left row: door faces east (street), back door faces west (outer alley) CreateBuilding(buildingsRoot, $"Building_L{i + 1}", new Vector3(-13, 0, rowZ[i]), buildingWidth, buildingDepth, wallHeight, wallThickness, doorEast: true, doorWest: true, doorWidth, wallMat); // Right row: mirrored CreateBuilding(buildingsRoot, $"Building_R{i + 1}", new Vector3(13, 0, rowZ[i]), buildingWidth, buildingDepth, wallHeight, wallThickness, doorEast: true, doorWest: true, doorWidth, wallMat); } // A couple of symmetric crates in the open street for players crossing between // the two building rows to duck behind. var streetCoverRoot = new GameObject("StreetCover").transform; streetCoverRoot.SetParent(root.transform); CreateBlock(streetCoverRoot, "StreetCover_1", new Vector3(-2, 1, -4), new Vector3(2, 2, 2), boxMat); CreateBlock(streetCoverRoot, "StreetCover_2", new Vector3(2, 1, 4), new Vector3(2, 2, 2), boxMat); SetupSpawnPoint("SpawnPoint_A", new Vector3(0, 0, -14)); SetupSpawnPoint("SpawnPoint_B", new Vector3(0, 0, 14)); EditorSceneManager.MarkSceneDirty(scene); Debug.Log("Portal Test Destination rebuilt as a shooter-map prototype. Review it in the Scene view, then save (Ctrl+S)."); } static void SetupCamera() { var cam = Camera.main; if (cam == null) { var go = new GameObject("Main Camera", typeof(Camera), typeof(AudioListener)); go.tag = "MainCamera"; cam = go.GetComponent(); } // Overview framing for the editor / before a local player's camera gets reparented // onto them at runtime (see PlayerScript.OnStartLocalPlayer). cam.transform.position = new Vector3(0, 14, -26); cam.transform.rotation = Quaternion.Euler(24f, 0f, 0f); cam.fieldOfView = 60f; cam.clearFlags = CameraClearFlags.Skybox; cam.nearClipPlane = 0.1f; cam.farClipPlane = 200f; if (cam.GetComponent() == null) cam.gameObject.AddComponent(); } static void SetupLighting() { // Low dusk sun in the north-west; the RangeSky skybox draws its disc from this light. var go = new GameObject("Directional Light", typeof(Light)); var light = go.GetComponent(); light.type = LightType.Directional; go.transform.rotation = Quaternion.Euler(20f, 120f, 0f); light.color = new Color(1f, 0.82f, 0.62f); light.intensity = 1.3f; light.shadows = LightShadows.Soft; RenderSettings.sun = light; var sky = AssetDatabase.LoadAssetAtPath(SkyMaterialPath); if (sky != null) RenderSettings.skybox = sky; else Debug.LogWarning($"Setup Portal Test Destination: {SkyMaterialPath} not found; keeping the current skybox."); // Ambient and fog pick up the sky's palette (indigo above, warm horizon). RenderSettings.ambientMode = AmbientMode.Trilight; RenderSettings.ambientSkyColor = new Color(0.42f, 0.40f, 0.62f); RenderSettings.ambientEquatorColor = new Color(0.62f, 0.45f, 0.42f); RenderSettings.ambientGroundColor = new Color(0.16f, 0.14f, 0.18f); RenderSettings.fog = true; RenderSettings.fogMode = FogMode.Linear; RenderSettings.fogColor = new Color(0.55f, 0.42f, 0.48f); RenderSettings.fogStartDistance = 30f; RenderSettings.fogEndDistance = 110f; DynamicGI.UpdateEnvironment(); } static void SetupSpawnPoint(string name, Vector3 position) { var go = new GameObject(name, typeof(NetworkStartPosition)); go.transform.position = position; } // Builds a walkable, roofless room: solid North/South walls and East/West walls that // can each optionally have a centered door gap. static void CreateBuilding(Transform parent, string name, Vector3 center, float width, float depth, float wallHeight, float wallThickness, bool doorEast, bool doorWest, float doorWidth, Material material) { var buildingRoot = new GameObject(name).transform; buildingRoot.SetParent(parent, false); buildingRoot.localPosition = center; float halfWidth = width / 2f; float halfDepth = depth / 2f; float wallY = wallHeight / 2f; // North/South walls run along X; extend by wallThickness so they close the corners // against the East/West walls instead of leaving a gap. CreateBlock(buildingRoot, "Wall_N", new Vector3(0, wallY, halfDepth), new Vector3(width + wallThickness, wallHeight, wallThickness), material); CreateBlock(buildingRoot, "Wall_S", new Vector3(0, wallY, -halfDepth), new Vector3(width + wallThickness, wallHeight, wallThickness), material); CreateSideWall(buildingRoot, "Wall_E", new Vector3(halfWidth, wallY, 0), depth, wallThickness, wallHeight, doorEast, doorWidth, material); CreateSideWall(buildingRoot, "Wall_W", new Vector3(-halfWidth, wallY, 0), depth, wallThickness, wallHeight, doorWest, doorWidth, material); } // An East/West wall runs along Z; a door splits it into two segments with a gap at the center. static void CreateSideWall(Transform parent, string name, Vector3 center, float depth, float thickness, float height, bool hasDoor, float doorWidth, Material material) { if (!hasDoor) { CreateBlock(parent, name, center, new Vector3(thickness, height, depth), material); return; } float segmentLength = (depth - doorWidth) / 2f; float segmentOffset = doorWidth / 2f + segmentLength / 2f; CreateBlock(parent, name + "_A", center + new Vector3(0, 0, -segmentOffset), new Vector3(thickness, height, segmentLength), material); CreateBlock(parent, name + "_B", center + new Vector3(0, 0, segmentOffset), new Vector3(thickness, height, segmentLength), material); } static void CreateBlock(Transform parent, string name, Vector3 position, Vector3 size, Material material) { var go = GameObject.CreatePrimitive(PrimitiveType.Cube); go.name = name; go.transform.SetParent(parent, false); go.transform.localPosition = position; go.transform.localScale = size; go.GetComponent().sharedMaterial = material; } static Material GetOrCreateMaterial(string name, Color color) { string path = $"{MaterialsFolder}/{name}.mat"; var existing = AssetDatabase.LoadAssetAtPath(path); if (existing != null) { existing.color = color; return existing; } if (!AssetDatabase.IsValidFolder(MaterialsFolder)) AssetDatabase.CreateFolder("Assets", "Materials"); var shader = Shader.Find("Universal Render Pipeline/Lit"); var mat = new Material(shader) { color = color }; AssetDatabase.CreateAsset(mat, path); AssetDatabase.SaveAssets(); return mat; } } }