using System.Collections.Generic; using System.IO; using OTG.Lab06.Abilities; using OTG.Lab06.Characters; using OTG.Lab06.Combat; using OTG.Lab06.Effects; using OTG.Lab06.UI; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.SceneManagement; using UnityEngine.UI; public static class Lab6SceneBuilder { private const string Root = "Assets"; private const string ScenePath = "Assets/_Scenes/Lab06_AbilitySystem.unity"; private const string FontFilePath = "Assets/_Fonts/Ubuntu-R.ttf"; [MenuItem("Tools/Technical Game Design/Lab6/Create Ability System Scene")] public static void CreateAbilitySystemScene() { CreateProjectStructure(); Font font = CopyUbuntuFont(); Dictionary materials = CreateMaterials(); Dictionary icons = CreateIcons(); GameObject deathEffect = CreateDeathEffectPrefab(materials["ArcaneViolet"]); FloatingDamage floatingDamage = CreateFloatingDamagePrefab(font); GameObject fireballEffect = CreateFireballPrefab(materials["Fire"], materials["FireTrail"]); GameObject iceNovaEffect = CreateIceNovaPrefab(materials["Ice"]); GameObject blinkEffect = CreateBlinkPrefab(materials["Blink"]); AbilityData fireball = CreateAbility("Fireball", "fireball", "Launches a burning projectile that detonates on the first enemy hit.", 8f, 2f, 30f, 24f, icons["Fireball"], fireballEffect); AbilityData iceNova = CreateAbility("Ice Nova", "ice_nova", "Releases a sharp frost ring that damages all nearby enemies.", 14f, 5f, 20f, 5.5f, icons["IceNova"], iceNovaEffect); AbilityData blink = CreateAbility("Blink", "blink", "Teleports forward and stops safely before obstacles.", 10f, 6f, 0f, 8f, icons["Blink"], blinkEffect); GameObject enemyPrefab = CreateEnemyPrefab(materials["Enemy"], deathEffect); GameObject crystalPrefab = CreateManaCrystalPrefab(materials["ManaCrystal"]); AbilitySlotUI slotPrefab = CreateAbilitySlotPrefab(font, materials); Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); scene.name = "Lab06_AbilitySystem"; BuildLighting(); BuildArena(materials); GameObject player = BuildPlayer(materials, fireball, iceNova, blink); BuildCamera(player.transform); BuildEnemies(enemyPrefab); BuildTrainingMannequins(materials); BuildManaCrystals(crystalPrefab); BuildFloatingDamageManager(floatingDamage); BuildUI(player, slotPrefab, font, materials); EditorSceneManager.SaveScene(scene, ScenePath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); EditorUtility.DisplayDialog("Lab06", "Ability System scene has been generated.", "OK"); } private static void CreateProjectStructure() { string[] folders = { "_Scenes", "_Scripts", "_Scripts/Core", "_Scripts/Abilities", "_Scripts/Combat", "_Scripts/Characters", "_Scripts/UI", "_Scripts/Effects", "_Data", "_Data/Abilities", "_Prefabs", "_Prefabs/Abilities", "_Prefabs/UI", "_Prefabs/Enemies", "_Materials", "_Fonts", "Editor" }; foreach (string folder in folders) { string path = $"{Root}/{folder}"; if (!AssetDatabase.IsValidFolder(path)) { string parent = Path.GetDirectoryName(path).Replace('\\', '/'); string name = Path.GetFileName(path); AssetDatabase.CreateFolder(parent, name); } } } private static Font CopyUbuntuFont() { string[] candidates = { "/usr/share/fonts/truetype/ubuntu/Ubuntu-R.ttf", "/usr/share/fonts/truetype/ubuntu/Ubuntu-M.ttf", "/usr/share/fonts/TTF/Ubuntu-R.ttf" }; string source = null; foreach (string candidate in candidates) { if (File.Exists(candidate)) { source = candidate; break; } } if (source == null) { throw new FileNotFoundException("Ubuntu font was not found in standard Linux font directories."); } File.Copy(source, FontFilePath, true); AssetDatabase.ImportAsset(FontFilePath, ImportAssetOptions.ForceUpdate); AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); Font font = AssetDatabase.LoadAssetAtPath(FontFilePath); if (font == null) { throw new FileNotFoundException("Ubuntu font was copied but Unity could not import it as a Font asset."); } return font; } private static Dictionary CreateMaterials() { var result = new Dictionary { ["ArenaFloor"] = CreateMaterial("ArenaFloor", new Color(0.48f, 0.49f, 0.46f), 0.08f), ["ArenaTrim"] = CreateMaterial("ArenaTrim", new Color(0.34f, 0.36f, 0.36f), 0.06f), ["ArenaWall"] = CreateMaterial("ArenaWall", new Color(0.42f, 0.43f, 0.41f), 0.05f), ["ArenaLine"] = CreateMaterial("ArenaLine", new Color(0.58f, 0.60f, 0.57f), 0.03f), ["Player"] = CreateMaterial("Player", new Color(0.12f, 0.39f, 0.82f), 0.25f), ["Enemy"] = CreateMaterial("Enemy", new Color(0.58f, 0.08f, 0.12f), 0.16f), ["Mannequin"] = CreateMaterial("Mannequin", new Color(0.56f, 0.43f, 0.28f), 0.05f), ["Fire"] = CreateMaterial("Fire", new Color(1f, 0.32f, 0.08f), 0.4f, new Color(2.2f, 0.55f, 0.08f)), ["FireTrail"] = CreateMaterial("FireTrail", new Color(1f, 0.68f, 0.12f, 0.55f), 0.15f, new Color(1.3f, 0.55f, 0.12f)), ["Ice"] = CreateMaterial("Ice", new Color(0.42f, 0.85f, 1f, 0.62f), 0.25f, new Color(0.2f, 0.9f, 1.7f)), ["Blink"] = CreateMaterial("Blink", new Color(0.52f, 0.48f, 1f, 0.66f), 0.3f, new Color(0.75f, 0.55f, 2.1f)), ["ManaCrystal"] = CreateMaterial("ManaCrystal", new Color(0.18f, 0.9f, 0.95f), 0.45f, new Color(0.05f, 1.4f, 1.8f)), ["ArcaneViolet"] = CreateMaterial("ArcaneViolet", new Color(0.58f, 0.23f, 0.86f), 0.35f, new Color(0.8f, 0.2f, 1.4f)), ["UIBack"] = CreateMaterial("UIBack", new Color(0.07f, 0.08f, 0.09f, 0.88f), 0f), }; return result; } private static Material CreateMaterial(string name, Color color, float smoothness, Color? emission = null) { string path = $"Assets/_Materials/{name}.mat"; Material material = AssetDatabase.LoadAssetAtPath(path); if (material == null) { material = new Material(Shader.Find("Standard")); AssetDatabase.CreateAsset(material, path); } material.color = color; material.SetFloat("_Glossiness", smoothness); if (emission.HasValue) { material.EnableKeyword("_EMISSION"); material.SetColor("_EmissionColor", emission.Value); } else { material.DisableKeyword("_EMISSION"); } EditorUtility.SetDirty(material); return material; } private static Dictionary CreateIcons() { return new Dictionary { ["Fireball"] = CreateIcon("FireballIcon", new Color(0.95f, 0.22f, 0.07f), new Color(1f, 0.75f, 0.15f)), ["IceNova"] = CreateIcon("IceNovaIcon", new Color(0.1f, 0.55f, 0.95f), new Color(0.78f, 0.96f, 1f)), ["Blink"] = CreateIcon("BlinkIcon", new Color(0.35f, 0.2f, 0.85f), new Color(0.85f, 0.78f, 1f)) }; } private static Sprite CreateIcon(string name, Color baseColor, Color markColor) { string pngPath = $"Assets/_Data/Abilities/{name}.png"; Texture2D texture = new Texture2D(64, 64, TextureFormat.RGBA32, false); for (int y = 0; y < 64; y++) { for (int x = 0; x < 64; x++) { Vector2 p = new Vector2(x - 31.5f, y - 31.5f); float ring = Mathf.InverseLerp(32f, 12f, p.magnitude); Color color = Color.Lerp(new Color(0.04f, 0.05f, 0.06f, 1f), baseColor, Mathf.Clamp01(ring)); if (Mathf.Abs(p.x) < 5f || Mathf.Abs(p.y) < 5f || p.magnitude < 12f) { color = Color.Lerp(color, markColor, 0.75f); } texture.SetPixel(x, y, color); } } texture.Apply(); File.WriteAllBytes(pngPath, texture.EncodeToPNG()); Object.DestroyImmediate(texture); AssetDatabase.ImportAsset(pngPath, ImportAssetOptions.ForceUpdate); TextureImporter importer = (TextureImporter)AssetImporter.GetAtPath(pngPath); importer.textureType = TextureImporterType.Sprite; importer.spritePixelsPerUnit = 64f; importer.mipmapEnabled = false; importer.SaveAndReimport(); return AssetDatabase.LoadAssetAtPath(pngPath); } private static AbilityData CreateAbility(string displayName, string id, string description, float mana, float cooldown, float damage, float range, Sprite icon, GameObject effectPrefab) { string path = $"Assets/_Data/Abilities/{displayName.Replace(" ", string.Empty)}.asset"; AbilityData data = AssetDatabase.LoadAssetAtPath(path); if (data == null) { data = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(data, path); } data.abilityId = id; data.abilityName = displayName; data.description = description; data.icon = icon; data.manaCost = mana; data.cooldown = cooldown; data.damage = damage; data.castRange = range; data.effectPrefab = effectPrefab; EditorUtility.SetDirty(data); return data; } private static GameObject CreateFireballPrefab(Material fire, Material trail) { GameObject root = new GameObject("PF_FireballEffect"); var collider = root.AddComponent(); collider.radius = 0.35f; collider.isTrigger = true; root.AddComponent(); GameObject visual = GameObject.CreatePrimitive(PrimitiveType.Sphere); visual.name = "Fireball_Visual"; visual.transform.SetParent(root.transform, false); visual.transform.localScale = Vector3.one * 0.55f; Object.DestroyImmediate(visual.GetComponent()); visual.GetComponent().sharedMaterial = fire; ParticleSystem particles = root.AddComponent(); ParticleSystem.MainModule main = particles.main; main.startLifetime = 0.35f; main.startSpeed = 0.2f; main.startSize = 0.32f; main.startColor = new Color(1f, 0.42f, 0.05f, 0.7f); ParticleSystem.EmissionModule emission = particles.emission; emission.rateOverTime = 34f; ParticleSystemRenderer renderer = particles.GetComponent(); renderer.sharedMaterial = trail; return SavePrefab(root, "Assets/_Prefabs/Abilities/PF_FireballEffect.prefab"); } private static GameObject CreateIceNovaPrefab(Material material) { GameObject root = new GameObject("PF_IceNovaEffect"); root.AddComponent(); GameObject ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder); ring.name = "IceNova_Ring"; ring.transform.SetParent(root.transform, false); ring.transform.localScale = new Vector3(5.5f, 0.035f, 5.5f); Object.DestroyImmediate(ring.GetComponent()); ring.GetComponent().sharedMaterial = material; ParticleSystem particles = root.AddComponent(); ParticleSystem.MainModule main = particles.main; main.startLifetime = 0.7f; main.startSpeed = 2.1f; main.startSize = 0.18f; main.startColor = new Color(0.55f, 0.9f, 1f, 0.75f); ParticleSystem.ShapeModule shape = particles.shape; shape.shapeType = ParticleSystemShapeType.Circle; shape.radius = 5.4f; ParticleSystem.EmissionModule emission = particles.emission; emission.rateOverTime = 90f; return SavePrefab(root, "Assets/_Prefabs/Abilities/PF_IceNovaEffect.prefab"); } private static GameObject CreateBlinkPrefab(Material material) { GameObject root = new GameObject("PF_BlinkEffect"); root.AddComponent(); GameObject burst = GameObject.CreatePrimitive(PrimitiveType.Cylinder); burst.name = "Blink_Burst"; burst.transform.SetParent(root.transform, false); burst.transform.localScale = new Vector3(1.25f, 0.05f, 1.25f); Object.DestroyImmediate(burst.GetComponent()); burst.GetComponent().sharedMaterial = material; ParticleSystem particles = root.AddComponent(); ParticleSystem.MainModule main = particles.main; main.startLifetime = 0.45f; main.startSpeed = 1.4f; main.startSize = 0.22f; main.startColor = new Color(0.65f, 0.55f, 1f, 0.8f); ParticleSystem.EmissionModule emission = particles.emission; emission.rateOverTime = 70f; return SavePrefab(root, "Assets/_Prefabs/Abilities/PF_BlinkEffect.prefab"); } private static GameObject CreateDeathEffectPrefab(Material material) { GameObject root = new GameObject("PF_EnemyDeathEffect"); ParticleSystem particles = root.AddComponent(); ParticleSystem.MainModule main = particles.main; main.startLifetime = 0.65f; main.startSpeed = 1.8f; main.startSize = 0.22f; main.duration = 0.5f; main.loop = false; main.startColor = new Color(0.65f, 0.2f, 1f, 0.85f); ParticleSystem.EmissionModule emission = particles.emission; emission.rateOverTime = 0f; emission.SetBursts(new[] { new ParticleSystem.Burst(0f, 34) }); particles.GetComponent().sharedMaterial = material; var destroy = root.AddComponent(); SetFloat(destroy, "lifetime", 1.2f); return SavePrefab(root, "Assets/_Prefabs/Abilities/PF_EnemyDeathEffect.prefab"); } private static FloatingDamage CreateFloatingDamagePrefab(Font font) { GameObject root = new GameObject("PF_FloatingDamage"); Canvas canvas = root.AddComponent(); canvas.renderMode = RenderMode.WorldSpace; RectTransform canvasRect = root.GetComponent(); canvasRect.sizeDelta = new Vector2(120f, 48f); canvasRect.localScale = Vector3.one * 0.01f; GameObject labelObject = new GameObject("DamageLabel", typeof(RectTransform)); labelObject.transform.SetParent(root.transform, false); Text label = labelObject.AddComponent(); label.font = font; label.fontSize = 38; label.alignment = TextAnchor.MiddleCenter; label.color = new Color(1f, 0.86f, 0.28f, 1f); Stretch(label.rectTransform); FloatingDamage damage = root.AddComponent(); SetObject(damage, "label", label); GameObject prefab = SavePrefab(root, "Assets/_Prefabs/UI/PF_FloatingDamage.prefab"); return prefab.GetComponent(); } private static GameObject CreateEnemyPrefab(Material enemyMaterial, GameObject deathEffect) { GameObject root = GameObject.CreatePrimitive(PrimitiveType.Capsule); root.name = "PF_Enemy"; root.transform.localScale = new Vector3(1f, 1.35f, 1f); root.GetComponent().sharedMaterial = enemyMaterial; CapsuleCollider hitbox = root.GetComponent(); hitbox.radius = 0.85f; hitbox.height = 2.4f; Enemy enemy = root.AddComponent(); SetObject(enemy, "deathEffectPrefab", deathEffect); return SavePrefab(root, "Assets/_Prefabs/Enemies/PF_Enemy.prefab"); } private static GameObject CreateManaCrystalPrefab(Material material) { GameObject root = new GameObject("PF_ManaCrystal"); SphereCollider trigger = root.AddComponent(); trigger.isTrigger = true; trigger.radius = 1.2f; GameObject visual = GameObject.CreatePrimitive(PrimitiveType.Sphere); visual.name = "CrystalVisual"; visual.transform.SetParent(root.transform, false); visual.transform.localPosition = Vector3.up * 0.8f; visual.transform.localScale = new Vector3(0.65f, 1.25f, 0.65f); Object.DestroyImmediate(visual.GetComponent()); visual.GetComponent().sharedMaterial = material; ManaCrystal crystal = root.AddComponent(); SetObject(crystal, "visualRoot", visual); return SavePrefab(root, "Assets/_Prefabs/Abilities/PF_ManaCrystal.prefab"); } private static AbilitySlotUI CreateAbilitySlotPrefab(Font font, Dictionary materials) { GameObject root = CreateUIObject("PF_AbilitySlot", null, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(76f, 76f), Vector2.zero); Image back = root.AddComponent(); back.color = new Color(0.045f, 0.05f, 0.055f, 0.9f); GameObject iconObject = CreateUIObject("Icon", root.transform, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), new Vector2(-14f, -22f), new Vector2(0f, 8f)); Image icon = iconObject.AddComponent(); icon.preserveAspect = true; icon.raycastTarget = false; GameObject overlayObject = CreateUIObject("CooldownOverlay", root.transform, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), new Vector2(-14f, -22f), new Vector2(0f, 8f)); Image overlay = overlayObject.AddComponent(); overlay.color = new Color(0f, 0f, 0f, 0.68f); overlay.type = Image.Type.Filled; overlay.fillMethod = Image.FillMethod.Radial360; overlay.fillOrigin = 2; overlay.raycastTarget = false; Text key = CreateText("Hotkey", root.transform, font, "1", 18, TextAnchor.MiddleCenter, new Color(0.95f, 0.95f, 0.95f, 1f)); Anchor(key.rectTransform, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(22f, 22f), new Vector2(9f, -8f)); Text name = CreateText("Name", root.transform, font, "Fireball", 11, TextAnchor.MiddleCenter, new Color(0.88f, 0.91f, 0.94f, 1f)); Anchor(name.rectTransform, new Vector2(0f, 0f), new Vector2(1f, 0f), new Vector2(0.5f, 0f), new Vector2(-8f, 18f), new Vector2(0f, 10f)); Text mana = CreateText("Mana", root.transform, font, "15", 13, TextAnchor.MiddleRight, new Color(0.43f, 0.9f, 1f, 1f)); Anchor(mana.rectTransform, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(34f, 20f), new Vector2(-7f, -8f)); Text cooldown = CreateText("Cooldown", root.transform, font, "", 20, TextAnchor.MiddleCenter, Color.white); Stretch(cooldown.rectTransform); AbilitySlotUI slot = root.AddComponent(); SetObject(slot, "icon", icon); SetObject(slot, "cooldownOverlay", overlay); SetObject(slot, "hotkeyLabel", key); SetObject(slot, "nameLabel", name); SetObject(slot, "manaLabel", mana); SetObject(slot, "cooldownLabel", cooldown); GameObject prefab = SavePrefab(root, "Assets/_Prefabs/UI/PF_AbilitySlot.prefab"); return prefab.GetComponent(); } private static void BuildLighting() { RenderSettings.ambientLight = new Color(0.38f, 0.39f, 0.40f); RenderSettings.fog = true; RenderSettings.fogColor = new Color(0.58f, 0.62f, 0.65f); RenderSettings.fogMode = FogMode.Linear; RenderSettings.fogStartDistance = 35f; RenderSettings.fogEndDistance = 80f; GameObject lightObject = new GameObject("Directional Light"); Light light = lightObject.AddComponent(); light.type = LightType.Directional; light.intensity = 1.05f; lightObject.transform.rotation = Quaternion.Euler(55f, -38f, 0f); GameObject rimObject = new GameObject("Arena Arcane Fill Light"); Light rim = rimObject.AddComponent(); rim.type = LightType.Point; rim.intensity = 3.2f; rim.range = 28f; rim.color = new Color(0.36f, 0.7f, 1f); rimObject.transform.position = new Vector3(0f, 8f, -8f); } private static void BuildArena(Dictionary materials) { const float arenaSize = 52f; const float half = arenaSize * 0.5f; GameObject floor = GameObject.CreatePrimitive(PrimitiveType.Cube); floor.name = "Flat Stone Arena Floor"; floor.transform.position = new Vector3(0f, -0.08f, 0f); floor.transform.localScale = new Vector3(arenaSize, 0.16f, arenaSize); floor.GetComponent().sharedMaterial = materials["ArenaFloor"]; CreateWall("North Wall", new Vector3(0f, 1.15f, half), new Vector3(arenaSize + 1.2f, 2.3f, 0.8f), materials["ArenaWall"]); CreateWall("South Wall", new Vector3(0f, 1.15f, -half), new Vector3(arenaSize + 1.2f, 2.3f, 0.8f), materials["ArenaWall"]); CreateWall("East Wall", new Vector3(half, 1.15f, 0f), new Vector3(0.8f, 2.3f, arenaSize + 1.2f), materials["ArenaWall"]); CreateWall("West Wall", new Vector3(-half, 1.15f, 0f), new Vector3(0.8f, 2.3f, arenaSize + 1.2f), materials["ArenaWall"]); for (int i = -2; i <= 2; i++) { if (i == 0) { continue; } CreateFloorLine($"Floor Guide X {i}", new Vector3(i * 8f, 0.012f, 0f), new Vector3(0.05f, 0.02f, arenaSize - 5f), materials["ArenaLine"]); CreateFloorLine($"Floor Guide Z {i}", new Vector3(0f, 0.014f, i * 8f), new Vector3(arenaSize - 5f, 0.02f, 0.05f), materials["ArenaLine"]); } Vector3[] blockPositions = { new Vector3(-16f, 0.55f, -13f), new Vector3(16f, 0.55f, -13f), new Vector3(-16f, 0.55f, 13f), new Vector3(16f, 0.55f, 13f), new Vector3(-7f, 0.45f, 18f), new Vector3(7f, 0.45f, 18f) }; Vector3[] blockScales = { new Vector3(4.5f, 1.1f, 2f), new Vector3(4.5f, 1.1f, 2f), new Vector3(2f, 1.1f, 4.5f), new Vector3(2f, 1.1f, 4.5f), new Vector3(3.2f, 0.9f, 2f), new Vector3(3.2f, 0.9f, 2f) }; for (int i = 0; i < blockPositions.Length; i++) { CreateWall($"Arena Cover {i + 1:00}", blockPositions[i], blockScales[i], materials["ArenaTrim"]); } Vector3[] columnPositions = { new Vector3(-22f, 1.7f, -22f), new Vector3(22f, 1.7f, -22f), new Vector3(-22f, 1.7f, 22f), new Vector3(22f, 1.7f, 22f), new Vector3(-22f, 1.7f, 0f), new Vector3(22f, 1.7f, 0f) }; for (int i = 0; i < columnPositions.Length; i++) { GameObject pillar = GameObject.CreatePrimitive(PrimitiveType.Cylinder); pillar.name = $"Stone Column {i + 1:00}"; pillar.transform.position = columnPositions[i]; pillar.transform.localScale = new Vector3(0.75f, 1.7f, 0.75f); pillar.GetComponent().sharedMaterial = materials["ArenaWall"]; } } private static void CreateWall(string name, Vector3 position, Vector3 scale, Material material) { GameObject wall = GameObject.CreatePrimitive(PrimitiveType.Cube); wall.name = name; wall.transform.position = position; wall.transform.localScale = scale; wall.GetComponent().sharedMaterial = material; } private static void CreateFloorLine(string name, Vector3 position, Vector3 scale, Material material) { GameObject line = GameObject.CreatePrimitive(PrimitiveType.Cube); line.name = name; line.transform.position = position; line.transform.localScale = scale; line.GetComponent().sharedMaterial = material; Object.DestroyImmediate(line.GetComponent()); } private static GameObject BuildPlayer(Dictionary materials, params AbilityData[] abilities) { GameObject player = new GameObject("Player_Mage"); player.name = "Player_Mage"; player.transform.position = new Vector3(0f, 0f, -10f); GameObject visual = GameObject.CreatePrimitive(PrimitiveType.Capsule); visual.name = "Player_Visual"; visual.transform.SetParent(player.transform, false); visual.transform.localPosition = Vector3.up; visual.GetComponent().sharedMaterial = materials["Player"]; Object.DestroyImmediate(visual.GetComponent()); CharacterController controller = player.AddComponent(); controller.height = 2f; controller.radius = 0.42f; controller.center = Vector3.up; PlayerStats stats = player.AddComponent(); player.AddComponent(); Transform castOrigin = new GameObject("CastOrigin").transform; castOrigin.SetParent(player.transform, false); castOrigin.localPosition = new Vector3(0f, 1.25f, 0.55f); AbilityManager manager = player.AddComponent(); SetObject(manager, "playerStats", stats); SetObject(manager, "castOrigin", castOrigin); SetObjectArray(manager, "learnedAbilities", abilities); return player; } private static void BuildCamera(Transform player) { GameObject cameraObject = new GameObject("Main Camera"); Camera camera = cameraObject.AddComponent(); cameraObject.tag = "MainCamera"; camera.nearClipPlane = 0.05f; camera.fieldOfView = 48f; cameraObject.transform.position = player.position + new Vector3(0f, 11f, -9f); cameraObject.transform.LookAt(player.position + Vector3.up * 1.3f); ThirdPersonCamera follow = cameraObject.AddComponent(); follow.SetTarget(player); SetFloat(follow, "mouseSensitivity", 1.8f); SetFloat(follow, "smoothTime", 0.12f); SetFloat(follow, "minPitch", 45f); SetFloat(follow, "maxPitch", 60f); SetFloat(follow, "initialPitch", 52f); SetFloat(follow, "distance", 14f); SetFloat(follow, "lookAhead", 3.5f); SetFloat(follow, "targetHeight", 1.25f); PlayerController controller = player.GetComponent(); SetObject(controller, "cameraTransform", cameraObject.transform); } private static void BuildEnemies(GameObject enemyPrefab) { Vector3[] positions = { new Vector3(-15f, 1.35f, 8f), new Vector3(-7f, 1.35f, 14f), new Vector3(7f, 1.35f, 14f), new Vector3(15f, 1.35f, 8f), new Vector3(-18f, 1.35f, -3f), new Vector3(18f, 1.35f, -3f), new Vector3(-9f, 1.35f, -18f), new Vector3(9f, 1.35f, -18f) }; for (int i = 0; i < positions.Length; i++) { GameObject enemy = (GameObject)PrefabUtility.InstantiatePrefab(enemyPrefab); enemy.name = $"Enemy_{i + 1:00}"; enemy.transform.position = positions[i]; enemy.transform.LookAt(new Vector3(0f, enemy.transform.position.y, -10f)); } } private static void BuildTrainingMannequins(Dictionary materials) { for (int i = 0; i < 4; i++) { float x = -9f + i * 6f; GameObject body = GameObject.CreatePrimitive(PrimitiveType.Capsule); body.name = $"Training Mannequin_{i + 1:00}"; body.transform.position = new Vector3(x, 1.1f, 21f); body.transform.localScale = new Vector3(0.75f, 1.1f, 0.75f); body.GetComponent().sharedMaterial = materials["Mannequin"]; GameObject cross = GameObject.CreatePrimitive(PrimitiveType.Cube); cross.name = "Practice Armature"; cross.transform.SetParent(body.transform, false); cross.transform.localPosition = new Vector3(0f, 0.35f, 0f); cross.transform.localScale = new Vector3(1.8f, 0.12f, 0.12f); cross.GetComponent().sharedMaterial = materials["ArenaTrim"]; } } private static void BuildManaCrystals(GameObject crystalPrefab) { Vector3[] positions = { new Vector3(-21f, 0.1f, -12f), new Vector3(21f, 0.1f, -12f), new Vector3(-21f, 0.1f, 12f), new Vector3(21f, 0.1f, 12f) }; for (int i = 0; i < positions.Length; i++) { GameObject crystal = (GameObject)PrefabUtility.InstantiatePrefab(crystalPrefab); crystal.name = $"Mana Crystal_{i + 1:00}"; crystal.transform.position = positions[i]; } } private static void BuildFloatingDamageManager(FloatingDamage prefab) { GameObject manager = new GameObject("FloatingDamageManager"); FloatingDamageManager damageManager = manager.AddComponent(); SetObject(damageManager, "floatingDamagePrefab", prefab); } private static void BuildUI(GameObject player, AbilitySlotUI slotPrefab, Font font, Dictionary materials) { GameObject canvasObject = new GameObject("Gameplay UI", typeof(RectTransform)); Canvas canvas = canvasObject.AddComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; CanvasScaler scaler = canvasObject.AddComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1280f, 720f); scaler.matchWidthOrHeight = 0.5f; canvasObject.AddComponent(); GameObject eventSystem = new GameObject("EventSystem"); eventSystem.AddComponent(); eventSystem.AddComponent(); TooltipUI tooltip = BuildTooltip(canvasObject.transform, font); BuildResourcePanel(canvasObject.transform, player.GetComponent(), font, false, new Vector2(24f, 72f), new Color(0.78f, 0.16f, 0.17f, 1f)); BuildResourcePanel(canvasObject.transform, player.GetComponent(), font, true, new Vector2(24f, 34f), new Color(0.12f, 0.55f, 0.95f, 1f)); BuildAbilityBar(canvasObject.transform, player.GetComponent(), slotPrefab, tooltip, font); } private static void BuildAbilityBar(Transform canvas, AbilityManager manager, AbilitySlotUI slotPrefab, TooltipUI tooltip, Font font) { GameObject panel = CreateUIObject("Ability Bar", canvas, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(318f, 92f), new Vector2(0f, 18f)); Image back = panel.AddComponent(); back.color = new Color(0.025f, 0.03f, 0.035f, 0.72f); GameObject root = CreateUIObject("Slots", panel.transform, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), new Vector2(-20f, -16f), Vector2.zero); HorizontalLayoutGroup layout = root.AddComponent(); layout.spacing = 10f; layout.childAlignment = TextAnchor.MiddleCenter; layout.childControlWidth = false; layout.childControlHeight = false; layout.childForceExpandWidth = false; layout.childForceExpandHeight = false; AbilityBarUI bar = panel.AddComponent(); SetObject(bar, "abilityManager", manager); SetObject(bar, "slotPrefab", slotPrefab); SetObject(bar, "slotRoot", root.transform); SetObject(bar, "tooltip", tooltip); } private static void BuildResourcePanel(Transform canvas, PlayerStats stats, Font font, bool mana, Vector2 anchoredPosition, Color fillColor) { GameObject panel = CreateUIObject(mana ? "Mana Bar" : "HP Bar", canvas, Vector2.zero, Vector2.zero, Vector2.zero, new Vector2(226f, 28f), anchoredPosition); Image back = panel.AddComponent(); back.color = new Color(0.025f, 0.03f, 0.035f, 0.74f); Text name = CreateText(mana ? "Mana Label" : "HP Label", panel.transform, font, mana ? "Mana" : "HP", 13, TextAnchor.MiddleLeft, new Color(0.9f, 0.92f, 0.94f, 1f)); Anchor(name.rectTransform, new Vector2(0f, 0f), new Vector2(0f, 1f), new Vector2(0f, 0.5f), new Vector2(50f, 0f), new Vector2(10f, 0f)); GameObject fillBackObject = CreateUIObject("Track", panel.transform, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), new Vector2(-62f, -10f), new Vector2(26f, 0f)); Image fillBack = fillBackObject.AddComponent(); fillBack.color = new Color(0f, 0f, 0f, 0.28f); GameObject fillObject = CreateUIObject("Fill", panel.transform, Vector2.zero, Vector2.one, new Vector2(0f, 0.5f), new Vector2(-62f, -10f), new Vector2(26f, 0f)); Image fill = fillObject.AddComponent(); fill.color = fillColor; fill.type = Image.Type.Filled; fill.fillMethod = Image.FillMethod.Horizontal; Text label = CreateText("Value", panel.transform, font, "100 / 100", 13, TextAnchor.MiddleRight, Color.white); Anchor(label.rectTransform, new Vector2(1f, 0f), new Vector2(1f, 1f), new Vector2(1f, 0.5f), new Vector2(78f, 0f), new Vector2(-9f, 0f)); ResourceBarUI resource = panel.AddComponent(); SetObject(resource, "playerStats", stats); SetBool(resource, "useMana", mana); SetObject(resource, "fill", fill); SetObject(resource, "valueLabel", label); } private static TooltipUI BuildTooltip(Transform canvas, Font font) { GameObject panel = CreateUIObject("Ability Tooltip", canvas, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(280f, 172f), new Vector2(0f, 164f)); Image back = panel.AddComponent(); back.color = new Color(0.025f, 0.03f, 0.035f, 0.96f); Text title = CreateText("Title", panel.transform, font, "Ability", 19, TextAnchor.MiddleLeft, Color.white); title.horizontalOverflow = HorizontalWrapMode.Overflow; title.verticalOverflow = VerticalWrapMode.Truncate; Anchor(title.rectTransform, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(0.5f, 1f), new Vector2(-28f, 30f), new Vector2(0f, -20f)); Text description = CreateText("Description", panel.transform, font, "", 13, TextAnchor.UpperLeft, new Color(0.78f, 0.82f, 0.86f, 1f)); description.horizontalOverflow = HorizontalWrapMode.Wrap; description.verticalOverflow = VerticalWrapMode.Truncate; Anchor(description.rectTransform, new Vector2(0f, 0.42f), new Vector2(1f, 0.79f), new Vector2(0.5f, 0.5f), new Vector2(-28f, 0f), new Vector2(0f, -4f)); Text stats = CreateText("Stats", panel.transform, font, "", 14, TextAnchor.UpperLeft, new Color(0.48f, 0.88f, 1f, 1f)); stats.horizontalOverflow = HorizontalWrapMode.Wrap; stats.verticalOverflow = VerticalWrapMode.Truncate; Anchor(stats.rectTransform, new Vector2(0f, 0f), new Vector2(1f, 0.34f), new Vector2(0.5f, 0f), new Vector2(-28f, -12f), new Vector2(0f, 10f)); TooltipUI tooltip = panel.AddComponent(); SetObject(tooltip, "titleLabel", title); SetObject(tooltip, "descriptionLabel", description); SetObject(tooltip, "statsLabel", stats); panel.SetActive(false); return tooltip; } private static GameObject CreateUIObject(string name, Transform parent, Vector2 anchorMin, Vector2 anchorMax, Vector2 pivot, Vector2 size, Vector2 anchoredPosition) { GameObject obj = new GameObject(name, typeof(RectTransform)); RectTransform rect = obj.GetComponent(); if (parent != null) { rect.SetParent(parent, false); } Anchor(rect, anchorMin, anchorMax, pivot, size, anchoredPosition); return obj; } private static Text CreateText(string name, Transform parent, Font font, string text, int size, TextAnchor alignment, Color color) { GameObject obj = CreateUIObject(name, parent, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(120f, 30f), Vector2.zero); Text label = obj.AddComponent(); label.font = font; label.text = text; label.fontSize = size; label.alignment = alignment; label.color = color; label.horizontalOverflow = HorizontalWrapMode.Wrap; label.verticalOverflow = VerticalWrapMode.Truncate; label.raycastTarget = false; Shadow shadow = obj.AddComponent(); shadow.effectColor = new Color(0f, 0f, 0f, 0.65f); shadow.effectDistance = new Vector2(1f, -1f); return label; } private static void Anchor(RectTransform rect, Vector2 anchorMin, Vector2 anchorMax, Vector2 pivot, Vector2 size, Vector2 anchoredPosition) { rect.anchorMin = anchorMin; rect.anchorMax = anchorMax; rect.pivot = pivot; rect.sizeDelta = size; rect.anchoredPosition = anchoredPosition; } private static void Stretch(RectTransform rect) { Anchor(rect, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), Vector2.zero, Vector2.zero); } private static GameObject SavePrefab(GameObject root, string path) { if (AssetDatabase.LoadAssetAtPath(path) != null) { AssetDatabase.DeleteAsset(path); } GameObject prefab = PrefabUtility.SaveAsPrefabAsset(root, path); Object.DestroyImmediate(root); return prefab; } private static void SetObject(Object target, string propertyName, Object value) { SerializedObject serializedObject = new SerializedObject(target); SerializedProperty property = serializedObject.FindProperty(propertyName); property.objectReferenceValue = value; serializedObject.ApplyModifiedPropertiesWithoutUndo(); } private static void SetObjectArray(Object target, string propertyName, Object[] values) { SerializedObject serializedObject = new SerializedObject(target); SerializedProperty property = serializedObject.FindProperty(propertyName); property.arraySize = values.Length; for (int i = 0; i < values.Length; i++) { property.GetArrayElementAtIndex(i).objectReferenceValue = values[i]; } serializedObject.ApplyModifiedPropertiesWithoutUndo(); } private static void SetFloat(Object target, string propertyName, float value) { SerializedObject serializedObject = new SerializedObject(target); SerializedProperty property = serializedObject.FindProperty(propertyName); property.floatValue = value; serializedObject.ApplyModifiedPropertiesWithoutUndo(); } private static void SetVector3(Object target, string propertyName, Vector3 value) { SerializedObject serializedObject = new SerializedObject(target); SerializedProperty property = serializedObject.FindProperty(propertyName); property.vector3Value = value; serializedObject.ApplyModifiedPropertiesWithoutUndo(); } private static void SetBool(Object target, string propertyName, bool value) { SerializedObject serializedObject = new SerializedObject(target); SerializedProperty property = serializedObject.FindProperty(propertyName); property.boolValue = value; serializedObject.ApplyModifiedPropertiesWithoutUndo(); } }