Files
OTG_7/Assets/Editor/Lab7SceneBuilder.cs
T
2026-06-04 22:14:21 +03:00

1154 lines
50 KiB
C#

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<string, TalentData> 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>();
talentManager.playerStats = playerStats;
talentManager.startingTalentPoints = 1;
talentManager.availableTalentPoints = 1;
talentManager.allTalents = new List<TalentData>(talents.Values);
LevelSystem levelSystem = systemsObject.AddComponent<LevelSystem>();
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<PlayerStats>(true);
TalentManager talentManager = UnityEngine.Object.FindObjectOfType<TalentManager>(true);
LevelSystem levelSystem = UnityEngine.Object.FindObjectOfType<LevelSystem>(true);
TalentTreeUI talentTreeUI = UnityEngine.Object.FindObjectOfType<TalentTreeUI>(true);
NotificationUI notificationUI = UnityEngine.Object.FindObjectOfType<NotificationUI>(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<Font>(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<Font>("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<Material>(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<string, TalentData> 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<string>()),
new TalentSpec(
"Strength",
"Сила",
"Увеличивает базовый урон персонажа.",
1,
new Vector2(-90f, 170f),
strengthColor,
new StatModifier(StatType.Damage, ModifierType.FlatAdd, 5f),
Array.Empty<string>()),
new TalentSpec(
"Agility",
"Ловкость",
"Увеличивает скорость передвижения.",
1,
new Vector2(160f, 170f),
agilityColor,
new StatModifier(StatType.MoveSpeed, ModifierType.FlatAdd, 1f),
Array.Empty<string>()),
new TalentSpec(
"ManaFlow",
"Поток маны",
"Увеличивает максимальный запас маны.",
1,
new Vector2(410f, 170f),
manaColor,
new StatModifier(StatType.MaxMana, ModifierType.FlatAdd, 25f),
Array.Empty<string>()),
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<string, TalentData> assets = new Dictionary<string, TalentData>();
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<TalentData>(path);
if (talent == null)
{
talent = ScriptableObject.CreateInstance<TalentData>();
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<Sprite>(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<RectTransform>();
rootRect.sizeDelta = new Vector2(166f, 94f);
Image background = root.GetComponent<Image>();
background.color = UITheme.LockedNode;
background.raycastTarget = true;
Button button = root.GetComponent<Button>();
button.targetGraphic = background;
ColorBlock colors = button.colors;
colors.normalColor = Color.white;
colors.highlightedColor = new Color(1f, 1f, 1f, 0.92f);
colors.pressedColor = new Color(0.84f, 0.84f, 0.84f, 1f);
colors.disabledColor = new Color(0.55f, 0.55f, 0.55f, 1f);
button.colors = colors;
Outline outline = root.GetComponent<Outline>();
outline.effectColor = UITheme.LockedNode;
outline.effectDistance = new Vector2(2f, -2f);
Image icon = CreateUIImage(root.transform, "Icon", Color.white);
SetRect(icon.rectTransform, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(10f, -10f), new Vector2(36f, 36f));
Text nameText = CreateText(root.transform, "Name", boldFont, "Talent", 13, UITheme.Text, TextAnchor.UpperLeft);
nameText.horizontalOverflow = HorizontalWrapMode.Wrap;
nameText.verticalOverflow = VerticalWrapMode.Truncate;
SetRect(nameText.rectTransform, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(0f, 1f), new Vector2(52f, -9f), new Vector2(-60f, 34f));
Text costText = CreateText(root.transform, "Cost", boldFont, "1 очк.", 12, UITheme.Gold, TextAnchor.UpperRight);
SetRect(costText.rectTransform, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-9f, -9f), new Vector2(54f, 24f));
Text stateText = CreateText(root.transform, "State", regularFont, "Заблокировано", 12, UITheme.Text, TextAnchor.LowerCenter);
stateText.horizontalOverflow = HorizontalWrapMode.Wrap;
SetRect(stateText.rectTransform, new Vector2(0f, 0f), new Vector2(1f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 10f), new Vector2(-18f, 30f));
TalentNodeUI node = root.GetComponent<TalentNodeUI>();
node.backgroundImage = background;
node.iconImage = icon;
node.nameText = nameText;
node.costText = costText;
node.stateText = stateText;
node.learnButton = button;
node.borderOutline = outline;
PrefabUtility.SaveAsPrefabAsset(root, NodePrefabPath);
UnityEngine.Object.DestroyImmediate(root);
AssetDatabase.SaveAssets();
return AssetDatabase.LoadAssetAtPath<TalentNodeUI>(NodePrefabPath);
}
private static void ConfigureSceneLighting()
{
RenderSettings.ambientLight = new Color(0.34f, 0.37f, 0.42f, 1f);
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, -35f, 0f);
}
private static Camera CreateCamera()
{
GameObject cameraObject = new GameObject("Main Camera");
cameraObject.tag = "MainCamera";
Camera camera = cameraObject.AddComponent<Camera>();
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = new Color(0.12f, 0.14f, 0.17f, 1f);
camera.fieldOfView = 52f;
cameraObject.AddComponent<AudioListener>();
cameraObject.transform.position = new Vector3(-6f, 8f, -8f);
cameraObject.transform.LookAt(Vector3.up);
return camera;
}
private static PlayerStats CreatePlayer(Material playerMaterial, Camera mainCamera)
{
GameObject player = GameObject.CreatePrimitive(PrimitiveType.Capsule);
player.name = "Player";
player.tag = "Player";
player.transform.position = new Vector3(0f, 1f, -5f);
player.GetComponent<Renderer>().sharedMaterial = playerMaterial;
CapsuleCollider capsuleCollider = player.GetComponent<CapsuleCollider>();
if (capsuleCollider != null)
{
UnityEngine.Object.DestroyImmediate(capsuleCollider);
}
CharacterController characterController = player.AddComponent<CharacterController>();
characterController.height = 2f;
characterController.radius = 0.45f;
characterController.center = Vector3.zero;
characterController.stepOffset = 0.35f;
PlayerStats playerStats = player.AddComponent<PlayerStats>();
SimplePlayerController controller = player.AddComponent<SimplePlayerController>();
controller.playerStats = playerStats;
controller.characterController = characterController;
controller.cameraTransform = mainCamera.transform;
controller.cameraYaw = 42f;
controller.cameraOffset = new Vector3(0f, 9f, -8.5f);
return playerStats;
}
private static void CreateArena(Material arenaMaterial)
{
GameObject arena = GameObject.CreatePrimitive(PrimitiveType.Plane);
arena.name = "Arena";
arena.transform.localScale = new Vector3(3f, 1f, 3f);
arena.GetComponent<Renderer>().sharedMaterial = arenaMaterial;
CreateArenaMarker(new Vector3(0f, 0.03f, 0f), new Vector3(9f, 0.04f, 9f), new Color(0.16f, 0.18f, 0.17f, 1f));
CreateArenaMarker(new Vector3(0f, 0.04f, 0f), new Vector3(4f, 0.05f, 4f), new Color(0.25f, 0.22f, 0.14f, 1f));
}
private static void CreateArenaMarker(Vector3 position, Vector3 scale, Color color)
{
GameObject marker = GameObject.CreatePrimitive(PrimitiveType.Cube);
marker.name = "Arena Marker";
marker.transform.position = position;
marker.transform.localScale = scale;
Material material = new Material(Shader.Find("Standard"));
material.color = color;
marker.GetComponent<Renderer>().sharedMaterial = material;
UnityEngine.Object.DestroyImmediate(marker.GetComponent<BoxCollider>());
}
private static void CreateTrainingDummies(Material mannequinMaterial)
{
Vector3[] positions =
{
new Vector3(-5f, 0f, 3.5f),
new Vector3(0f, 0f, 5f),
new Vector3(5f, 0f, 3.5f)
};
for (int i = 0; i < positions.Length; i++)
{
GameObject root = new GameObject($"Training Dummy {i + 1}");
root.transform.position = positions[i];
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
body.name = "Body";
body.transform.SetParent(root.transform, false);
body.transform.localPosition = new Vector3(0f, 0.9f, 0f);
body.transform.localScale = new Vector3(0.55f, 0.8f, 0.55f);
body.GetComponent<Renderer>().sharedMaterial = mannequinMaterial;
GameObject head = GameObject.CreatePrimitive(PrimitiveType.Sphere);
head.name = "Head";
head.transform.SetParent(root.transform, false);
head.transform.localPosition = new Vector3(0f, 1.85f, 0f);
head.transform.localScale = Vector3.one * 0.55f;
head.GetComponent<Renderer>().sharedMaterial = mannequinMaterial;
GameObject baseObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
baseObject.name = "Base";
baseObject.transform.SetParent(root.transform, false);
baseObject.transform.localPosition = new Vector3(0f, 0.1f, 0f);
baseObject.transform.localScale = new Vector3(1.2f, 0.2f, 1.2f);
baseObject.GetComponent<Renderer>().sharedMaterial = mannequinMaterial;
}
}
private static void CreateDecorations(Material decorMaterial)
{
Vector3[] positions =
{
new Vector3(-9f, 0.5f, -7f),
new Vector3(8f, 0.5f, -7f),
new Vector3(-8f, 0.5f, 8f),
new Vector3(8.5f, 0.5f, 7.5f),
new Vector3(-2.5f, 0.5f, 8.5f)
};
for (int i = 0; i < positions.Length; i++)
{
GameObject decor = GameObject.CreatePrimitive(i % 2 == 0 ? PrimitiveType.Cube : PrimitiveType.Sphere);
decor.name = $"Arena Decor {i + 1}";
decor.transform.position = positions[i];
decor.transform.localScale = i % 2 == 0 ? new Vector3(1.2f, 1f, 1.2f) : Vector3.one * 1.15f;
decor.GetComponent<Renderer>().sharedMaterial = decorMaterial;
}
}
private static void CreateExperiencePickups(Material crystalMaterial, LevelSystem levelSystem, NotificationUI notificationUI)
{
GameObject root = new GameObject("Experience Pickups");
Vector3[] positions =
{
new Vector3(-6f, 0.55f, -2f),
new Vector3(-3f, 0.55f, 1.5f),
new Vector3(2.5f, 0.55f, -1.5f),
new Vector3(5.5f, 0.55f, 1.8f),
new Vector3(0f, 0.55f, 8f)
};
for (int i = 0; i < positions.Length; i++)
{
GameObject crystal = GameObject.CreatePrimitive(PrimitiveType.Sphere);
crystal.name = $"Experience Crystal {i + 1}";
crystal.transform.SetParent(root.transform, true);
crystal.transform.position = positions[i];
crystal.transform.localScale = new Vector3(0.55f, 0.75f, 0.55f);
crystal.GetComponent<Renderer>().sharedMaterial = crystalMaterial;
Collider collider = crystal.GetComponent<Collider>();
if (collider != null)
{
collider.isTrigger = true;
}
ExperiencePickup pickup = crystal.AddComponent<ExperiencePickup>();
pickup.experienceAmount = 50;
pickup.destroyOnCollect = true;
pickup.levelSystem = levelSystem;
pickup.notificationUI = notificationUI;
Light light = crystal.AddComponent<Light>();
light.type = LightType.Point;
light.color = new Color(0.16f, 0.78f, 0.96f, 1f);
light.range = 3.2f;
light.intensity = 1.5f;
}
}
private static Canvas CreateCanvas()
{
GameObject canvasObject = new GameObject("Canvas", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
Canvas canvas = canvasObject.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
CanvasScaler scaler = canvasObject.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920f, 1080f);
scaler.matchWidthOrHeight = 0.5f;
return canvas;
}
private static void EnsureEventSystem()
{
if (UnityEngine.Object.FindObjectOfType<EventSystem>() != null)
{
return;
}
new GameObject("EventSystem", typeof(EventSystem), typeof(StandaloneInputModule));
}
private static NotificationUI CreateNotificationUI(Transform parent, Font font)
{
Image panel = CreateUIImage(parent, "Notification", new Color(0.07f, 0.08f, 0.10f, 0.92f));
SetRect(panel.rectTransform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -18f), new Vector2(560f, 48f));
CanvasGroup canvasGroup = panel.gameObject.AddComponent<CanvasGroup>();
Text messageText = CreateText(panel.transform, "Message", font, "", 18, UITheme.Text, TextAnchor.MiddleCenter);
Stretch(messageText.rectTransform, Vector2.zero, Vector2.one, new Vector2(14f, 6f), new Vector2(-14f, -6f));
NotificationUI notification = panel.gameObject.AddComponent<NotificationUI>();
notification.messageText = messageText;
notification.canvasGroup = canvasGroup;
return notification;
}
private static void CreateLevelHud(Transform parent, Font regularFont, Font boldFont, LevelSystem levelSystem)
{
Image panel = CreateUIImage(parent, "Level HUD", UITheme.PanelSoft);
SetRect(panel.rectTransform, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(16f, -16f), new Vector2(300f, 98f));
Text levelText = CreateText(panel.transform, "Level Text", boldFont, "Уровень 1", 20, UITheme.Gold, TextAnchor.UpperLeft);
SetRect(levelText.rectTransform, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(0f, 1f), new Vector2(16f, -12f), new Vector2(-32f, 28f));
Image expBackground = CreateUIImage(panel.transform, "Exp Bar Background", new Color(0.03f, 0.035f, 0.04f, 0.95f));
SetRect(expBackground.rectTransform, new Vector2(0f, 0f), new Vector2(1f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 18f), new Vector2(-32f, 18f));
Image expFill = CreateUIImage(expBackground.transform, "Exp Bar Fill", new Color(0.20f, 0.78f, 0.96f, 1f));
Stretch(expFill.rectTransform, Vector2.zero, Vector2.one, Vector2.zero, Vector2.zero);
expFill.type = Image.Type.Filled;
expFill.fillMethod = Image.FillMethod.Horizontal;
expFill.fillOrigin = 0;
Text expText = CreateText(panel.transform, "Exp Text", regularFont, "0 / 100 XP", 14, UITheme.Text, TextAnchor.MiddleCenter);
SetRect(expText.rectTransform, new Vector2(0f, 0f), new Vector2(1f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 17f), new Vector2(-32f, 20f));
levelSystem.levelText = levelText;
levelSystem.expText = expText;
levelSystem.expFillImage = expFill;
}
private static StatsPanelUI CreateStatsHud(Transform parent, Font regularFont, Font boldFont, PlayerStats playerStats)
{
return CreateStatsPanel(parent, "Stats HUD", new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-16f, -16f), new Vector2(300f, 230f), regularFont, boldFont, playerStats);
}
private static TalentTreeUI CreateTalentTree(Transform parent, Font regularFont, Font boldFont, TalentNodeUI nodePrefab, TalentManager manager, PlayerStats playerStats)
{
TalentTreeUI treeUI = parent.gameObject.AddComponent<TalentTreeUI>();
treeUI.manager = manager;
treeUI.playerStats = playerStats;
treeUI.nodePrefab = nodePrefab;
treeUI.openOnStart = false;
Image overlay = CreateUIImage(parent, "Talent Tree Panel", UITheme.Panel);
Stretch(overlay.rectTransform, Vector2.zero, Vector2.one, Vector2.zero, Vector2.zero);
treeUI.panelRoot = overlay.gameObject;
Text title = CreateText(overlay.transform, "Title", boldFont, "Дерево талантов", 30, UITheme.Gold, TextAnchor.MiddleLeft);
SetRect(title.rectTransform, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(28f, -18f), new Vector2(420f, 46f));
treeUI.titleText = title;
Text points = CreateText(overlay.transform, "Talent Points", boldFont, "Очки талантов: 1", 22, UITheme.Text, TextAnchor.MiddleLeft);
SetRect(points.rectTransform, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(470f, -22f), new Vector2(300f, 40f));
treeUI.pointsText = points;
Button saveButton = CreateButton(overlay.transform, "Save Button", regularFont, "Save", new Color(0.18f, 0.21f, 0.25f, 1f));
SetRect(saveButton.GetComponent<RectTransform>(), new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-292f, -20f), new Vector2(82f, 36f));
treeUI.saveButton = saveButton;
Button loadButton = CreateButton(overlay.transform, "Load Button", regularFont, "Load", new Color(0.18f, 0.21f, 0.25f, 1f));
SetRect(loadButton.GetComponent<RectTransform>(), new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-198f, -20f), new Vector2(82f, 36f));
treeUI.loadButton = loadButton;
Button resetButton = CreateButton(overlay.transform, "Reset Button", regularFont, "Reset", new Color(0.30f, 0.17f, 0.17f, 1f));
SetRect(resetButton.GetComponent<RectTransform>(), new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-104f, -20f), new Vector2(82f, 36f));
treeUI.resetButton = resetButton;
Image treePanel = CreateUIImage(overlay.transform, "Talent Tree Area", UITheme.PanelSoft);
Stretch(treePanel.rectTransform, Vector2.zero, Vector2.one, new Vector2(24f, 78f), new Vector2(-360f, -78f));
RectTransform lineRoot = CreateUIRoot(treePanel.transform, "Lines");
Stretch(lineRoot, Vector2.zero, Vector2.one, Vector2.zero, Vector2.zero);
treeUI.lineRoot = lineRoot;
RectTransform nodesRoot = CreateUIRoot(treePanel.transform, "Nodes");
Stretch(nodesRoot, Vector2.zero, Vector2.one, Vector2.zero, Vector2.zero);
treeUI.nodesRoot = nodesRoot;
StatsPanelUI treeStats = CreateStatsPanel(overlay.transform, "Tree Stats Panel", new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-24f, -90f), new Vector2(320f, 260f), regularFont, boldFont, playerStats);
treeUI.statsPanel = treeStats;
Text controls = CreateText(overlay.transform, "Controls", regularFont, "", 17, UITheme.MutedText, TextAnchor.MiddleLeft);
SetRect(controls.rectTransform, new Vector2(0f, 0f), new Vector2(1f, 0f), new Vector2(0f, 0f), new Vector2(28f, 20f), new Vector2(-56f, 34f));
treeUI.controlsText = controls;
TalentTooltipUI tooltip = CreateTooltip(overlay.transform, regularFont, boldFont);
treeUI.tooltip = tooltip;
return treeUI;
}
private static TalentTooltipUI CreateTooltip(Transform parent, Font regularFont, Font boldFont)
{
Image panel = CreateUIImage(parent, "Talent Tooltip", new Color(0.05f, 0.055f, 0.07f, 0.98f));
SetRect(panel.rectTransform, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, -120f), new Vector2(330f, 260f));
CanvasGroup canvasGroup = panel.gameObject.AddComponent<CanvasGroup>();
TalentTooltipUI tooltip = panel.gameObject.AddComponent<TalentTooltipUI>();
tooltip.panelRoot = panel.rectTransform;
tooltip.canvasGroup = canvasGroup;
tooltip.titleText = CreateText(panel.transform, "Tooltip Title", boldFont, "", 20, UITheme.Gold, TextAnchor.UpperLeft);
SetRect(tooltip.titleText.rectTransform, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(0f, 1f), new Vector2(14f, -12f), new Vector2(-28f, 30f));
tooltip.descriptionText = CreateText(panel.transform, "Tooltip Description", regularFont, "", 14, UITheme.Text, TextAnchor.UpperLeft);
tooltip.descriptionText.horizontalOverflow = HorizontalWrapMode.Wrap;
SetRect(tooltip.descriptionText.rectTransform, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(0f, 1f), new Vector2(14f, -46f), new Vector2(-28f, 46f));
tooltip.costText = CreateText(panel.transform, "Tooltip Cost", regularFont, "", 14, UITheme.Text, TextAnchor.UpperLeft);
SetRect(tooltip.costText.rectTransform, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(0f, 1f), new Vector2(14f, -92f), new Vector2(-28f, 22f));
tooltip.modifiersText = CreateText(panel.transform, "Tooltip Modifiers", regularFont, "", 14, UITheme.Text, TextAnchor.UpperLeft);
tooltip.modifiersText.horizontalOverflow = HorizontalWrapMode.Wrap;
tooltip.modifiersText.verticalOverflow = VerticalWrapMode.Overflow;
SetRect(tooltip.modifiersText.rectTransform, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(0f, 1f), new Vector2(14f, -118f), new Vector2(-28f, 70f));
tooltip.requirementsText = CreateText(panel.transform, "Tooltip Requirements", regularFont, "", 14, UITheme.MutedText, TextAnchor.UpperLeft);
tooltip.requirementsText.horizontalOverflow = HorizontalWrapMode.Wrap;
SetRect(tooltip.requirementsText.rectTransform, new Vector2(0f, 0f), new Vector2(1f, 0f), new Vector2(0f, 0f), new Vector2(14f, 54f), new Vector2(-28f, 32f));
tooltip.statusText = CreateText(panel.transform, "Tooltip Status", boldFont, "", 14, UITheme.Gold, TextAnchor.UpperLeft);
SetRect(tooltip.statusText.rectTransform, new Vector2(0f, 0f), new Vector2(1f, 0f), new Vector2(0f, 0f), new Vector2(14f, 30f), new Vector2(-28f, 24f));
tooltip.reasonText = CreateText(panel.transform, "Tooltip Reason", regularFont, "", 13, UITheme.MutedText, TextAnchor.UpperLeft);
tooltip.reasonText.horizontalOverflow = HorizontalWrapMode.Wrap;
SetRect(tooltip.reasonText.rectTransform, new Vector2(0f, 0f), new Vector2(1f, 0f), new Vector2(0f, 0f), new Vector2(14f, 8f), new Vector2(-28f, 26f));
return tooltip;
}
private static StatsPanelUI CreateStatsPanel(Transform parent, string name, Vector2 anchorMin, Vector2 anchorMax, Vector2 pivot, Vector2 position, Vector2 size, Font regularFont, Font boldFont, PlayerStats playerStats)
{
Image panel = CreateUIImage(parent, name, UITheme.PanelSoft);
SetRect(panel.rectTransform, anchorMin, anchorMax, pivot, position, size);
StatsPanelUI statsPanel = panel.gameObject.AddComponent<StatsPanelUI>();
statsPanel.playerStats = playerStats;
Text title = CreateText(panel.transform, "Title", boldFont, "Характеристики", 18, UITheme.Gold, TextAnchor.UpperLeft);
SetRect(title.rectTransform, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(0f, 1f), new Vector2(16f, -14f), new Vector2(-32f, 28f));
statsPanel.titleText = title;
statsPanel.maxHealthText = CreateStatLine(panel.transform, regularFont, "Max Health: 100", 50f);
statsPanel.maxManaText = CreateStatLine(panel.transform, regularFont, "Max Mana: 100", 78f);
statsPanel.damageText = CreateStatLine(panel.transform, regularFont, "Damage: 10", 106f);
statsPanel.moveSpeedText = CreateStatLine(panel.transform, regularFont, "Move Speed: 5", 134f);
statsPanel.cooldownReductionText = CreateStatLine(panel.transform, regularFont, "Cooldown Reduction: 0%", 162f);
statsPanel.criticalChanceText = CreateStatLine(panel.transform, regularFont, "Critical Chance: 0%", 190f);
statsPanel.Refresh();
return statsPanel;
}
private static Text CreateStatLine(Transform parent, Font font, string text, float topOffset)
{
Text line = CreateText(parent, text.Replace(":", string.Empty), font, text, 15, UITheme.Text, TextAnchor.UpperLeft);
line.horizontalOverflow = HorizontalWrapMode.Wrap;
SetRect(line.rectTransform, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(0f, 1f), new Vector2(16f, -topOffset), new Vector2(-32f, 24f));
return line;
}
private static Image CreateUIImage(Transform parent, string name, Color color)
{
GameObject gameObject = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
gameObject.transform.SetParent(parent, false);
Image image = gameObject.GetComponent<Image>();
image.color = color;
return image;
}
private static RectTransform CreateUIRoot(Transform parent, string name)
{
GameObject gameObject = new GameObject(name, typeof(RectTransform));
gameObject.transform.SetParent(parent, false);
return gameObject.GetComponent<RectTransform>();
}
private static Text CreateText(Transform parent, string name, Font font, string text, int size, Color color, TextAnchor alignment)
{
GameObject gameObject = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(Text));
gameObject.transform.SetParent(parent, false);
Text label = gameObject.GetComponent<Text>();
label.font = font != null ? font : Resources.GetBuiltinResource<Font>("Arial.ttf");
label.text = text;
label.fontSize = size;
label.color = color;
label.alignment = alignment;
label.raycastTarget = false;
label.horizontalOverflow = HorizontalWrapMode.Overflow;
label.verticalOverflow = VerticalWrapMode.Truncate;
return label;
}
private static Button CreateButton(Transform parent, string name, Font font, string text, Color backgroundColor)
{
Image background = CreateUIImage(parent, name, backgroundColor);
Button button = background.gameObject.AddComponent<Button>();
button.targetGraphic = background;
Text label = CreateText(background.transform, "Label", font, text, 16, UITheme.Text, TextAnchor.MiddleCenter);
Stretch(label.rectTransform, Vector2.zero, Vector2.one, Vector2.zero, Vector2.zero);
ColorBlock colors = button.colors;
colors.normalColor = Color.white;
colors.highlightedColor = new Color(1f, 0.92f, 0.72f, 1f);
colors.pressedColor = new Color(0.82f, 0.70f, 0.46f, 1f);
colors.disabledColor = new Color(0.46f, 0.46f, 0.46f, 1f);
button.colors = colors;
return button;
}
private static void SetRect(RectTransform rectTransform, Vector2 anchorMin, Vector2 anchorMax, Vector2 pivot, Vector2 anchoredPosition, Vector2 sizeDelta)
{
rectTransform.anchorMin = anchorMin;
rectTransform.anchorMax = anchorMax;
rectTransform.pivot = pivot;
rectTransform.anchoredPosition = anchoredPosition;
rectTransform.sizeDelta = sizeDelta;
}
private static void Stretch(RectTransform rectTransform, Vector2 anchorMin, Vector2 anchorMax, Vector2 offsetMin, Vector2 offsetMax)
{
rectTransform.anchorMin = anchorMin;
rectTransform.anchorMax = anchorMax;
rectTransform.pivot = new Vector2(0.5f, 0.5f);
rectTransform.offsetMin = offsetMin;
rectTransform.offsetMax = offsetMax;
}
private static string ToSystemPath(string assetPath)
{
return Path.Combine(Directory.GetCurrentDirectory(), assetPath);
}
private static int ValidateTalentPrerequisites(TalentManager talentManager)
{
int errors = 0;
Dictionary<string, TalentData> byId = new Dictionary<string, TalentData>();
for (int i = 0; i < talentManager.allTalents.Count; i++)
{
TalentData talent = talentManager.allTalents[i];
if (talent == null)
{
errors += RequireCondition(false, "TalentManager contains a null talent reference.");
continue;
}
if (byId.ContainsKey(talent.talentId))
{
errors += RequireCondition(false, $"Duplicate talent id: {talent.talentId}");
continue;
}
byId.Add(talent.talentId, talent);
}
string[] requiredIds =
{
"Toughness",
"Strength",
"Agility",
"AdvancedToughness",
"BattleTraining",
"SwiftStep",
"ManaFlow",
"ArcaneFocus",
"CriticalEye",
"MasterWarrior"
};
for (int i = 0; i < requiredIds.Length; i++)
{
errors += RequireCondition(byId.ContainsKey(requiredIds[i]), $"Missing talent id: {requiredIds[i]}");
}
errors += RequirePrerequisites(byId, "AdvancedToughness", "Toughness");
errors += RequirePrerequisites(byId, "BattleTraining", "Strength");
errors += RequirePrerequisites(byId, "SwiftStep", "Agility");
errors += RequirePrerequisites(byId, "ArcaneFocus", "ManaFlow");
errors += RequirePrerequisites(byId, "CriticalEye", "Strength");
errors += RequirePrerequisites(byId, "MasterWarrior", "BattleTraining", "AdvancedToughness");
return errors;
}
private static int RequirePrerequisites(Dictionary<string, TalentData> byId, string talentId, params string[] prerequisiteIds)
{
if (!byId.ContainsKey(talentId))
{
return 1;
}
TalentData talent = byId[talentId];
for (int i = 0; i < prerequisiteIds.Length; i++)
{
if (!byId.ContainsKey(prerequisiteIds[i]) || talent.prerequisites == null || Array.IndexOf(talent.prerequisites, byId[prerequisiteIds[i]]) < 0)
{
Debug.LogError($"{talentId} is missing prerequisite {prerequisiteIds[i]}.");
return 1;
}
}
return 0;
}
private static int ValidateMissingScripts(Scene scene)
{
int errors = 0;
GameObject[] roots = scene.GetRootGameObjects();
for (int i = 0; i < roots.Length; i++)
{
Transform[] transforms = roots[i].GetComponentsInChildren<Transform>(true);
for (int transformIndex = 0; transformIndex < transforms.Length; transformIndex++)
{
GameObject gameObject = transforms[transformIndex].gameObject;
int missingCount = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(gameObject);
if (missingCount > 0)
{
Debug.LogError($"{gameObject.name} has missing scripts: {missingCount}");
errors += missingCount;
}
}
}
return errors;
}
private static int ValidateUbuntuFonts(Scene scene)
{
Font regular = AssetDatabase.LoadAssetAtPath<Font>("Assets/_Fonts/Ubuntu-Regular.ttf");
Font bold = AssetDatabase.LoadAssetAtPath<Font>("Assets/_Fonts/Ubuntu-Bold.ttf");
int errors = 0;
Text[] texts = UnityEngine.Object.FindObjectsOfType<Text>(true);
for (int i = 0; i < texts.Length; i++)
{
Text text = texts[i];
if (text.gameObject.scene != scene)
{
continue;
}
if (text.font != regular && text.font != bold)
{
Debug.LogError($"Text object does not use local Ubuntu font: {text.name}");
errors++;
}
}
return errors;
}
private static int RequireObject(UnityEngine.Object value, string label)
{
if (value != null)
{
return 0;
}
Debug.LogError($"Missing required reference: {label}");
return 1;
}
private static int RequireCondition(bool condition, string message)
{
if (condition)
{
return 0;
}
Debug.LogError(message);
return 1;
}
private sealed class TalentSpec
{
public readonly string id;
public readonly string displayName;
public readonly string description;
public readonly int cost;
public readonly Vector2 treePosition;
public readonly Color branchColor;
public readonly StatModifier[] modifiers;
public readonly string[] prerequisiteIds;
public TalentSpec(string id, string displayName, string description, int cost, Vector2 treePosition, Color branchColor, StatModifier modifier, string[] prerequisiteIds)
: this(id, displayName, description, cost, treePosition, branchColor, new[] { modifier }, prerequisiteIds)
{
}
public TalentSpec(string id, string displayName, string description, int cost, Vector2 treePosition, Color branchColor, StatModifier[] modifiers, string[] prerequisiteIds)
{
this.id = id;
this.displayName = displayName;
this.description = description;
this.cost = cost;
this.treePosition = treePosition;
this.branchColor = branchColor;
this.modifiers = modifiers;
this.prerequisiteIds = prerequisiteIds;
}
}
}