Files
OTG_1/Assets/Editor/Lab1SceneBuilder.cs
T
2026-06-04 17:00:04 +03:00

447 lines
16 KiB
C#

using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
/// <summary>
/// Builds the complete lab scene from code:
/// folders, materials, prefabs, player, camera, generator, UI and saved scene.
/// </summary>
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<Material>(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<Renderer>().sharedMaterial = baseMaterial;
PlatformInfo info = root.AddComponent<PlatformInfo>();
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<Renderer>().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<Renderer>().sharedMaterial = directionMaterial;
Collider markerCollider = marker.GetComponent<Collider>();
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<Renderer>().sharedMaterial = playerMaterial;
Rigidbody rb = player.AddComponent<Rigidbody>();
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<SimplePlayerController>();
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>();
camera.tag = "MainCamera";
camera.clearFlags = CameraClearFlags.Skybox;
camera.fieldOfView = 60f;
camera.nearClipPlane = 0.1f;
camera.farClipPlane = 250f;
CameraFollow follow = cameraObject.AddComponent<CameraFollow>();
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<LevelGenerator>();
generator.platformPrefabs = new[] { basicPrefab, widePrefab, narrowPrefab, obstaclePrefab };
generator.player = player.transform;
generator.playerController = player.GetComponent<SimplePlayerController>();
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>();
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<Font>("LegacyRuntime.ttf");
GameObject canvasObject = new GameObject("Canvas");
Canvas canvas = canvasObject.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvasObject.AddComponent<GraphicRaycaster>();
CanvasScaler scaler = canvasObject.AddComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920f, 1080f);
scaler.matchWidthOrHeight = 0.5f;
LevelGenUI ui = canvasObject.AddComponent<LevelGenUI>();
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>();
rectTransform.anchorMin = anchorMin;
rectTransform.anchorMax = anchorMax;
rectTransform.pivot = pivot;
rectTransform.anchoredPosition = anchoredPosition;
rectTransform.sizeDelta = sizeDelta;
Text text = textObject.AddComponent<Text>();
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>();
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<EventSystem>();
eventSystemObject.AddComponent<StandaloneInputModule>();
}
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;
}
}