Instantly share code, notes, and snippets.
CreatedMay 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
| usingSystem; | |
| usingSystem.Collections.Generic; | |
| usingUnityEngine; | |
| [CreateAssetMenu(fileName="AbilityData",menuName="ScriptableObjects/AbilityData")] | |
| classAbilityData:ScriptableObject{ | |
| publicstringlabel; | |
| publicAnimationClipanimationClip; | |
| [Range(0.1f,4f)]publicfloatcastTime=2f; | |
| publicProjectileMovevfxPrefab; | |
| [SerializeReference]publicList<AbilityEffect>effects; | |
| voidOnEnable(){ | |
| if(string.IsNullOrEmpty(label))label=name; | |
| if(effects==null)effects=newList<AbilityEffect>(); | |
| } | |
| } | |
| [Serializable] | |
| abstractclassAbilityEffect{ | |
| publicabstractvoidExecute(GameObjectcaster,GameObjecttarget); | |
| } | |
| [Serializable] | |
| classDamageEffect:AbilityEffect{ | |
| publicintamount; | |
| publicoverridevoidExecute(GameObjectcaster,GameObjecttarget){ | |
| target.GetComponent<Health>().ApplyDamage(amount); | |
| Debug.Log($"{caster.name} dealt{amount} damage to{target.name}"); | |
| } | |
| } | |
| [Serializable] | |
| classKnockbackEffect:AbilityEffect{ | |
| publicfloatforce; | |
| publicoverridevoidExecute(GameObjectcaster,GameObjecttarget){ | |
| vardir=(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
| usingUnityEngine; | |
| usingUnityEditor; | |
| usingSystem; | |
| usingSystem.Linq; | |
| usingSystem.Collections.Generic; | |
| [CustomPropertyDrawer(typeof(AbilityEffect),true)] | |
| publicclassAbilityEffectDrawer:PropertyDrawer{ | |
| staticDictionary<string,Type>typeMap; | |
| publicoverridevoidOnGUI(Rectposition,SerializedPropertyproperty,GUIContentlabel){ | |
| if(typeMap==null)BuildTypeMap(); | |
| vartypeRect=newRect(position.x,position.y,position.width,EditorGUIUtility.singleLineHeight); | |
| varcontentRect=newRect(position.x,position.y+EditorGUIUtility.singleLineHeight,position.width,position.height-EditorGUIUtility.singleLineHeight); | |
| EditorGUI.BeginProperty(position,label,property); | |
| vartypeName=property.managedReferenceFullTypename; | |
| vardisplayName=GetShortTypeName(typeName); | |
| if(EditorGUI.DropdownButton(typeRect,newGUIContent(displayName??"Select Effect Type"),FocusType.Keyboard)){ | |
| varmenu=newGenericMenu(); | |
| if(typeMap==null||typeMap.Count==0){ | |
| menu.AddDisabledItem(newGUIContent("No Ability Effects available")); | |
| menu.ShowAsContext(); | |
| return; | |
| } | |
| foreach(varkvpintypeMap){ | |
| varname=kvp.Key; | |
| vartype=kvp.Value; | |
| menu.AddItem(newGUIContent(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(); | |
| } | |
| publicoverridefloatGetPropertyHeight(SerializedPropertyproperty,GUIContentlabel){ | |
| returnEditorGUI.GetPropertyHeight(property,label,true)+EditorGUIUtility.singleLineHeight; | |
| } | |
| staticvoidBuildTypeMap(){ | |
| varbaseType=typeof(AbilityEffect); | |
| typeMap=AppDomain.CurrentDomain.GetAssemblies() | |
| .SelectMany(asm=>{ | |
| try{returnasm.GetTypes();} | |
| catch{returnType.EmptyTypes;} | |
| }) | |
| .Where(t=>!t.IsAbstract&&baseType.IsAssignableFrom(t)) | |
| .ToDictionary(t=>ObjectNames.NicifyVariableName(t.Name), t=>t); | |
| } | |
| staticstringGetShortTypeName(stringfullTypeName){ | |
| if(string.IsNullOrEmpty(fullTypeName))returnnull; | |
| varparts=fullTypeName.Split(' '); | |
| returnparts.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
| usingSystem.Collections; | |
| usingUnityEngine; | |
| usingUnityEngine.InputSystem; | |
| usingAdvancedController; | |
| usingImprovedTimers; | |
| classAbilityExecutor:MonoBehaviour{ | |
| [SerializeField]AbilityDataability; | |
| [SerializeField]GameObjecttarget; | |
| AnimationControlleranimationController; | |
| CountdownTimercastTimer; | |
| voidAwake(){ | |
| animationController=GetComponent<AnimationController>(); | |
| castTimer=newCountdownTimer(ability.castTime); | |
| castTimer.OnTimerStart=()=>animationController.OrNull()?.PlayOneShot(ability.animationClip); | |
| castTimer.OnTimerStop=()=>SpawnVFX(); | |
| } | |
| voidSpawnVFX(){ | |
| if(ability.vfxPrefab==null)return; | |
| varvfx=Instantiate(ability.vfxPrefab,transform.position,transform.rotation); | |
| vfx.SetCallback((Collisionco)=>{ | |
| foreach(vareffectinability.effects){ | |
| effect.Execute(gameObject,target); | |
| } | |
| }); | |
| Destroy(vfx,5f); | |
| } | |
| publicvoidExecute(GameObjecttarget){ | |
| castTimer.Start(); | |
| } | |
| voidUpdate(){ | |
| if(Keyboard.current.spaceKey.wasPressedThisFrame){ | |
| Execute(target); | |
| } | |
| } | |
| } |
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment