using System; using System.IO; using UnityEditor; using UnityEditor.Events; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.SceneManagement; using UnityEngine.UI; public static class Lab8SceneBuilder { private const string ScenePath = "Assets/_Scenes/NarrativeScene.unity"; [MenuItem("Tools/Lab8/Build Narrative Scene")] public static void BuildNarrativeScene() { EnsureFolders(); Material floorMat = CreateMaterial("Floor_Mat", new Color(0.28f, 0.30f, 0.27f), 0.1f, 0.35f); Material wallMat = CreateMaterial("Wall_Mat", new Color(0.48f, 0.46f, 0.42f), 0.05f, 0.25f); Material statueMat = CreateMaterial("Statue_Mat", new Color(0.62f, 0.66f, 0.70f), 0.25f, 0.55f); Material highlightMat = CreateMaterial("Highlight_Mat", new Color(1f, 0.86f, 0.34f), 0.1f, 0.75f); Material doorMat = CreateMaterial("Door_Mat", new Color(0.34f, 0.18f, 0.09f), 0.05f, 0.25f); Material chestMat = CreateMaterial("Chest_Mat", new Color(0.50f, 0.28f, 0.12f), 0.05f, 0.45f); Material darkMat = CreateMaterial("Dark_Mat", new Color(0.12f, 0.13f, 0.15f), 0.02f, 0.15f); AudioClip attentionClip = CreateAttentionCue(); Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); RenderSettings.ambientLight = new Color(0.08f, 0.09f, 0.11f); RenderSettings.fog = true; RenderSettings.fogColor = new Color(0.08f, 0.09f, 0.10f); RenderSettings.fogDensity = 0.018f; CreateEnvironment(floorMat, wallMat, darkMat); HintManager hintManager = CreateUI(); NarrativeEventManager eventManager = CreateEventManager(hintManager); GameObject player = CreatePlayer(); CameraController cameraController = CreateCamera(player.transform); GameObject statue = CreateStatue(statueMat, highlightMat, player.transform, eventManager); Transform statueCameraPoint = CreateStatueCameraPoint(statue.transform); GameObject exitDoor = CreateExitDoor(doorMat); GameObject secretChest = CreateSecretChest(chestMat, highlightMat, player.transform, eventManager); Light secretLight = CreateSecretLight(); CreateDecorations(darkMat, chestMat); CreateLights(); CreateAttentionTrigger(cameraController, statueCameraPoint, hintManager, attentionClip); eventManager.secretChest = secretChest; eventManager.secretLight = secretLight; eventManager.exitDoor = exitDoor.transform; PlayerMovement movement = player.GetComponent(); movement.cameraController = cameraController; SaveSecretChestPrefab(secretChest); secretChest.SetActive(false); EditorSceneManager.MarkSceneDirty(scene); EditorSceneManager.SaveScene(scene, ScenePath); AddSceneToBuildSettings(ScenePath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log($"Lab 8 narrative scene built and saved: {ScenePath}"); } private static void EnsureFolders() { string[] folders = { "Assets/_Scenes", "Assets/_Scripts", "Assets/_Scripts/Editor", "Assets/_Materials", "Assets/_Prefabs", "Assets/_Audio" }; foreach (string folder in folders) { Directory.CreateDirectory(folder); } AssetDatabase.Refresh(); } private static Material CreateMaterial(string materialName, Color color, float metallic, float smoothness) { string path = $"Assets/_Materials/{materialName}.mat"; Material material = AssetDatabase.LoadAssetAtPath(path); if (material == null) { Shader shader = Shader.Find("Standard"); material = new Material(shader); AssetDatabase.CreateAsset(material, path); } material.name = materialName; material.color = color; material.SetFloat("_Metallic", metallic); material.SetFloat("_Glossiness", smoothness); EditorUtility.SetDirty(material); return material; } private static void CreateEnvironment(Material floorMat, Material wallMat, Material darkMat) { GameObject environment = new GameObject("AdventureLocation"); CreateCube("CorridorFloor", new Vector3(0f, -0.05f, -5f), new Vector3(3f, 0.1f, 14f), floorMat, environment.transform); CreateCube("RoomFloor", new Vector3(0f, -0.05f, 6f), new Vector3(10f, 0.1f, 8f), floorMat, environment.transform); CreateCube("Corridor_LeftWall", new Vector3(-1.65f, 1.5f, -5f), new Vector3(0.25f, 3f, 14f), wallMat, environment.transform); CreateCube("Corridor_RightWall", new Vector3(1.65f, 1.5f, -5f), new Vector3(0.25f, 3f, 14f), wallMat, environment.transform); CreateCube("Corridor_BackWall", new Vector3(0f, 1.5f, -12.1f), new Vector3(3.5f, 3f, 0.25f), darkMat, environment.transform); CreateCube("Room_LeftWall", new Vector3(-5.1f, 1.5f, 6f), new Vector3(0.25f, 3f, 8.4f), wallMat, environment.transform); CreateCube("Room_RightWall", new Vector3(5.1f, 1.5f, 6f), new Vector3(0.25f, 3f, 8.4f), wallMat, environment.transform); CreateCube("Room_BackWall", new Vector3(0f, 1.5f, 10.1f), new Vector3(10.2f, 3f, 0.25f), wallMat, environment.transform); CreateCube("Room_EntranceWall_Left", new Vector3(-3.4f, 1.5f, 1.9f), new Vector3(3.2f, 3f, 0.25f), wallMat, environment.transform); CreateCube("Room_EntranceWall_Right", new Vector3(3.4f, 1.5f, 1.9f), new Vector3(3.2f, 3f, 0.25f), wallMat, environment.transform); CreateCube("Room_EntranceLintel", new Vector3(0f, 2.75f, 1.9f), new Vector3(3f, 0.5f, 0.25f), wallMat, environment.transform); } private static GameObject CreatePlayer() { GameObject player = GameObject.CreatePrimitive(PrimitiveType.Capsule); player.name = "Player"; player.tag = "Player"; player.transform.position = new Vector3(0f, 1f, -8.5f); UnityEngine.Object.DestroyImmediate(player.GetComponent()); CharacterController controller = player.AddComponent(); controller.height = 2f; controller.radius = 0.45f; controller.stepOffset = 0.35f; controller.slopeLimit = 45f; player.AddComponent(); return player; } private static CameraController CreateCamera(Transform player) { GameObject rig = new GameObject("CameraRig"); DepthOfFieldController dof = rig.AddComponent(); CameraController controller = rig.AddComponent(); controller.target = player; controller.followOffset = new Vector3(0f, 1.65f, -3f); controller.depthOfFieldController = dof; GameObject cameraObject = new GameObject("Main Camera"); cameraObject.tag = "MainCamera"; cameraObject.transform.SetParent(rig.transform, false); Camera camera = cameraObject.AddComponent(); camera.nearClipPlane = 0.05f; camera.fieldOfView = 65f; cameraObject.AddComponent(); return controller; } private static HintManager CreateUI() { GameObject canvasObject = new GameObject("HintCanvas", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster)); Canvas canvas = canvasObject.GetComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; CanvasScaler scaler = canvasObject.GetComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1920f, 1080f); scaler.matchWidthOrHeight = 0.5f; GameObject panelObject = new GameObject("HintPanel", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image)); panelObject.transform.SetParent(canvasObject.transform, false); RectTransform panelRect = panelObject.GetComponent(); panelRect.anchorMin = new Vector2(0.5f, 0f); panelRect.anchorMax = new Vector2(0.5f, 0f); panelRect.pivot = new Vector2(0.5f, 0f); panelRect.anchoredPosition = new Vector2(0f, 36f); panelRect.sizeDelta = new Vector2(900f, 96f); Image panelImage = panelObject.GetComponent(); panelImage.color = new Color(0.02f, 0.02f, 0.02f, 0.78f); GameObject textObject = new GameObject("HintText", typeof(RectTransform), typeof(CanvasRenderer), typeof(Text)); textObject.transform.SetParent(panelObject.transform, false); RectTransform textRect = textObject.GetComponent(); textRect.anchorMin = Vector2.zero; textRect.anchorMax = Vector2.one; textRect.offsetMin = new Vector2(24f, 10f); textRect.offsetMax = new Vector2(-24f, -10f); Text text = textObject.GetComponent(); text.font = Resources.GetBuiltinResource("LegacyRuntime.ttf"); text.fontSize = 28; text.alignment = TextAnchor.MiddleCenter; text.color = Color.white; text.horizontalOverflow = HorizontalWrapMode.Wrap; text.verticalOverflow = VerticalWrapMode.Truncate; HintManager hintManager = canvasObject.AddComponent(); hintManager.hintPanel = panelObject; hintManager.hintText = text; panelObject.SetActive(false); GameObject eventSystemObject = new GameObject("EventSystem", typeof(EventSystem), typeof(StandaloneInputModule)); eventSystemObject.transform.SetAsLastSibling(); return hintManager; } private static NarrativeEventManager CreateEventManager(HintManager hintManager) { GameObject managerObject = new GameObject("NarrativeEventManager"); NarrativeEventManager manager = managerObject.AddComponent(); manager.hintManager = hintManager; return manager; } private static GameObject CreateStatue(Material statueMat, Material highlightMat, Transform player, NarrativeEventManager eventManager) { GameObject statue = new GameObject("Statue"); statue.transform.position = new Vector3(0f, 0f, 6.8f); CreateCube("Statue_Pedestal", new Vector3(0f, 0.25f, 6.8f), new Vector3(1.8f, 0.5f, 1.8f), statueMat, statue.transform); GameObject body = GameObject.CreatePrimitive(PrimitiveType.Cylinder); body.name = "Statue_Body"; body.transform.SetParent(statue.transform, true); body.transform.position = new Vector3(0f, 1.15f, 6.8f); body.transform.localScale = new Vector3(0.72f, 0.9f, 0.72f); SetMaterial(body, statueMat); GameObject head = GameObject.CreatePrimitive(PrimitiveType.Sphere); head.name = "Statue_Head"; head.transform.SetParent(statue.transform, true); head.transform.position = new Vector3(0f, 2.15f, 6.8f); head.transform.localScale = new Vector3(0.65f, 0.65f, 0.65f); SetMaterial(head, statueMat); GameObject leftArm = GameObject.CreatePrimitive(PrimitiveType.Capsule); leftArm.name = "Statue_LeftArm"; leftArm.transform.SetParent(statue.transform, true); leftArm.transform.position = new Vector3(-0.7f, 1.35f, 6.8f); leftArm.transform.localScale = new Vector3(0.22f, 0.55f, 0.22f); leftArm.transform.rotation = Quaternion.Euler(0f, 0f, 28f); SetMaterial(leftArm, statueMat); GameObject rightArm = GameObject.CreatePrimitive(PrimitiveType.Capsule); rightArm.name = "Statue_RightArm"; rightArm.transform.SetParent(statue.transform, true); rightArm.transform.position = new Vector3(0.7f, 1.35f, 6.8f); rightArm.transform.localScale = new Vector3(0.22f, 0.55f, 0.22f); rightArm.transform.rotation = Quaternion.Euler(0f, 0f, -28f); SetMaterial(rightArm, statueMat); HighlightOnApproach highlight = statue.AddComponent(); highlight.player = player; highlight.highlightMaterial = highlightMat; highlight.targetRenderers = statue.GetComponentsInChildren(); highlight.radius = 2.8f; InteractableObject interactable = statue.AddComponent(); interactable.player = player; interactable.interactionRadius = 2.6f; interactable.promptText = "Нажмите E, чтобы осмотреть статую"; UnityEventTools.AddPersistentListener(interactable.onInteract, eventManager.OnStatueInteracted); return statue; } private static Transform CreateStatueCameraPoint(Transform statue) { GameObject point = new GameObject("StatueCameraPoint"); point.transform.position = new Vector3(0f, 2.15f, 3.45f); Vector3 focusPosition = statue.position + new Vector3(0f, 1.45f, 0f); point.transform.rotation = Quaternion.LookRotation(focusPosition - point.transform.position, Vector3.up); return point.transform; } private static GameObject CreateExitDoor(Material doorMat) { return CreateCube("ExitDoor", new Vector3(0f, 1.2f, 9.95f), new Vector3(2.1f, 2.4f, 0.28f), doorMat, null); } private static GameObject CreateSecretChest(Material chestMat, Material highlightMat, Transform player, NarrativeEventManager eventManager) { GameObject chest = new GameObject("SecretChest"); chest.transform.position = new Vector3(3.45f, 0.05f, 7.5f); CreateCube("SecretChest_Base", new Vector3(3.45f, 0.35f, 7.5f), new Vector3(1.3f, 0.55f, 0.85f), chestMat, chest.transform); GameObject lid = CreateCube("SecretChest_Lid", new Vector3(3.45f, 0.75f, 7.5f), new Vector3(1.35f, 0.25f, 0.9f), chestMat, chest.transform); lid.transform.rotation = Quaternion.Euler(-8f, 0f, 0f); GameObject artifact = GameObject.CreatePrimitive(PrimitiveType.Sphere); artifact.name = "KeyArtifact"; artifact.transform.SetParent(chest.transform, true); artifact.transform.position = new Vector3(3.45f, 1.05f, 7.5f); artifact.transform.localScale = new Vector3(0.35f, 0.35f, 0.35f); SetMaterial(artifact, highlightMat); InteractableObject interactable = chest.AddComponent(); interactable.player = player; interactable.interactionRadius = 2.2f; interactable.promptText = "Нажмите E, чтобы забрать артефакт"; UnityEventTools.AddPersistentListener(interactable.onInteract, eventManager.FinishScene); return chest; } private static void CreateDecorations(Material darkMat, Material chestMat) { GameObject decorRoot = new GameObject("Decorations"); CreateColumn("Column_LeftFront", new Vector3(-3.7f, 1.1f, 3.2f), darkMat, decorRoot.transform); CreateColumn("Column_RightFront", new Vector3(3.7f, 1.1f, 3.2f), darkMat, decorRoot.transform); CreateColumn("Column_LeftBack", new Vector3(-3.7f, 1.1f, 8.8f), darkMat, decorRoot.transform); CreateColumn("Column_RightBack", new Vector3(3.7f, 1.1f, 8.8f), darkMat, decorRoot.transform); CreateCube("Crate_Left", new Vector3(-3.1f, 0.35f, 5.4f), new Vector3(0.75f, 0.7f, 0.75f), chestMat, decorRoot.transform); CreateCube("Crate_Right", new Vector3(2.85f, 0.3f, 4.4f), new Vector3(0.65f, 0.6f, 0.65f), chestMat, decorRoot.transform); CreateCube("BrokenBlock_01", new Vector3(-1.2f, 0.18f, 8.8f), new Vector3(0.9f, 0.35f, 0.55f), darkMat, decorRoot.transform); CreateCube("BrokenBlock_02", new Vector3(1.25f, 0.18f, 8.55f), new Vector3(0.8f, 0.35f, 0.5f), darkMat, decorRoot.transform); } private static void CreateColumn(string name, Vector3 position, Material material, Transform parent) { GameObject column = GameObject.CreatePrimitive(PrimitiveType.Cylinder); column.name = name; column.transform.SetParent(parent, true); column.transform.position = position; column.transform.localScale = new Vector3(0.42f, 1.1f, 0.42f); SetMaterial(column, material); } private static void CreateLights() { GameObject directionObject = new GameObject("LowAmbient_DirectionalLight"); Light directionLight = directionObject.AddComponent(); directionLight.type = LightType.Directional; directionLight.intensity = 0.22f; directionLight.color = new Color(0.70f, 0.78f, 0.9f); directionObject.transform.rotation = Quaternion.Euler(50f, -35f, 0f); GameObject statueSpotObject = new GameObject("Statue_SpotLight"); Light statueSpot = statueSpotObject.AddComponent(); statueSpot.type = LightType.Spot; statueSpot.intensity = 5.2f; statueSpot.range = 8f; statueSpot.spotAngle = 45f; statueSpot.color = new Color(1f, 0.86f, 0.58f); statueSpotObject.transform.position = new Vector3(0f, 4.6f, 4.5f); statueSpotObject.transform.rotation = Quaternion.LookRotation(new Vector3(0f, 1.2f, 6.8f) - statueSpotObject.transform.position, Vector3.up); GameObject corridorLightObject = new GameObject("DimCorridor_PointLight"); Light corridorLight = corridorLightObject.AddComponent(); corridorLight.type = LightType.Point; corridorLight.intensity = 0.65f; corridorLight.range = 5.5f; corridorLight.color = new Color(0.55f, 0.62f, 0.78f); corridorLightObject.transform.position = new Vector3(0f, 2.4f, -5.5f); } private static Light CreateSecretLight() { GameObject lightObject = new GameObject("SecretChest_PointLight"); Light light = lightObject.AddComponent(); light.type = LightType.Point; light.intensity = 3.2f; light.range = 4.5f; light.color = new Color(1f, 0.80f, 0.35f); light.transform.position = new Vector3(3.45f, 1.8f, 7.5f); light.enabled = false; return light; } private static void CreateAttentionTrigger(CameraController cameraController, Transform cameraPoint, HintManager hintManager, AudioClip clip) { GameObject triggerObject = new GameObject("RoomEntrance_AttentionTrigger"); triggerObject.transform.position = new Vector3(0f, 1.2f, 1.1f); BoxCollider collider = triggerObject.AddComponent(); collider.isTrigger = true; collider.size = new Vector3(3.1f, 2.4f, 1.1f); AudioSource audioSource = triggerObject.AddComponent(); audioSource.playOnAwake = false; audioSource.spatialBlend = 0.25f; AttentionTrigger trigger = triggerObject.AddComponent(); trigger.cameraController = cameraController; trigger.cameraPoint = cameraPoint; trigger.hintManager = hintManager; trigger.hintText = "Похоже, здесь что-то спрятано..."; trigger.fixedCameraDuration = 2.4f; trigger.hintDuration = 3.2f; trigger.audioClip = clip; trigger.audioSource = audioSource; trigger.destroyAfterTrigger = false; } private static GameObject CreateCube(string name, Vector3 position, Vector3 scale, Material material, Transform parent) { GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.name = name; cube.transform.position = position; cube.transform.localScale = scale; if (parent != null) { cube.transform.SetParent(parent, true); } SetMaterial(cube, material); return cube; } private static void SetMaterial(GameObject gameObject, Material material) { Renderer renderer = gameObject.GetComponent(); if (renderer != null) { renderer.sharedMaterial = material; } } private static AudioClip CreateAttentionCue() { string path = "Assets/_Audio/AttentionCue.wav"; string fullPath = Path.Combine(Directory.GetCurrentDirectory(), path); const int sampleRate = 44100; const float duration = 0.55f; int sampleCount = Mathf.CeilToInt(sampleRate * duration); byte[] wav = BuildWav(sampleRate, sampleCount, index => { float time = index / (float)sampleRate; float envelope = Mathf.Clamp01(1f - time / duration); float toneA = Mathf.Sin(2f * Mathf.PI * 660f * time); float toneB = Mathf.Sin(2f * Mathf.PI * 990f * time) * 0.35f; return (toneA + toneB) * envelope * 0.35f; }); File.WriteAllBytes(fullPath, wav); AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate); return AssetDatabase.LoadAssetAtPath(path); } private static byte[] BuildWav(int sampleRate, int sampleCount, Func sampleFunction) { const short channels = 1; const short bitsPerSample = 16; int byteRate = sampleRate * channels * bitsPerSample / 8; int dataSize = sampleCount * channels * bitsPerSample / 8; byte[] bytes = new byte[44 + dataSize]; WriteAscii(bytes, 0, "RIFF"); WriteInt(bytes, 4, 36 + dataSize); WriteAscii(bytes, 8, "WAVE"); WriteAscii(bytes, 12, "fmt "); WriteInt(bytes, 16, 16); WriteShort(bytes, 20, 1); WriteShort(bytes, 22, channels); WriteInt(bytes, 24, sampleRate); WriteInt(bytes, 28, byteRate); WriteShort(bytes, 32, (short)(channels * bitsPerSample / 8)); WriteShort(bytes, 34, bitsPerSample); WriteAscii(bytes, 36, "data"); WriteInt(bytes, 40, dataSize); int offset = 44; for (int i = 0; i < sampleCount; i++) { float sample = Mathf.Clamp(sampleFunction(i), -1f, 1f); short value = (short)(sample * short.MaxValue); WriteShort(bytes, offset, value); offset += 2; } return bytes; } private static void WriteAscii(byte[] bytes, int offset, string value) { for (int i = 0; i < value.Length; i++) { bytes[offset + i] = (byte)value[i]; } } private static void WriteInt(byte[] bytes, int offset, int value) { bytes[offset] = (byte)(value & 0xff); bytes[offset + 1] = (byte)((value >> 8) & 0xff); bytes[offset + 2] = (byte)((value >> 16) & 0xff); bytes[offset + 3] = (byte)((value >> 24) & 0xff); } private static void WriteShort(byte[] bytes, int offset, short value) { bytes[offset] = (byte)(value & 0xff); bytes[offset + 1] = (byte)((value >> 8) & 0xff); } private static void SaveSecretChestPrefab(GameObject secretChest) { string path = "Assets/_Prefabs/SecretChest.prefab"; PrefabUtility.SaveAsPrefabAsset(secretChest, path); } private static void AddSceneToBuildSettings(string scenePath) { EditorBuildSettings.scenes = new[] { new EditorBuildSettingsScene(scenePath, true) }; } }