using System.IO; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.SceneManagement; using UnityEngine.UI; public static class Lab4SceneBuilder { private const string ScriptsPath = "Assets/_Scripts"; private const string DataPath = "Assets/_Data"; private const string DialoguePath = "Assets/_Data/Dialogue"; private const string ItemsPath = "Assets/_Data/Items"; private const string PrefabsPath = "Assets/_Prefabs"; private const string ScenesPath = "Assets/_Scenes"; private const string MaterialsPath = "Assets/_Materials"; private const string FontsPath = "Assets/_Fonts"; private const string ScenePath = "Assets/_Scenes/Lab04_DialogueSystem.unity"; private const string ResponseButtonPrefabPath = "Assets/_Prefabs/DialogueResponseButton.prefab"; [MenuItem("Tools/Technical Game Design/Lab4/Create Dialogue System Scene")] public static void CreateDialogueSystemScene() { CreateFolders(); Font regularFont = InstallUbuntuFont(); MaterialLibrary materials = CreateMaterials(); ItemLibrary items = CreateItems(); DialogueLibrary dialogue = CreateDialogue(items); CreateGraphReadme(); GameObject responseButtonPrefab = CreateResponseButtonPrefab(regularFont); Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); scene.name = "Lab04_DialogueSystem"; BuildScene(materials, items, dialogue, responseButtonPrefab, regularFont); EditorSceneManager.SaveScene(scene, ScenePath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log("Lab04 dialogue system scene generated: " + ScenePath); } private static void CreateFolders() { string[] paths = { ScriptsPath, DataPath, DialoguePath, ItemsPath, PrefabsPath, ScenesPath, MaterialsPath, FontsPath, "Assets/Editor" }; foreach (string path in paths) Directory.CreateDirectory(path); AssetDatabase.Refresh(); } private static Font InstallUbuntuFont() { string regularDestination = FontsPath + "/Ubuntu-Regular.ttf"; string boldDestination = FontsPath + "/Ubuntu-Bold.ttf"; string regularSource = FindFirstExistingFile( "/usr/share/fonts/truetype/ubuntu/Ubuntu-Regular.ttf", "/usr/share/fonts/truetype/ubuntu/Ubuntu-R.ttf", "/usr/share/fonts/TTF/Ubuntu-Regular.ttf", "/usr/share/fonts/TTF/Ubuntu-R.ttf", "/usr/share/fonts/Ubuntu-Regular.ttf", "/usr/share/fonts/Ubuntu-R.ttf"); string boldSource = FindFirstExistingFile( "/usr/share/fonts/truetype/ubuntu/Ubuntu-Bold.ttf", "/usr/share/fonts/truetype/ubuntu/Ubuntu-B.ttf", "/usr/share/fonts/TTF/Ubuntu-Bold.ttf", "/usr/share/fonts/TTF/Ubuntu-B.ttf", "/usr/share/fonts/Ubuntu-Bold.ttf", "/usr/share/fonts/Ubuntu-B.ttf"); if (!string.IsNullOrEmpty(regularSource)) File.Copy(regularSource, regularDestination, true); else Debug.LogWarning("Ubuntu font was not found in common Linux font folders. Falling back to built-in Arial."); if (!string.IsNullOrEmpty(boldSource)) File.Copy(boldSource, boldDestination, true); if (File.Exists(regularDestination)) AssetDatabase.ImportAsset(regularDestination); if (File.Exists(boldDestination)) AssetDatabase.ImportAsset(boldDestination); AssetDatabase.Refresh(); Font font = AssetDatabase.LoadAssetAtPath(regularDestination); return font != null ? font : Resources.GetBuiltinResource("Arial.ttf"); } private static string FindFirstExistingFile(params string[] candidates) { foreach (string candidate in candidates) { if (File.Exists(candidate)) return candidate; } return null; } private static MaterialLibrary CreateMaterials() { MaterialLibrary library = new MaterialLibrary { ground = CreateMaterial("Ground_Mat", new Color(0.34f, 0.43f, 0.34f)), player = CreateMaterial("Player_Mat", new Color(0.17f, 0.43f, 0.74f)), npc = CreateMaterial("NPC_Mat", new Color(0.68f, 0.44f, 0.17f)), pass = CreateMaterial("VillagePass_Mat", new Color(0.95f, 0.78f, 0.25f)), prop = CreateMaterial("Props_Mat", new Color(0.42f, 0.34f, 0.25f)), accent = CreateMaterial("Accent_Mat", new Color(0.14f, 0.62f, 0.75f)), interactionZone = CreateMaterial("InteractionZone_Mat", new Color(0.25f, 0.75f, 1f, 0.22f), true) }; return library; } private static Material CreateMaterial(string name, Color color, bool transparent = false) { string path = MaterialsPath + "/" + name + ".mat"; Material material = AssetDatabase.LoadAssetAtPath(path); if (material == null) { material = new Material(Shader.Find("Standard")); AssetDatabase.CreateAsset(material, path); } material.name = name; material.color = color; if (transparent) { material.SetFloat("_Mode", 3f); material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); material.SetInt("_ZWrite", 0); material.DisableKeyword("_ALPHATEST_ON"); material.EnableKeyword("_ALPHABLEND_ON"); material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); material.renderQueue = 3000; } else { material.SetFloat("_Mode", 0f); material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One); material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero); material.SetInt("_ZWrite", 1); material.DisableKeyword("_ALPHATEST_ON"); material.DisableKeyword("_ALPHABLEND_ON"); material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); material.renderQueue = -1; } EditorUtility.SetDirty(material); return material; } private static ItemLibrary CreateItems() { ItemLibrary library = new ItemLibrary { villagePass = LoadOrCreateAsset(ItemsPath + "/VillagePass.asset"), oldAmulet = LoadOrCreateAsset(ItemsPath + "/OldAmulet.asset") }; library.villagePass.itemId = "VillagePass"; library.villagePass.itemName = "Пропуск"; library.villagePass.description = "Временный документ для прохода к старосте."; library.villagePass.itemColor = new Color(0.95f, 0.78f, 0.25f); library.villagePass.maxStack = 1; library.oldAmulet.itemId = "OldAmulet"; library.oldAmulet.itemName = "Старый амулет"; library.oldAmulet.description = "Потёртый амулет, который ценит смотритель ворот."; library.oldAmulet.itemColor = new Color(0.35f, 0.82f, 0.8f); library.oldAmulet.maxStack = 1; EditorUtility.SetDirty(library.villagePass); EditorUtility.SetDirty(library.oldAmulet); AssetDatabase.SaveAssets(); return library; } private static DialogueLibrary CreateDialogue(ItemLibrary items) { DialogueLibrary library = new DialogueLibrary { start = LoadOrCreateAsset(DialoguePath + "/StartNode.asset"), newcomer = LoadOrCreateAsset(DialoguePath + "/Node_Newcomer.asset"), passInfo = LoadOrCreateAsset(DialoguePath + "/Node_PassInfo.asset"), noPassWarning = LoadOrCreateAsset(DialoguePath + "/Node_NoPassWarning.asset"), hasPass = LoadOrCreateAsset(DialoguePath + "/Node_HasPass.asset"), work = LoadOrCreateAsset(DialoguePath + "/Node_Work.asset"), reward = LoadOrCreateAsset(DialoguePath + "/Node_Reward.asset"), amuletComplete = LoadOrCreateAsset(DialoguePath + "/Node_AmuletQuestComplete.asset"), goodbye = LoadOrCreateAsset(DialoguePath + "/Node_Goodbye.asset") }; SetupNode(library.start, "StartNode", "Смотритель ворот", "Приветствую, путник. Ты впервые в нашем поселении?", false, Response("Да, я только пришёл.", library.newcomer), Response("Я ищу работу.", library.work), Response("Мне пора.", library.goodbye)); SetupNode(library.newcomer, "Node_Newcomer", "Смотритель ворот", "Тогда тебе понадобится пропуск. Без него стража не пустит тебя к старосте.", false, Response("Где его получить?", library.passInfo), Response("У меня уже есть пропуск.", library.hasPass, requiredItem: items.villagePass, unavailableText: "Нужен предмет: Пропуск"), Response("Понятно.", library.goodbye)); SetupNode(library.passInfo, "Node_PassInfo", "Смотритель ворот", "Осмотри площадку рядом с воротами. Иногда там оставляют временные пропуска.", false, Response("Спасибо, я поищу.", library.goodbye), Response("Я лучше сразу пойду к старосте.", library.noPassWarning)); SetupNode(library.noPassWarning, "Node_NoPassWarning", "Смотритель ворот", "Без пропуска тебя не примут. Сначала найди документ.", false, Response("Хорошо, найду пропуск.", library.goodbye)); SetupNode(library.hasPass, "Node_HasPass", "Смотритель ворот", "Отлично. Раз у тебя есть пропуск, можешь пройти дальше. Передай старосте, что я тебя направил.", false, Response("Спасибо за помощь.", library.reward, rewardItem: items.oldAmulet, rewardAmount: 1, eventId: "Gatekeeper_Rewards_Amulet"), Response("До встречи.", library.goodbye)); SetupNode(library.work, "Node_Work", "Смотритель ворот", "Работа есть. Принеси старый амулет, и я расскажу больше.", false, Response("У меня есть амулет.", library.amuletComplete, requiredItem: items.oldAmulet, consumeRequiredItem: true, unavailableText: "Нужен предмет: Старый амулет", eventId: "Gatekeeper_Amulet_TurnedIn"), Response("Я вернусь позже.", library.goodbye)); SetupNode(library.reward, "Node_Reward", "Смотритель ворот", "Возьми этот старый амулет. Он может пригодиться в разговоре о работе.", false, Response("Благодарю.", library.goodbye)); SetupNode(library.amuletComplete, "Node_AmuletQuestComplete", "Смотритель ворот", "Ты справился. Теперь я доверяю тебе. Для следующего задания приходи позже.", false, Response("Понял.", library.goodbye)); SetupNode(library.goodbye, "Node_Goodbye", "Смотритель ворот", "Удачи тебе.", true); AssetDatabase.SaveAssets(); return library; } private static DialogueResponse Response( string text, DialogueNode nextNode, bool endsDialogue = false, ItemData requiredItem = null, int requiredItemAmount = 1, string unavailableText = "", bool hideIfRequirementsNotMet = false, bool consumeRequiredItem = false, ItemData rewardItem = null, int rewardAmount = 0, string eventId = "") { return new DialogueResponse { responseText = text, nextNode = nextNode, endsDialogue = endsDialogue, requiredItem = requiredItem, requiredItemAmount = Mathf.Max(1, requiredItemAmount), unavailableText = unavailableText, hideIfRequirementsNotMet = hideIfRequirementsNotMet, consumeRequiredItem = consumeRequiredItem, rewardItem = rewardItem, rewardAmount = rewardAmount, eventId = eventId }; } private static void SetupNode(DialogueNode node, string id, string speaker, string line, bool isEnd, params DialogueResponse[] responses) { node.nodeId = id; node.speakerName = speaker; node.npcLine = line; node.isEndNode = isEnd; node.responses = responses; node.editorNote = "Generated by Lab4SceneBuilder."; EditorUtility.SetDirty(node); } private static T LoadOrCreateAsset(string path) where T : ScriptableObject { T asset = AssetDatabase.LoadAssetAtPath(path); if (asset != null) return asset; asset = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(asset, path); return asset; } private static void CreateGraphReadme() { const string text = "StartNode\n" + "├─ Да, я только пришёл → Node_Newcomer\n" + "│ ├─ Где его получить? → Node_PassInfo → Node_Goodbye\n" + "│ ├─ У меня уже есть пропуск [requires VillagePass] → Node_HasPass → Node_Reward [gives OldAmulet] → Node_Goodbye\n" + "│ └─ Понятно → Node_Goodbye\n" + "├─ Я ищу работу → Node_Work\n" + "│ ├─ У меня есть амулет [requires OldAmulet, consumes] → Node_AmuletQuestComplete → Node_Goodbye\n" + "│ └─ Я вернусь позже → Node_Goodbye\n" + "└─ Мне пора → Node_Goodbye\n"; File.WriteAllText(DialoguePath + "/DialogueGraph_Readme.txt", text); } private static GameObject CreateResponseButtonPrefab(Font font) { GameObject root = new GameObject("DialogueResponseButton", typeof(RectTransform), typeof(Image), typeof(Button), typeof(LayoutElement), typeof(Outline)); RectTransform rect = root.GetComponent(); rect.sizeDelta = new Vector2(0f, 38f); Image image = root.GetComponent(); image.color = new Color(0.095f, 0.12f, 0.15f, 0.98f); Outline outline = root.GetComponent(); outline.effectColor = new Color(0.36f, 0.48f, 0.54f, 0.28f); outline.effectDistance = new Vector2(1f, -1f); Button button = root.GetComponent