using UnityEngine; using UnityEditor; using UnityEngine.UI; using UnityEditor.SceneManagement; public static class LevelStoryBuilder { private static Font defaultUiFont; private static Font handwrittenFont; private static Material echoMat; [MenuItem("Tools/Build Level Story System (Elevator + Notes)")] public static void Build() { Debug.Log("=== СБОРКА СЮЖЕТНОЙ СИСТЕМЫ: ЛИФТ И 4 ЗАПИСКИ С КОДОМ ==="); if (EditorApplication.isPlaying) { Debug.LogError("Нельзя запускать сборку во время Play Mode! Остановите игру."); return; } defaultUiFont = Resources.GetBuiltinResource("LegacyRuntime.ttf"); if (defaultUiFont == null) defaultUiFont = Resources.GetBuiltinResource("Arial.ttf"); handwrittenFont = AssetDatabase.LoadAssetAtPath("Assets/Fonts/SegoeScriptBold.ttf"); if (handwrittenFont == null) handwrittenFont = AssetDatabase.LoadAssetAtPath("Assets/Fonts/Inkfree.ttf"); if (handwrittenFont == null) handwrittenFont = defaultUiFont; echoMat = AssetDatabase.LoadAssetAtPath("Assets/EchoCloudMat.mat"); if (echoMat == null) echoMat = AssetDatabase.LoadAssetAtPath("Assets/EchoMat.mat"); // Убеждаемся, что у игрока стоит тег Player var player = GameObject.Find("Player"); if (player != null) { player.tag = "Player"; EditorUtility.SetDirty(player); } // 1. Создаем или находим StoryManager (LevelCodeManager) SetupStoryManager(); // 2. Создаем 3D-зону лифта и терминал SetupElevatorPlatformAndTerminal(); // 3. Создаем 4 записки в разных комнатах лаборатории SetupFourStoryNotes(); // 4. Добавляем в HUD_Canvas весь UI SetupStoryUIInHUDCanvas(); // Сохраняем сцену EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); EditorSceneManager.SaveOpenScenes(); Debug.Log("=== СЮЖЕТНАЯ СИСТЕМА УСПЕШНО СОБРАНА И СОХРАНЕНА! ==="); } private static void SetupStoryManager() { var existing = Object.FindAnyObjectByType(); if (existing == null) { var go = new GameObject("StoryManager"); Undo.RegisterCreatedObjectUndo(go, "Create StoryManager"); go.AddComponent(); Debug.Log("[LevelStoryBuilder] Создан объект StoryManager с LevelCodeManager."); } } private static void SetupElevatorPlatformAndTerminal() { var oldElevator = GameObject.Find("Elevator_Level2_Zone"); if (oldElevator != null) { Undo.DestroyObjectImmediate(oldElevator); } GameObject elevatorRoot = new GameObject("Elevator_Level2_Zone"); Undo.RegisterCreatedObjectUndo(elevatorRoot, "Create Elevator_Level2_Zone"); elevatorRoot.transform.position = new Vector3(0f, 0f, 45f); // 1. Платформа лифта (6x6м на полу) GameObject platform = GameObject.CreatePrimitive(PrimitiveType.Cube); platform.name = "Elevator_Platform"; platform.transform.SetParent(elevatorRoot.transform); platform.transform.localPosition = new Vector3(0f, 0.05f, 0f); platform.transform.localScale = new Vector3(6f, 0.1f, 6f); if (echoMat != null) platform.GetComponent().sharedMaterial = echoMat; CreateElevatorFrame(elevatorRoot.transform); // 2. Стойка терминала управления лифтом GameObject console = new GameObject("Elevator_Terminal_Console"); console.transform.SetParent(elevatorRoot.transform); console.transform.localPosition = new Vector3(1.8f, 0f, -2.2f); console.transform.localRotation = Quaternion.Euler(0f, -35f, 0f); GameObject stand = GameObject.CreatePrimitive(PrimitiveType.Cylinder); stand.name = "Stand"; stand.transform.SetParent(console.transform); stand.transform.localPosition = new Vector3(0f, 0.55f, 0f); stand.transform.localScale = new Vector3(0.2f, 0.55f, 0.2f); if (echoMat != null) stand.GetComponent().sharedMaterial = echoMat; GameObject screenBox = GameObject.CreatePrimitive(PrimitiveType.Cube); screenBox.name = "ScreenBox"; screenBox.transform.SetParent(console.transform); screenBox.transform.localPosition = new Vector3(0f, 1.15f, 0f); screenBox.transform.localRotation = Quaternion.Euler(30f, 0f, 0f); screenBox.transform.localScale = new Vector3(0.7f, 0.45f, 0.12f); if (echoMat != null) screenBox.GetComponent().sharedMaterial = echoMat; GameObject lightGo = new GameObject("Terminal_StatusLight"); lightGo.transform.SetParent(console.transform); lightGo.transform.localPosition = new Vector3(0f, 1.45f, 0f); Light statusLight = lightGo.AddComponent(); statusLight.type = LightType.Point; statusLight.color = new Color(1f, 0.85f, 0.1f); statusLight.intensity = 2.5f; statusLight.range = 5.0f; var terminalComp = console.AddComponent(); terminalComp.interactionDistance = 3.8f; terminalComp.statusLight = statusLight; terminalComp.screenRenderer = screenBox.GetComponent(); CreateWorldText(console.transform, new Vector3(0f, 1.7f, 0f), "ТЕРМИНАЛ ЛИФТА\n[ВВЕДИТЕ 4 ЦИФРЫ]", new Color(1f, 0.85f, 0.1f), 0.035f); } private static void CreateElevatorFrame(Transform parent) { Vector3[] corners = new Vector3[] { new Vector3(-2.9f, 2.25f, -2.9f), new Vector3(2.9f, 2.25f, -2.9f), new Vector3(-2.9f, 2.25f, 2.9f), new Vector3(2.9f, 2.25f, 2.9f) }; for (int i = 0; i < corners.Length; i++) { GameObject pillar = GameObject.CreatePrimitive(PrimitiveType.Cube); pillar.name = $"Pillar_{i}"; pillar.transform.SetParent(parent); pillar.transform.localPosition = corners[i]; pillar.transform.localScale = new Vector3(0.25f, 4.5f, 0.25f); if (echoMat != null) pillar.GetComponent().sharedMaterial = echoMat; } GameObject beam = GameObject.CreatePrimitive(PrimitiveType.Cube); beam.name = "Elevator_TopBeam"; beam.transform.SetParent(parent); beam.transform.localPosition = new Vector3(0f, 4.3f, -2.9f); beam.transform.localScale = new Vector3(6f, 0.4f, 0.3f); if (echoMat != null) beam.GetComponent().sharedMaterial = echoMat; CreateWorldText(parent, new Vector3(0f, 4.3f, -3.1f), "ВЫХОД: ЭВАКУАЦИОННЫЙ ЛИФТ (УРОВЕНЬ 2)", new Color(1f, 0.85f, 0.1f), 0.05f); } private static void SetupFourStoryNotes() { var oldNotes = GameObject.Find("StoryNotes_Container"); if (oldNotes != null) { Undo.DestroyObjectImmediate(oldNotes); } GameObject notesRoot = new GameObject("StoryNotes_Container"); Undo.RegisterCreatedObjectUndo(notesRoot, "Create StoryNotes_Container"); var noteDefs = new[] { new { pos = 0, coords = new Vector3(-36f, 0.85f, -36f), title = "ЗАПИСКА ДЕЖУРНОГО ТЕХНИКА #1", sector = "СЕКТОР B: КРИОГЕНИКА", text = "Срочное сообщение: система заблокировала шлюз из-за проникновения мутантов. Первую цифру кода аварийного лифта пришлось изменить, чтобы изолировать отсек. Запомните её, если хотите выбраться отсюда живым! Слепые твари не видят вас в темноте, пока вы стоите неподвижно." }, new { pos = 1, coords = new Vector3(36f, 0.85f, -36f), title = "РАПОРТ СЛУЖБЫ БЕЗОПАСНОСТИ #2", sector = "СЕКТОР C: АРХИВ ОРУЖЕЙНОЙ", text = "Внимание личному составу: мутанты ориентируются исключительно на эхо шагов и грохот выстрелов! Вторая цифра защитного протокола лифта сохранена в этой директиве. Берегите патроны и используйте скрытное перемещение." }, new { pos = 2, coords = new Vector3(-34f, 0.85f, -10f), title = "ДНЕВНИК ГЛАВНОГО БИОХИМИКА #3", sector = "СЕКТОР D: БИОХИМИЯ", text = "Эксперимент 'Эхо-11' полностью вышел из-под контроля. Все отсеки запечатаны. Третья цифра для активации транспортного лифта зафиксирована здесь. Если вы читаете это — немедленно прорывайтесь к реакторному залу на севере!" }, new { pos = 3, coords = new Vector3(36f, 0.85f, 16f), title = "СЕРВЕРНЫЙ ЛОГ ЭНЕРГОСИСТЕМЫ #4", sector = "СЕКТОР G: ГЕНЕТИКА И КИБЕРНЕТИКА", text = "Автоматический протокол эвакуации: питание гидравлической шахты переведено на аварийный генератор. Финальная, четвертая цифра защитного кода записана в этот модуль. Транспортный лифт ожидает в северном крыле комплекса." } }; foreach (var def in noteDefs) { CreateNoteObject(notesRoot.transform, def.pos, def.coords, def.title, def.sector, def.text); } } private static void CreateNoteObject(Transform parent, int digitPos, Vector3 position, string title, string sector, string text) { GameObject noteGo = new GameObject($"Note_Digit_{digitPos + 1}"); noteGo.transform.SetParent(parent); noteGo.transform.position = position; GameObject folder = GameObject.CreatePrimitive(PrimitiveType.Cube); folder.name = "FolderVisual"; folder.transform.SetParent(noteGo.transform); folder.transform.localPosition = Vector3.zero; folder.transform.localRotation = Quaternion.Euler(0f, Random.Range(-30f, 30f), 0f); folder.transform.localScale = new Vector3(0.4f, 0.04f, 0.55f); if (echoMat != null) folder.GetComponent().sharedMaterial = echoMat; GameObject paper = GameObject.CreatePrimitive(PrimitiveType.Cube); paper.name = "PaperSheet"; paper.transform.SetParent(folder.transform); paper.transform.localPosition = new Vector3(0f, 0.55f, 0f); paper.transform.localScale = new Vector3(0.85f, 0.1f, 0.85f); if (echoMat != null) paper.GetComponent().sharedMaterial = echoMat; GameObject markerLight = new GameObject("NoteLight"); markerLight.transform.SetParent(noteGo.transform); markerLight.transform.localPosition = new Vector3(0f, 0.4f, 0f); Light lightComp = markerLight.AddComponent(); lightComp.type = LightType.Point; lightComp.color = new Color(1.0f, 0.85f, 0.1f); lightComp.intensity = 2.0f; lightComp.range = 3.5f; NotePickup pickup = noteGo.AddComponent(); pickup.digitPosition = digitPos; pickup.noteTitle = title; pickup.sectorName = sector; pickup.storyText = text; pickup.interactionDistance = 3.6f; // 3D текстовая подсказка отключена по запросу пользователя (убрана желтая надпись НАЖМИТЕ [E]) // CreateWorldText(noteGo.transform, new Vector3(0f, 0.7f, 0f), $"[ЗАПИСКА #{digitPos + 1}]\nНАЖМИТЕ [E]", new Color(1f, 0.85f, 0.1f), 0.025f); } private static void CreateWorldText(Transform parent, Vector3 localPos, string text, Color color, float characterSize) { GameObject textGo = new GameObject("FloatingLabel"); textGo.transform.SetParent(parent); textGo.transform.localPosition = localPos; TextMesh tm = textGo.AddComponent(); tm.text = text; tm.fontSize = 42; tm.characterSize = characterSize; tm.color = color; tm.alignment = TextAlignment.Center; tm.anchor = TextAnchor.MiddleCenter; tm.fontStyle = FontStyle.Bold; if (defaultUiFont != null) tm.font = defaultUiFont; } private static void SetupStoryUIInHUDCanvas() { GameObject hudCanvasGo = GameObject.Find("HUD_Canvas"); if (hudCanvasGo == null) { hudCanvasGo = new GameObject("HUD_Canvas"); Canvas c = hudCanvasGo.AddComponent(); c.renderMode = RenderMode.ScreenSpaceOverlay; hudCanvasGo.AddComponent(); hudCanvasGo.AddComponent(); } Transform hudRoot = hudCanvasGo.transform; SetupInteractionPrompt(hudRoot); SetupCodeHUDTracker(hudRoot); SetupNoteViewerPanel(hudRoot); SetupElevatorKeypadPanel(hudRoot); SetupLevelSuccessPanel(hudRoot); } private static void SetupInteractionPrompt(Transform hudRoot) { var old = hudRoot.Find("InteractionPromptPanel"); if (old != null) Undo.DestroyObjectImmediate(old.gameObject); } private static void SetupCodeHUDTracker(Transform hudRoot) { var old = hudRoot.Find("CodeTrackerHUD"); if (old != null) Undo.DestroyObjectImmediate(old.gameObject); GameObject trackerObj = new GameObject("CodeTrackerHUD"); trackerObj.transform.SetParent(hudRoot, false); RectTransform rt = trackerObj.AddComponent(); rt.anchorMin = new Vector2(1.0f, 1.0f); rt.anchorMax = new Vector2(1.0f, 1.0f); rt.pivot = new Vector2(1.0f, 1.0f); rt.anchoredPosition = new Vector2(-25f, -25f); rt.sizeDelta = new Vector2(260f, 68f); Image bg = trackerObj.AddComponent(); bg.color = new Color(0.04f, 0.04f, 0.04f, 0.88f); Outline outline = trackerObj.AddComponent(); outline.effectColor = new Color(1.0f, 0.85f, 0.1f, 0.5f); outline.effectDistance = new Vector2(1f, -1f); GameObject labelObj = new GameObject("Label"); labelObj.transform.SetParent(trackerObj.transform, false); RectTransform labelRt = labelObj.AddComponent(); labelRt.anchorMin = new Vector2(0f, 0.6f); labelRt.anchorMax = new Vector2(1f, 1f); labelRt.offsetMin = new Vector2(10f, 0f); labelRt.offsetMax = new Vector2(-10f, -4f); Text labelTxt = labelObj.AddComponent(); labelTxt.font = defaultUiFont; labelTxt.fontSize = 13; labelTxt.fontStyle = FontStyle.Bold; labelTxt.alignment = TextAnchor.MiddleCenter; labelTxt.color = new Color(1.0f, 0.85f, 0.1f, 0.9f); labelTxt.text = "КОД ЛИФТА (УРОВЕНЬ 2):"; Text[] slotTexts = new Text[4]; Image[] slotBorders = new Image[4]; float startX = 25f; float spacing = 55f; for (int i = 0; i < 4; i++) { GameObject slotObj = new GameObject($"Slot_{i}"); slotObj.transform.SetParent(trackerObj.transform, false); RectTransform slotRt = slotObj.AddComponent(); slotRt.anchorMin = new Vector2(0f, 0f); slotRt.anchorMax = new Vector2(0f, 0.6f); slotRt.pivot = new Vector2(0.5f, 0.5f); slotRt.anchoredPosition = new Vector2(startX + i * spacing + 20f, 20f); slotRt.sizeDelta = new Vector2(42f, 32f); Image slotBg = slotObj.AddComponent(); slotBg.color = new Color(0.08f, 0.08f, 0.08f, 0.95f); slotBorders[i] = slotBg; Outline slotOutline = slotObj.AddComponent(); slotOutline.effectColor = new Color(0.3f, 0.3f, 0.3f, 0.5f); slotOutline.effectDistance = new Vector2(1f, -1f); GameObject txtObj = new GameObject("Text"); txtObj.transform.SetParent(slotObj.transform, false); RectTransform txtRt = txtObj.AddComponent(); txtRt.anchorMin = Vector2.zero; txtRt.anchorMax = Vector2.one; txtRt.sizeDelta = Vector2.zero; Text st = txtObj.AddComponent(); st.font = handwrittenFont; st.fontSize = 20; st.fontStyle = FontStyle.Bold; st.alignment = TextAnchor.MiddleCenter; st.color = new Color(0.45f, 0.45f, 0.45f, 0.7f); st.text = "?"; slotTexts[i] = st; } var trackerComp = trackerObj.AddComponent(); SetField(trackerComp, "digitSlotTexts", slotTexts); SetField(trackerComp, "digitSlotBorders", slotBorders); } private static void SetupNoteViewerPanel(Transform hudRoot) { var old = hudRoot.Find("NoteViewerPanel"); if (old != null) Undo.DestroyObjectImmediate(old.gameObject); var tornPaperSprite = AssetDatabase.LoadAssetAtPath("Assets/Textures/TornPaper_Sprite.png"); var inkFreeFont = AssetDatabase.LoadAssetAtPath("Assets/Fonts/Inkfree.ttf"); var courierFont = Font.CreateDynamicFontFromOSFont("Courier New", 18); if (courierFont == null) courierFont = Font.CreateDynamicFontFromOSFont("Consolas", 18); if (courierFont == null) courierFont = defaultUiFont; GameObject rootPanel = new GameObject("NoteViewerPanel", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button)); rootPanel.transform.SetParent(hudRoot, false); RectTransform rootRt = rootPanel.GetComponent(); rootRt.anchorMin = Vector2.zero; rootRt.anchorMax = Vector2.one; rootRt.sizeDelta = Vector2.zero; Image overlay = rootPanel.GetComponent(); overlay.color = new Color(0.02f, 0.02f, 0.03f, 0.82f); Button overlayBtn = rootPanel.GetComponent