using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.SceneManagement; using UnityEngine.UI; /// /// Builds the complete lab scene from code: /// folders, materials, prefabs, player, camera, generator, UI and saved scene. /// public static class Lab1SceneBuilder { private const string ScriptsFolder = "Assets/_Scripts"; private const string ScenesFolder = "Assets/_Scenes"; private const string PrefabsFolder = "Assets/_Prefabs"; private const string MaterialsFolder = "Assets/_Materials"; private const string EditorFolder = "Assets/Editor"; private const string ScenePath = "Assets/_Scenes/Level_Procedural.unity"; [MenuItem("Tools/Technical Game Design/Lab1/Create Procedural Level Scene")] public static void CreateProceduralLevelScene() { EnsureProjectFolders(); Material basicMaterial = CreateOrUpdateMaterial("M_Platform_Basic", new Color(0.35f, 0.48f, 0.68f)); Material wideMaterial = CreateOrUpdateMaterial("M_Platform_Wide", new Color(0.28f, 0.62f, 0.43f)); Material narrowMaterial = CreateOrUpdateMaterial("M_Platform_Narrow", new Color(0.82f, 0.65f, 0.28f)); Material obstacleBaseMaterial = CreateOrUpdateMaterial("M_Platform_Obstacle_Base", new Color(0.42f, 0.42f, 0.48f)); Material obstacleMaterial = CreateOrUpdateMaterial("M_Obstacle_Red", new Color(0.86f, 0.16f, 0.14f)); Material directionMaterial = CreateOrUpdateMaterial("M_Direction_Marker", new Color(0.15f, 0.9f, 0.95f)); Material playerMaterial = CreateOrUpdateMaterial("M_Player", new Color(0.2f, 0.2f, 0.24f)); GameObject basicPrefab = CreatePlatformPrefab( "Platform_Basic", PlatformInfo.PlatformType.Basic, new Vector3(3f, 0.5f, 3f), basicMaterial, directionMaterial, false, obstacleMaterial); GameObject widePrefab = CreatePlatformPrefab( "Platform_Wide", PlatformInfo.PlatformType.Wide, new Vector3(5f, 0.5f, 3f), wideMaterial, directionMaterial, false, obstacleMaterial); GameObject narrowPrefab = CreatePlatformPrefab( "Platform_Narrow", PlatformInfo.PlatformType.Narrow, new Vector3(2f, 0.5f, 3f), narrowMaterial, directionMaterial, false, obstacleMaterial); GameObject obstaclePrefab = CreatePlatformPrefab( "Platform_Obstacle", PlatformInfo.PlatformType.Obstacle, new Vector3(3f, 0.5f, 3f), obstacleBaseMaterial, directionMaterial, true, obstacleMaterial); Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); scene.name = "Level_Procedural"; SetupLighting(); GameObject player = CreatePlayer(playerMaterial); Camera camera = CreateCamera(player.transform); LevelGenerator generator = CreateGenerator(player, basicPrefab, widePrefab, narrowPrefab, obstaclePrefab); LevelGenUI ui = CreateUi(generator); generator.ui = ui; ui.generator = generator; generator.GenerateInitialLevel(); ui.UpdateInfo(); Selection.activeObject = generator.gameObject; EditorSceneManager.SaveScene(scene, ScenePath); AddSceneToBuildSettings(ScenePath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log("Lab 1 procedural level scene created: " + ScenePath); } private static void EnsureProjectFolders() { EnsureFolder(ScriptsFolder); EnsureFolder(ScenesFolder); EnsureFolder(PrefabsFolder); EnsureFolder(MaterialsFolder); EnsureFolder(EditorFolder); } private static void EnsureFolder(string path) { if (AssetDatabase.IsValidFolder(path)) { return; } string[] parts = path.Split('/'); string current = parts[0]; for (int i = 1; i < parts.Length; i++) { string next = current + "/" + parts[i]; if (!AssetDatabase.IsValidFolder(next)) { AssetDatabase.CreateFolder(current, parts[i]); } current = next; } } private static Material CreateOrUpdateMaterial(string materialName, Color color) { string path = MaterialsFolder + "/" + materialName + ".mat"; Material material = AssetDatabase.LoadAssetAtPath(path); if (material == null) { Shader shader = Shader.Find("Standard"); if (shader == null) { shader = Shader.Find("Diffuse"); } material = new Material(shader); AssetDatabase.CreateAsset(material, path); } material.color = color; EditorUtility.SetDirty(material); return material; } private static GameObject CreatePlatformPrefab( string prefabName, PlatformInfo.PlatformType platformType, Vector3 platformScale, Material baseMaterial, Material directionMaterial, bool withObstacle, Material obstacleMaterial) { GameObject root = GameObject.CreatePrimitive(PrimitiveType.Cube); root.name = prefabName; root.transform.localScale = platformScale; root.GetComponent().sharedMaterial = baseMaterial; PlatformInfo info = root.AddComponent(); info.platformType = platformType; info.generatedIndex = -1; info.isSafe = platformType != PlatformInfo.PlatformType.Obstacle; CreateDirectionMarker(root.transform, platformScale, directionMaterial); if (withObstacle) { GameObject obstacle = GameObject.CreatePrimitive(PrimitiveType.Cube); obstacle.name = "Obstacle"; obstacle.transform.SetParent(root.transform, false); obstacle.transform.localPosition = new Vector3( 0.9f / platformScale.x, 0.78f / platformScale.y, 0.35f / platformScale.z); obstacle.transform.localScale = new Vector3( 0.75f / platformScale.x, 1.05f / platformScale.y, 0.95f / platformScale.z); obstacle.GetComponent().sharedMaterial = obstacleMaterial; } string path = PrefabsFolder + "/" + prefabName + ".prefab"; AssetDatabase.DeleteAsset(path); GameObject prefab = PrefabUtility.SaveAsPrefabAsset(root, path); Object.DestroyImmediate(root); return prefab; } private static void CreateDirectionMarker(Transform parent, Vector3 platformScale, Material directionMaterial) { GameObject marker = GameObject.CreatePrimitive(PrimitiveType.Cube); marker.name = "Direction_Marker"; marker.transform.SetParent(parent, false); marker.transform.localPosition = new Vector3(0f, 0.58f, 0.22f); marker.transform.localScale = new Vector3(0.55f, 0.08f, 0.1f); marker.GetComponent().sharedMaterial = directionMaterial; Collider markerCollider = marker.GetComponent(); if (markerCollider != null) { Object.DestroyImmediate(markerCollider); } } private static GameObject CreatePlayer(Material playerMaterial) { GameObject player = GameObject.CreatePrimitive(PrimitiveType.Capsule); player.name = "Player"; player.transform.position = new Vector3(0f, 1.35f, 0f); player.GetComponent().sharedMaterial = playerMaterial; Rigidbody rb = player.AddComponent(); rb.mass = 1f; rb.drag = 0f; rb.angularDrag = 0.05f; rb.interpolation = RigidbodyInterpolation.Interpolate; rb.collisionDetectionMode = CollisionDetectionMode.Continuous; rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ; SimplePlayerController controller = player.AddComponent(); controller.moveSpeed = 6f; controller.jumpForce = 7f; controller.groundCheckRadius = 0.25f; controller.groundMask = LayerMask.GetMask("Default"); controller.fallResetY = -10f; controller.lastSafePosition = player.transform.position; return player; } private static Camera CreateCamera(Transform player) { GameObject cameraObject = new GameObject("Main Camera"); Camera camera = cameraObject.AddComponent(); camera.tag = "MainCamera"; camera.clearFlags = CameraClearFlags.Skybox; camera.fieldOfView = 60f; camera.nearClipPlane = 0.1f; camera.farClipPlane = 250f; CameraFollow follow = cameraObject.AddComponent(); follow.target = player; follow.offset = new Vector3(0f, 8f, -8f); follow.followSpeed = 6f; follow.lookHeight = 1f; cameraObject.transform.position = player.position + follow.offset; cameraObject.transform.LookAt(player.position + Vector3.up); return camera; } private static LevelGenerator CreateGenerator( GameObject player, GameObject basicPrefab, GameObject widePrefab, GameObject narrowPrefab, GameObject obstaclePrefab) { GameObject generatorObject = new GameObject("LevelGenerator"); LevelGenerator generator = generatorObject.AddComponent(); generator.platformPrefabs = new[] { basicPrefab, widePrefab, narrowPrefab, obstaclePrefab }; generator.player = player.transform; generator.playerController = player.GetComponent(); generator.initialPlatformCount = 12; generator.stepDistance = 3.5f; generator.generationThreshold = 12f; generator.removeThreshold = 18f; generator.obstacleChance = 0.3f; generator.sideOffsetRange = 2f; generator.infiniteMode = true; generator.maxPlatformsAlive = 30; return generator; } private static void SetupLighting() { RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat; RenderSettings.ambientLight = new Color(0.45f, 0.48f, 0.54f); RenderSettings.skybox = null; GameObject lightObject = new GameObject("Directional Light"); Light light = lightObject.AddComponent(); light.type = LightType.Directional; light.intensity = 1.15f; light.shadows = LightShadows.Soft; lightObject.transform.rotation = Quaternion.Euler(50f, -30f, 0f); } private static LevelGenUI CreateUi(LevelGenerator generator) { Font font = Resources.GetBuiltinResource("LegacyRuntime.ttf"); GameObject canvasObject = new GameObject("Canvas"); Canvas canvas = canvasObject.AddComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvasObject.AddComponent(); CanvasScaler scaler = canvasObject.AddComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1920f, 1080f); scaler.matchWidthOrHeight = 0.5f; LevelGenUI ui = canvasObject.AddComponent(); ui.generator = generator; ui.titleText = CreateText( "Title", canvasObject.transform, font, 30, FontStyle.Bold, new Color(1f, 1f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(28f, -24f), new Vector2(900f, 42f), TextAnchor.UpperLeft); ui.hintsText = CreateText( "Controls_Hints", canvasObject.transform, font, 22, FontStyle.Normal, new Color(0.9f, 0.95f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(30f, -82f), new Vector2(360f, 110f), TextAnchor.UpperLeft); ui.parametersText = CreateText( "Generation_Parameters", canvasObject.transform, font, 22, FontStyle.Normal, new Color(0.92f, 1f, 0.92f), new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-370f, -28f), new Vector2(340f, 170f), TextAnchor.UpperLeft); ui.statusText = CreateText( "Status", canvasObject.transform, font, 20, FontStyle.Normal, new Color(1f, 0.94f, 0.78f), new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(30f, 28f), new Vector2(620f, 65f), TextAnchor.LowerLeft); ui.messageText = CreateText( "Message", canvasObject.transform, font, 26, FontStyle.Bold, new Color(0.55f, 1f, 0.55f), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(-180f, -150f), new Vector2(360f, 42f), TextAnchor.MiddleCenter); ui.messageText.gameObject.SetActive(false); CreateEventSystem(); return ui; } private static Text CreateText( string name, Transform parent, Font font, int fontSize, FontStyle fontStyle, Color color, Vector2 anchorMin, Vector2 anchorMax, Vector2 pivot, Vector2 anchoredPosition, Vector2 sizeDelta, TextAnchor alignment) { GameObject textObject = new GameObject(name); textObject.transform.SetParent(parent, false); RectTransform rectTransform = textObject.AddComponent(); rectTransform.anchorMin = anchorMin; rectTransform.anchorMax = anchorMax; rectTransform.pivot = pivot; rectTransform.anchoredPosition = anchoredPosition; rectTransform.sizeDelta = sizeDelta; Text text = textObject.AddComponent(); text.font = font; text.fontSize = fontSize; text.fontStyle = fontStyle; text.color = color; text.alignment = alignment; text.horizontalOverflow = HorizontalWrapMode.Wrap; text.verticalOverflow = VerticalWrapMode.Overflow; text.raycastTarget = false; Outline outline = textObject.AddComponent(); outline.effectColor = new Color(0f, 0f, 0f, 0.8f); outline.effectDistance = new Vector2(1.4f, -1.4f); return text; } private static void CreateEventSystem() { GameObject eventSystemObject = new GameObject("EventSystem"); eventSystemObject.AddComponent(); eventSystemObject.AddComponent(); } private static void AddSceneToBuildSettings(string scenePath) { EditorBuildSettingsScene[] oldScenes = EditorBuildSettings.scenes; for (int i = 0; i < oldScenes.Length; i++) { if (oldScenes[i].path == scenePath) { oldScenes[i].enabled = true; EditorBuildSettings.scenes = oldScenes; return; } } EditorBuildSettingsScene[] newScenes = new EditorBuildSettingsScene[oldScenes.Length + 1]; oldScenes.CopyTo(newScenes, 0); newScenes[newScenes.Length - 1] = new EditorBuildSettingsScene(scenePath, true); EditorBuildSettings.scenes = newScenes; } }