420 lines
15 KiB
C#
420 lines
15 KiB
C#
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<string, Type> abilityTypes;
|
|
private Dictionary<string, Type> effectTypes;
|
|
|
|
// Tabs
|
|
private int selectedTab = 0;
|
|
private readonly string[] tabNames = { "Abilities", "Effects" };
|
|
|
|
[MenuItem("Tools/Ability & Effect Editor")]
|
|
public static void ShowWindow()
|
|
{
|
|
GetWindow<AbilityEffectEditorWindow>("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<float>("projectileSpeed");
|
|
projectileSpeed = EditorGUILayout.FloatField("Projectile Speed", projectileSpeed);
|
|
abilityData.typeSpecificData["projectileSpeed"] = projectileSpeed;
|
|
|
|
float lifeSpan = abilityData.GetOrCreateTypeSpecific<float>("lifeSpan");
|
|
lifeSpan = EditorGUILayout.FloatField("Life Span", lifeSpan);
|
|
abilityData.typeSpecificData["lifeSpan"] = lifeSpan;
|
|
|
|
bool canPierce = abilityData.GetOrCreateTypeSpecific<bool>("canPierce");
|
|
canPierce = EditorGUILayout.Toggle("Can Pierce", canPierce);
|
|
abilityData.typeSpecificData["canPierce"] = canPierce;
|
|
|
|
GameObject projectilePrefab = abilityData.GetOrCreateTypeSpecific<GameObject>("projectilePrefab");
|
|
projectilePrefab = (GameObject)EditorGUILayout.ObjectField("Projectile Prefab",
|
|
projectilePrefab, typeof(GameObject), false);
|
|
abilityData.typeSpecificData["projectilePrefab"] = projectilePrefab;
|
|
}
|
|
|
|
private void DrawAreaOfEffectFields()
|
|
{
|
|
float radius = abilityData.GetOrCreateTypeSpecific<float>("radius");
|
|
radius = EditorGUILayout.FloatField("Radius", radius);
|
|
abilityData.typeSpecificData["radius"] = radius;
|
|
|
|
float duration = abilityData.GetOrCreateTypeSpecific<float>("duration");
|
|
duration = EditorGUILayout.FloatField("Duration", duration);
|
|
abilityData.typeSpecificData["duration"] = duration;
|
|
|
|
GameObject aoePrefab = abilityData.GetOrCreateTypeSpecific<GameObject>("aoePrefab");
|
|
aoePrefab = (GameObject)EditorGUILayout.ObjectField("AoE Prefab",
|
|
aoePrefab, typeof(GameObject), false);
|
|
abilityData.typeSpecificData["aoePrefab"] = aoePrefab;
|
|
}
|
|
|
|
private void DrawChanneledFields()
|
|
{
|
|
float channelDuration = abilityData.GetOrCreateTypeSpecific<float>("channelDuration");
|
|
channelDuration = EditorGUILayout.FloatField("Channel Duration", channelDuration);
|
|
abilityData.typeSpecificData["channelDuration"] = channelDuration;
|
|
|
|
bool canMove = abilityData.GetOrCreateTypeSpecific<bool>("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<float>("projectileSpeed");
|
|
projectileAbility.lifeSpan = abilityData.GetOrCreateTypeSpecific<float>("lifeSpan");
|
|
projectileAbility.canPierce = abilityData.GetOrCreateTypeSpecific<bool>("canPierce");
|
|
projectileAbility.projectilePrefab = abilityData.GetOrCreateTypeSpecific<GameObject>("projectilePrefab");
|
|
}
|
|
else if (ability is AreaOfEffectAbility aoeAbility)
|
|
{
|
|
aoeAbility.radius = abilityData.GetOrCreateTypeSpecific<float>("radius");
|
|
aoeAbility.lifeSpan = abilityData.GetOrCreateTypeSpecific<float>("duration");
|
|
aoeAbility.aoePrefab = abilityData.GetOrCreateTypeSpecific<GameObject>("aoePrefab");
|
|
}
|
|
else if (ability is ChanneledAbility channeledAbility)
|
|
{
|
|
channeledAbility.duration = abilityData.GetOrCreateTypeSpecific<float>("channelDuration");
|
|
channeledAbility.castableWhileMoving = abilityData.GetOrCreateTypeSpecific<bool>("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<BaseAbility>(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<BaseEffect> 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<GameTag> tags = new List<GameTag>();
|
|
public List<StatInfluence> statInfluences = new List<StatInfluence>();
|
|
public SerializedDictionary<string, object> typeSpecificData = new SerializedDictionary<string, object>();
|
|
|
|
public T GetOrCreateTypeSpecific<T>(string key)
|
|
{
|
|
if (!typeSpecificData.ContainsKey(key))
|
|
{
|
|
if (typeof(T) == typeof(List<StatInfluence>))
|
|
typeSpecificData[key] = new List<StatInfluence>();
|
|
else
|
|
typeSpecificData[key] = default(T);
|
|
}
|
|
return (T)typeSpecificData[key];
|
|
}
|
|
} |