using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.SceneManagement; using UnityEngine.UI; public static class Lab7SceneBuilder { private const string ScenePath = "Assets/_Scenes/Lab07_TalentSystem.unity"; private const string NodePrefabPath = "Assets/_Prefabs/UI/TalentNodeUI.prefab"; private const string TalentIconFolder = "Assets/_Data/Talents/Icons"; [MenuItem("Tools/Technical Game Design/Lab7/Create Talent System Scene")] public static void CreateTalentSystemScene() { EnsureProjectFolders(); Font regularFont = InstallUbuntuFont( "Ubuntu-Regular.ttf", new[] { "/usr/share/fonts/truetype/ubuntu/Ubuntu-R.ttf", "/usr/share/fonts/TTF/Ubuntu-R.ttf", "/usr/share/fonts/Ubuntu-R.ttf" }, "regular"); Font boldFont = InstallUbuntuFont( "Ubuntu-Bold.ttf", new[] { "/usr/share/fonts/truetype/ubuntu/Ubuntu-B.ttf", "/usr/share/fonts/TTF/Ubuntu-B.ttf", "/usr/share/fonts/Ubuntu-B.ttf" }, "bold"); if (boldFont == null) { boldFont = regularFont; } Material arenaMaterial = CreateMaterial("Assets/_Materials/Arena_Ground.mat", new Color(0.22f, 0.27f, 0.23f, 1f)); Material playerMaterial = CreateMaterial("Assets/_Materials/Player_Gold.mat", new Color(0.92f, 0.62f, 0.18f, 1f)); Material mannequinMaterial = CreateMaterial("Assets/_Materials/Training_Dummy.mat", new Color(0.48f, 0.34f, 0.22f, 1f)); Material crystalMaterial = CreateMaterial("Assets/_Materials/Experience_Crystal.mat", new Color(0.16f, 0.78f, 0.96f, 1f), true); Material decorMaterial = CreateMaterial("Assets/_Materials/Decor_Stone.mat", new Color(0.34f, 0.36f, 0.38f, 1f)); Dictionary talents = CreateTalentAssets(); TalentNodeUI nodePrefab = CreateTalentNodePrefab(regularFont, boldFont); Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); scene.name = "Lab07_TalentSystem"; ConfigureSceneLighting(); Camera mainCamera = CreateCamera(); PlayerStats playerStats = CreatePlayer(playerMaterial, mainCamera); GameObject systemsObject = new GameObject("GameSystems"); TalentManager talentManager = systemsObject.AddComponent(); talentManager.playerStats = playerStats; talentManager.startingTalentPoints = 1; talentManager.availableTalentPoints = 1; talentManager.allTalents = new List(talents.Values); LevelSystem levelSystem = systemsObject.AddComponent(); levelSystem.talentManager = talentManager; CreateArena(arenaMaterial); CreateTrainingDummies(mannequinMaterial); CreateDecorations(decorMaterial); Canvas canvas = CreateCanvas(); EnsureEventSystem(); NotificationUI notificationUI = CreateNotificationUI(canvas.transform, regularFont); talentManager.notificationUI = notificationUI; levelSystem.notificationUI = notificationUI; CreateLevelHud(canvas.transform, regularFont, boldFont, levelSystem); CreateStatsHud(canvas.transform, regularFont, boldFont, playerStats); CreateTalentTree(canvas.transform, regularFont, boldFont, nodePrefab, talentManager, playerStats); CreateExperiencePickups(crystalMaterial, levelSystem, notificationUI); EditorUtility.SetDirty(talentManager); EditorUtility.SetDirty(levelSystem); EditorSceneManager.SaveScene(scene, ScenePath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log($"Lab 7 talent system scene created: {ScenePath}"); } [MenuItem("Tools/Technical Game Design/Lab7/Validate Generated Scene")] public static void ValidateGeneratedScene() { if (!File.Exists(ToSystemPath(ScenePath))) { Debug.LogError($"Scene does not exist: {ScenePath}"); return; } Scene scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single); int errors = 0; PlayerStats playerStats = UnityEngine.Object.FindObjectOfType(true); TalentManager talentManager = UnityEngine.Object.FindObjectOfType(true); LevelSystem levelSystem = UnityEngine.Object.FindObjectOfType(true); TalentTreeUI talentTreeUI = UnityEngine.Object.FindObjectOfType(true); NotificationUI notificationUI = UnityEngine.Object.FindObjectOfType(true); errors += RequireObject(playerStats, "PlayerStats"); errors += RequireObject(talentManager, "TalentManager"); errors += RequireObject(levelSystem, "LevelSystem"); errors += RequireObject(talentTreeUI, "TalentTreeUI"); errors += RequireObject(notificationUI, "NotificationUI"); if (talentManager != null) { errors += RequireObject(talentManager.playerStats, "TalentManager.playerStats"); errors += RequireObject(talentManager.notificationUI, "TalentManager.notificationUI"); errors += RequireCondition(talentManager.allTalents != null && talentManager.allTalents.Count == 10, "TalentManager must reference exactly 10 talents."); errors += ValidateTalentPrerequisites(talentManager); } if (levelSystem != null) { errors += RequireObject(levelSystem.talentManager, "LevelSystem.talentManager"); errors += RequireObject(levelSystem.notificationUI, "LevelSystem.notificationUI"); errors += RequireObject(levelSystem.levelText, "LevelSystem.levelText"); errors += RequireObject(levelSystem.expText, "LevelSystem.expText"); errors += RequireObject(levelSystem.expFillImage, "LevelSystem.expFillImage"); } if (talentTreeUI != null) { errors += RequireObject(talentTreeUI.panelRoot, "TalentTreeUI.panelRoot"); errors += RequireObject(talentTreeUI.lineRoot, "TalentTreeUI.lineRoot"); errors += RequireObject(talentTreeUI.nodesRoot, "TalentTreeUI.nodesRoot"); errors += RequireObject(talentTreeUI.nodePrefab, "TalentTreeUI.nodePrefab"); errors += RequireObject(talentTreeUI.tooltip, "TalentTreeUI.tooltip"); errors += RequireObject(talentTreeUI.statsPanel, "TalentTreeUI.statsPanel"); errors += RequireObject(talentTreeUI.saveButton, "TalentTreeUI.saveButton"); errors += RequireObject(talentTreeUI.loadButton, "TalentTreeUI.loadButton"); errors += RequireObject(talentTreeUI.resetButton, "TalentTreeUI.resetButton"); } errors += ValidateMissingScripts(scene); errors += ValidateUbuntuFonts(scene); if (errors == 0) { Debug.Log("Lab 7 validation passed."); } else { Debug.LogError($"Lab 7 validation failed. Errors: {errors}"); } } private static void EnsureProjectFolders() { string[] folders = { "Assets/_Scenes", "Assets/_Scripts", "Assets/_Scripts/Core", "Assets/_Scripts/Stats", "Assets/_Scripts/Talents", "Assets/_Scripts/UI", "Assets/_Scripts/Gameplay", "Assets/_Scripts/Save", "Assets/_Data", "Assets/_Data/Talents", TalentIconFolder, "Assets/_Prefabs", "Assets/_Prefabs/UI", "Assets/_Materials", "Assets/_Fonts", "Assets/Editor" }; for (int i = 0; i < folders.Length; i++) { if (!Directory.Exists(folders[i])) { Directory.CreateDirectory(folders[i]); } } AssetDatabase.Refresh(); } private static Font InstallUbuntuFont(string destinationFileName, string[] preferredPaths, string label) { string destinationPath = "Assets/_Fonts/" + destinationFileName; string sourcePath = FindExistingFile(preferredPaths); if (string.IsNullOrEmpty(sourcePath)) { sourcePath = FindFontRecursive(destinationFileName.Contains("Bold") ? "Ubuntu-B.ttf" : "Ubuntu-R.ttf"); } if (!string.IsNullOrEmpty(sourcePath)) { File.Copy(sourcePath, destinationPath, true); AssetDatabase.ImportAsset(destinationPath, ImportAssetOptions.ForceUpdate); Font font = AssetDatabase.LoadAssetAtPath(destinationPath); if (font != null) { return font; } } Debug.LogWarning($"Ubuntu {label} font was not found in system font folders. Arial fallback will be used."); return Resources.GetBuiltinResource("Arial.ttf"); } private static string FindExistingFile(string[] paths) { for (int i = 0; i < paths.Length; i++) { if (File.Exists(paths[i])) { return paths[i]; } } return null; } private static string FindFontRecursive(string fileName) { string[] roots = { "/usr/share/fonts", "/usr/share/fonts/truetype/ubuntu", "/usr/share/fonts/TTF" }; for (int i = 0; i < roots.Length; i++) { if (!Directory.Exists(roots[i])) { continue; } string[] matches = Directory.GetFiles(roots[i], fileName, SearchOption.AllDirectories); if (matches.Length > 0) { return matches[0]; } } return null; } private static Material CreateMaterial(string path, Color color, bool emission = false) { Material material = AssetDatabase.LoadAssetAtPath(path); if (material == null) { material = new Material(Shader.Find("Standard")); AssetDatabase.CreateAsset(material, path); } material.color = color; if (emission) { material.EnableKeyword("_EMISSION"); material.SetColor("_EmissionColor", color * 1.8f); } else { material.DisableKeyword("_EMISSION"); } EditorUtility.SetDirty(material); return material; } private static Dictionary CreateTalentAssets() { Color toughnessColor = new Color(0.27f, 0.78f, 0.42f, 1f); Color strengthColor = new Color(0.92f, 0.46f, 0.24f, 1f); Color agilityColor = new Color(0.24f, 0.76f, 0.86f, 1f); Color manaColor = new Color(0.42f, 0.52f, 0.96f, 1f); Color masteryColor = new Color(1.00f, 0.72f, 0.24f, 1f); TalentSpec[] specs = { new TalentSpec( "Toughness", "Выносливость", "Увеличивает максимальное здоровье персонажа.", 1, new Vector2(-340f, 170f), toughnessColor, new StatModifier(StatType.MaxHealth, ModifierType.FlatAdd, 20f), Array.Empty()), new TalentSpec( "Strength", "Сила", "Увеличивает базовый урон персонажа.", 1, new Vector2(-90f, 170f), strengthColor, new StatModifier(StatType.Damage, ModifierType.FlatAdd, 5f), Array.Empty()), new TalentSpec( "Agility", "Ловкость", "Увеличивает скорость передвижения.", 1, new Vector2(160f, 170f), agilityColor, new StatModifier(StatType.MoveSpeed, ModifierType.FlatAdd, 1f), Array.Empty()), new TalentSpec( "ManaFlow", "Поток маны", "Увеличивает максимальный запас маны.", 1, new Vector2(410f, 170f), manaColor, new StatModifier(StatType.MaxMana, ModifierType.FlatAdd, 25f), Array.Empty()), new TalentSpec( "AdvancedToughness", "Улучшенная выносливость", "Дополнительно усиливает запас здоровья.", 1, new Vector2(-340f, 35f), toughnessColor, new StatModifier(StatType.MaxHealth, ModifierType.FlatAdd, 30f), new[] { "Toughness" }), new TalentSpec( "BattleTraining", "Боевая подготовка", "Повышает весь наносимый урон на 10%.", 1, new Vector2(-90f, 35f), strengthColor, new StatModifier(StatType.Damage, ModifierType.PercentAdd, 0.10f), new[] { "Strength" }), new TalentSpec( "CriticalEye", "Меткий взгляд", "Увеличивает шанс критического удара.", 1, new Vector2(35f, -100f), strengthColor, new StatModifier(StatType.CriticalChance, ModifierType.FlatAdd, 5f), new[] { "Strength" }), new TalentSpec( "SwiftStep", "Быстрый шаг", "Повышает скорость передвижения на 10%.", 1, new Vector2(160f, 35f), agilityColor, new StatModifier(StatType.MoveSpeed, ModifierType.PercentAdd, 0.10f), new[] { "Agility" }), new TalentSpec( "ArcaneFocus", "Магическая концентрация", "Сокращает время восстановления способностей на 10%.", 1, new Vector2(410f, 35f), manaColor, new StatModifier(StatType.AbilityCooldownReduction, ModifierType.FlatAdd, 10f), new[] { "ManaFlow" }), new TalentSpec( "MasterWarrior", "Мастер-воин", "Финальный боевой талант: больше урона и здоровья.", 2, new Vector2(-220f, -185f), masteryColor, new[] { new StatModifier(StatType.Damage, ModifierType.PercentAdd, 0.15f), new StatModifier(StatType.MaxHealth, ModifierType.FlatAdd, 10f) }, new[] { "BattleTraining", "AdvancedToughness" }) }; Dictionary assets = new Dictionary(); for (int i = 0; i < specs.Length; i++) { TalentSpec spec = specs[i]; TalentData talent = LoadOrCreateTalent(spec.id); talent.talentId = spec.id; talent.displayName = spec.displayName; talent.description = spec.description; talent.cost = spec.cost; talent.treePosition = spec.treePosition; talent.branchColor = spec.branchColor; talent.modifiers = spec.modifiers; talent.unlockedByDefault = false; talent.icon = CreateTalentIcon(spec.id, spec.branchColor); assets[spec.id] = talent; } for (int i = 0; i < specs.Length; i++) { TalentSpec spec = specs[i]; TalentData talent = assets[spec.id]; TalentData[] prerequisites = new TalentData[spec.prerequisiteIds.Length]; for (int prerequisiteIndex = 0; prerequisiteIndex < spec.prerequisiteIds.Length; prerequisiteIndex++) { prerequisites[prerequisiteIndex] = assets[spec.prerequisiteIds[prerequisiteIndex]]; } talent.prerequisites = prerequisites; EditorUtility.SetDirty(talent); } AssetDatabase.SaveAssets(); return assets; } private static TalentData LoadOrCreateTalent(string id) { string path = $"Assets/_Data/Talents/{id}.asset"; TalentData talent = AssetDatabase.LoadAssetAtPath(path); if (talent == null) { talent = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(talent, path); } return talent; } private static Sprite CreateTalentIcon(string id, Color color) { string iconPath = $"{TalentIconFolder}/{id}_Icon.png"; Texture2D texture = new Texture2D(64, 64, TextureFormat.RGBA32, false); Color clear = new Color(0f, 0f, 0f, 0f); Vector2 center = new Vector2(31.5f, 31.5f); for (int y = 0; y < 64; y++) { for (int x = 0; x < 64; x++) { float distance = Vector2.Distance(new Vector2(x, y), center); if (distance > 29f) { texture.SetPixel(x, y, clear); } else if (distance > 25f) { texture.SetPixel(x, y, UITheme.Gold); } else if (distance < 8f) { texture.SetPixel(x, y, Color.white); } else { texture.SetPixel(x, y, color); } } } texture.Apply(); File.WriteAllBytes(ToSystemPath(iconPath), texture.EncodeToPNG()); UnityEngine.Object.DestroyImmediate(texture); AssetDatabase.ImportAsset(iconPath, ImportAssetOptions.ForceUpdate); TextureImporter importer = AssetImporter.GetAtPath(iconPath) as TextureImporter; if (importer != null) { importer.textureType = TextureImporterType.Sprite; importer.spriteImportMode = SpriteImportMode.Single; importer.alphaIsTransparency = true; importer.mipmapEnabled = false; importer.SaveAndReimport(); } return AssetDatabase.LoadAssetAtPath(iconPath); } private static TalentNodeUI CreateTalentNodePrefab(Font regularFont, Font boldFont) { GameObject root = new GameObject("TalentNodeUI", typeof(RectTransform), typeof(Image), typeof(Button), typeof(Outline), typeof(TalentNodeUI)); RectTransform rootRect = root.GetComponent(); rootRect.sizeDelta = new Vector2(166f, 94f); Image background = root.GetComponent(); background.color = UITheme.LockedNode; background.raycastTarget = true; Button button = root.GetComponent