Created
May 25, 2025 10:34
-
-
Save adammyhre/8e6f57d24e96d33738273ed0570828be to your computer and use it in GitHub Desktop.
Modular Ability Effects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
[CreateAssetMenu(fileName = "AbilityData", menuName = "ScriptableObjects/AbilityData")] | |
class AbilityData : ScriptableObject { | |
public string label; | |
public AnimationClip animationClip; | |
[Range(0.1f, 4f)] public float castTime = 2f; | |
public ProjectileMove vfxPrefab; | |
[SerializeReference] public List<AbilityEffect> effects; | |
void OnEnable() { | |
if (string.IsNullOrEmpty(label)) label = name; | |
if (effects == null) effects = new List<AbilityEffect>(); | |
} | |
} | |
[Serializable] | |
abstract class AbilityEffect { | |
public abstract void Execute(GameObject caster, GameObject target); | |
} | |
[Serializable] | |
class DamageEffect : AbilityEffect { | |
public int amount; | |
public override void Execute(GameObject caster, GameObject target) { | |
target.GetComponent<Health>().ApplyDamage(amount); | |
Debug.Log($"{caster.name} dealt {amount} damage to {target.name}"); | |
} | |
} | |
[Serializable] | |
class KnockbackEffect : AbilityEffect { | |
public float force; | |
public override void Execute(GameObject caster, GameObject target) { | |
var dir = (target.transform.position - caster.transform.position).normalized; | |
target.GetComponent<Rigidbody>().AddForce(dir * force, ForceMode.Impulse); | |
Debug.Log($"{caster.name} knocked back {target.name} with force {force}"); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEditor; | |
using System; | |
using System.Linq; | |
using System.Collections.Generic; | |
[CustomPropertyDrawer(typeof(AbilityEffect), true)] | |
public class AbilityEffectDrawer : PropertyDrawer { | |
static Dictionary<string, Type> typeMap; | |
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { | |
if (typeMap == null) BuildTypeMap(); | |
var typeRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); | |
var contentRect = new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight, position.width, position.height - EditorGUIUtility.singleLineHeight); | |
EditorGUI.BeginProperty(position, label, property); | |
var typeName = property.managedReferenceFullTypename; | |
var displayName = GetShortTypeName(typeName); | |
if (EditorGUI.DropdownButton(typeRect, new GUIContent(displayName ?? "Select Effect Type"), FocusType.Keyboard)) { | |
var menu = new GenericMenu(); | |
if (typeMap == null || typeMap.Count == 0) { | |
menu.AddDisabledItem(new GUIContent("No Ability Effects available")); | |
menu.ShowAsContext(); | |
return; | |
} | |
foreach (var kvp in typeMap) { | |
var name = kvp.Key; | |
var type = kvp.Value; | |
menu.AddItem(new GUIContent(name), type.FullName == typeName, () => { | |
property.managedReferenceValue = Activator.CreateInstance(type); | |
property.serializedObject.ApplyModifiedProperties(); | |
}); | |
} | |
menu.ShowAsContext(); | |
} | |
if (property.managedReferenceValue != null) { | |
EditorGUI.indentLevel++; | |
EditorGUI.PropertyField(contentRect, property, GUIContent.none, true); | |
EditorGUI.indentLevel--; | |
} | |
EditorGUI.EndProperty(); | |
} | |
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { | |
return EditorGUI.GetPropertyHeight(property, label, true) + EditorGUIUtility.singleLineHeight; | |
} | |
static void BuildTypeMap() { | |
var baseType = typeof(AbilityEffect); | |
typeMap = AppDomain.CurrentDomain.GetAssemblies() | |
.SelectMany(asm => { | |
try { return asm.GetTypes(); } | |
catch { return Type.EmptyTypes; } | |
}) | |
.Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)) | |
.ToDictionary(t => ObjectNames.NicifyVariableName(t.Name), t => t); | |
} | |
static string GetShortTypeName(string fullTypeName) { | |
if (string.IsNullOrEmpty(fullTypeName)) return null; | |
var parts = fullTypeName.Split(' '); | |
return parts.Length > 1 ? parts[1].Split('.').Last() : fullTypeName; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using UnityEngine; | |
using UnityEngine.InputSystem; | |
using AdvancedController; | |
using ImprovedTimers; | |
class AbilityExecutor : MonoBehaviour { | |
[SerializeField] AbilityData ability; | |
[SerializeField] GameObject target; | |
AnimationController animationController; | |
CountdownTimer castTimer; | |
void Awake() { | |
animationController = GetComponent<AnimationController>(); | |
castTimer = new CountdownTimer(ability.castTime); | |
castTimer.OnTimerStart = () => animationController.OrNull()?.PlayOneShot(ability.animationClip); | |
castTimer.OnTimerStop = () => SpawnVFX(); | |
} | |
void SpawnVFX() { | |
if (ability.vfxPrefab == null) return; | |
var vfx = Instantiate(ability.vfxPrefab, transform.position, transform.rotation); | |
vfx.SetCallback((Collision co) => { | |
foreach (var effect in ability.effects) { | |
effect.Execute(gameObject, target); | |
} | |
}); | |
Destroy(vfx, 5f); | |
} | |
public void Execute(GameObject target) { | |
castTimer.Start(); | |
} | |
void Update() { | |
if (Keyboard.current.spaceKey.wasPressedThisFrame) { | |
Execute(target); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment