using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.Linq; using System; public partial class AbilityEffectEditorWindow : EditorWindow { private Vector2 scrollPosition; private string searchQuery = ""; private AbilityCreationData abilityData; private EffectCreationData effectData; private Type selectedAbilityType; private Type selectedEffectType; private Dictionary abilityTypes; private Dictionary effectTypes; // Tabs private int selectedTab = 0; private readonly string[] tabNames = { "Abilities", "Effects" }; [MenuItem("Tools/Ability & Effect Editor")] public static void ShowWindow() { GetWindow("Ability & Effect Editor"); } private void OnEnable() { InitializeData(); CacheTypes(); } private void InitializeData() { abilityData = new AbilityCreationData(); effectData = new EffectCreationData(); } private void CacheTypes() { // Cache ability types abilityTypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(assembly => assembly.GetTypes()) .Where(type => type.IsClass && !type.IsAbstract && typeof(BaseAbility).IsAssignableFrom(type)) .ToDictionary(type => type.Name, type => type); // Cache effect types effectTypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(assembly => assembly.GetTypes()) .Where(type => type.IsClass && !type.IsAbstract && typeof(BaseEffect).IsAssignableFrom(type)) .ToDictionary(type => type.Name, type => type); } private void OnGUI() { DrawHeader(); DrawSearchBar(); selectedTab = GUILayout.Toolbar(selectedTab, tabNames); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); switch (selectedTab) { case 0: // Abilities Tab DrawAbilityCreation(); EditorGUILayout.Space(20); DrawExistingAbilities(); break; case 1: // Effects Tab DrawEffectCreation(); EditorGUILayout.Space(20); DrawExistingEffects(); break; } EditorGUILayout.EndScrollView(); } private void DrawHeader() { EditorGUILayout.Space(10); GUILayout.Label("Ability Effect Editor", EditorStyles.boldLabel); EditorGUILayout.Space(5); } private void DrawAbilityCreation() { EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField("Create New Ability", EditorStyles.boldLabel); DrawAbilityTypeSelection(); if (selectedAbilityType != null) { DrawBasicFields(); DrawTypeSpecificFields(); EditorGUILayout.Space(10); if (GUILayout.Button("Create Ability")) { CreateAbility(); } } EditorGUILayout.EndVertical(); } private void DrawBasicFields() { EditorGUILayout.Space(10); EditorGUILayout.LabelField("Basic Settings", EditorStyles.boldLabel); abilityData.name = EditorGUILayout.TextField("Name", abilityData.name); abilityData.description = EditorGUILayout.TextArea(abilityData.description, GUILayout.Height(60)); abilityData.icon = (Sprite)EditorGUILayout.ObjectField("Icon", abilityData.icon, typeof(Sprite), false); abilityData.animationType = (AbilityAnimationType)EditorGUILayout.EnumPopup("Animation Type", abilityData.animationType); EditorGUILayout.Space(5); EditorGUILayout.LabelField("Costs and Timing", EditorStyles.boldLabel); abilityData.manaCost = EditorGUILayout.FloatField("Mana Cost", abilityData.manaCost); abilityData.healthCost = EditorGUILayout.FloatField("Health Cost", abilityData.healthCost); abilityData.cooldown = EditorGUILayout.FloatField("Cooldown", abilityData.cooldown); abilityData.castTime = EditorGUILayout.FloatField("Cast Time", abilityData.castTime); abilityData.castableWhileMoving = EditorGUILayout.Toggle("Castable While Moving", abilityData.castableWhileMoving); } private void DrawTypeSpecificFields() { if (selectedAbilityType == null) return; EditorGUILayout.Space(10); EditorGUILayout.LabelField("Type Specific Settings", EditorStyles.boldLabel); if (selectedAbilityType == typeof(ProjectileAbility)) { DrawProjectileFields(); } else if (selectedAbilityType == typeof(AreaOfEffectAbility)) { DrawAreaOfEffectFields(); } else if (selectedAbilityType == typeof(ChanneledAbility)) { DrawChanneledFields(); } } private void DrawProjectileFields() { float projectileSpeed = abilityData.GetOrCreateTypeSpecific("projectileSpeed"); projectileSpeed = EditorGUILayout.FloatField("Projectile Speed", projectileSpeed); abilityData.typeSpecificData["projectileSpeed"] = projectileSpeed; float lifeSpan = abilityData.GetOrCreateTypeSpecific("lifeSpan"); lifeSpan = EditorGUILayout.FloatField("Life Span", lifeSpan); abilityData.typeSpecificData["lifeSpan"] = lifeSpan; bool canPierce = abilityData.GetOrCreateTypeSpecific("canPierce"); canPierce = EditorGUILayout.Toggle("Can Pierce", canPierce); abilityData.typeSpecificData["canPierce"] = canPierce; GameObject projectilePrefab = abilityData.GetOrCreateTypeSpecific("projectilePrefab"); projectilePrefab = (GameObject)EditorGUILayout.ObjectField("Projectile Prefab", projectilePrefab, typeof(GameObject), false); abilityData.typeSpecificData["projectilePrefab"] = projectilePrefab; } private void DrawAreaOfEffectFields() { float radius = abilityData.GetOrCreateTypeSpecific("radius"); radius = EditorGUILayout.FloatField("Radius", radius); abilityData.typeSpecificData["radius"] = radius; float duration = abilityData.GetOrCreateTypeSpecific("duration"); duration = EditorGUILayout.FloatField("Duration", duration); abilityData.typeSpecificData["duration"] = duration; GameObject aoePrefab = abilityData.GetOrCreateTypeSpecific("aoePrefab"); aoePrefab = (GameObject)EditorGUILayout.ObjectField("AoE Prefab", aoePrefab, typeof(GameObject), false); abilityData.typeSpecificData["aoePrefab"] = aoePrefab; } private void DrawChanneledFields() { float channelDuration = abilityData.GetOrCreateTypeSpecific("channelDuration"); channelDuration = EditorGUILayout.FloatField("Channel Duration", channelDuration); abilityData.typeSpecificData["channelDuration"] = channelDuration; bool canMove = abilityData.GetOrCreateTypeSpecific("canMove"); canMove = EditorGUILayout.Toggle("Can Move While Channeling", canMove); abilityData.typeSpecificData["canMove"] = canMove; } private void CreateAbility() { if (selectedAbilityType == null) { EditorUtility.DisplayDialog("Error", "Please select an ability type!", "OK"); return; } if (string.IsNullOrEmpty(abilityData.name)) { EditorUtility.DisplayDialog("Error", "Ability name cannot be empty!", "OK"); return; } BaseAbility ability = (BaseAbility)CreateInstance(selectedAbilityType); ability.name = abilityData.name; ability.Icon = abilityData.icon; ability.animationType = abilityData.animationType; ability.manaCost = abilityData.manaCost; ability.healthCost = abilityData.healthCost; ability.cooldown = abilityData.cooldown; ability.castTime = abilityData.castTime; ability.castableWhileMoving = abilityData.castableWhileMoving; ApplyTypeSpecificData(ability); string path = EditorUtility.SaveFilePanelInProject( "Save Ability", abilityData.name, "asset", "Save ability asset" ); if (!string.IsNullOrEmpty(path)) { AssetDatabase.CreateAsset(ability, path); AssetDatabase.SaveAssets(); EditorUtility.FocusProjectWindow(); Selection.activeObject = ability; abilityData = new AbilityCreationData(); selectedAbilityType = null; } } private void ApplyTypeSpecificData(BaseAbility ability) { if (ability is ProjectileAbility projectileAbility) { projectileAbility.projectileSpeed = abilityData.GetOrCreateTypeSpecific("projectileSpeed"); projectileAbility.lifeSpan = abilityData.GetOrCreateTypeSpecific("lifeSpan"); projectileAbility.canPierce = abilityData.GetOrCreateTypeSpecific("canPierce"); projectileAbility.projectilePrefab = abilityData.GetOrCreateTypeSpecific("projectilePrefab"); } else if (ability is AreaOfEffectAbility aoeAbility) { aoeAbility.radius = abilityData.GetOrCreateTypeSpecific("radius"); aoeAbility.lifeSpan = abilityData.GetOrCreateTypeSpecific("duration"); aoeAbility.aoePrefab = abilityData.GetOrCreateTypeSpecific("aoePrefab"); } else if (ability is ChanneledAbility channeledAbility) { channeledAbility.duration = abilityData.GetOrCreateTypeSpecific("channelDuration"); channeledAbility.castableWhileMoving = abilityData.GetOrCreateTypeSpecific("canMove"); } } private void DrawAbilityTypeSelection() { EditorGUILayout.Space(10); EditorGUILayout.LabelField("Ability Type", EditorStyles.boldLabel); string[] typeNames = abilityTypes.Keys.ToArray(); int currentIndex = Array.IndexOf(typeNames, selectedAbilityType?.Name ?? ""); int newIndex = EditorGUILayout.Popup("Select Type", currentIndex, typeNames); if (newIndex != currentIndex && newIndex >= 0) { selectedAbilityType = abilityTypes[typeNames[newIndex]]; abilityData.typeSpecificData.Clear(); } } private void DrawAbilityItem(BaseAbility ability) { EditorGUILayout.BeginHorizontal(); if (ability.Icon != null) { GUILayout.Label(AssetPreview.GetAssetPreview(ability.Icon), GUILayout.Width(40), GUILayout.Height(40)); } EditorGUILayout.BeginVertical(); EditorGUILayout.LabelField(ability.name, EditorStyles.boldLabel); EditorGUILayout.LabelField($"Type: {ability.GetType().Name}"); EditorGUILayout.EndVertical(); if (GUILayout.Button("Clone", GUILayout.Width(50))) { CloneAbility(ability); } if (GUILayout.Button("Edit", GUILayout.Width(50))) { Selection.activeObject = ability; EditorUtility.FocusProjectWindow(); } EditorGUILayout.EndHorizontal(); } private void CloneAbility(BaseAbility sourceAbility) { string newName = $"{sourceAbility.name}_Clone"; string path = EditorUtility.SaveFilePanelInProject( "Save Cloned Ability", newName, "asset", "Save cloned ability asset" ); if (string.IsNullOrEmpty(path)) return; BaseAbility newAbility = (BaseAbility)CreateInstance(sourceAbility.GetType()); EditorUtility.CopySerialized(sourceAbility, newAbility); newAbility.name = newName; AssetDatabase.CreateAsset(newAbility, path); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); EditorUtility.FocusProjectWindow(); Selection.activeObject = newAbility; } private void DrawExistingAbilities() { EditorGUILayout.LabelField("Existing Abilities", EditorStyles.boldLabel); var abilities = GetAllAbilities(); foreach (var ability in abilities) { if (string.IsNullOrEmpty(searchQuery) || ability.name.ToLower().Contains(searchQuery.ToLower())) { EditorGUILayout.BeginVertical("box"); DrawAbilityItem(ability); EditorGUILayout.EndVertical(); } } } private BaseAbility[] GetAllAbilities() { return AssetDatabase.FindAssets("t:BaseAbility") .Select(guid => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid))) .ToArray(); } private void DrawSearchBar() { EditorGUILayout.BeginHorizontal(); searchQuery = EditorGUILayout.TextField("Search", searchQuery); if (GUILayout.Button("Clear", GUILayout.Width(50))) searchQuery = ""; EditorGUILayout.EndHorizontal(); } private void DrawEffectListForAbility(List effects) { EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField("Effects", EditorStyles.boldLabel); for (int i = 0; i < effects.Count; i++) { EditorGUILayout.BeginHorizontal(); effects[i] = (BaseEffect)EditorGUILayout.ObjectField(effects[i], typeof(BaseEffect), false); if (GUILayout.Button("Remove", GUILayout.Width(60))) { effects.RemoveAt(i); break; } EditorGUILayout.EndHorizontal(); } EditorGUILayout.Space(); if (GUILayout.Button("Add Effect")) { effects.Add(null); } if (GUILayout.Button("Create New Effect")) { selectedTab = 1; // Switch to Effects tab effectData = new EffectCreationData(); } EditorGUILayout.EndVertical(); } } [System.Serializable] public class EffectCreationData { public string name; public string description; public float duration; public bool applyToTargetsHit = true; public bool applyToSelf; public List tags = new List(); public List statInfluences = new List(); public SerializedDictionary typeSpecificData = new SerializedDictionary(); public T GetOrCreateTypeSpecific(string key) { if (!typeSpecificData.ContainsKey(key)) { if (typeof(T) == typeof(List)) typeSpecificData[key] = new List(); else typeSpecificData[key] = default(T); } return (T)typeSpecificData[key]; } }