- Home /
Modular ability creation in editor
Newbie here
My goal:
I want to modular generate different abilties in the inspector that can be attached to a Monobehaviour. Abilty values can also be tweaked in the inspector seperatly from each other
What i came up with so far
Every unique Ability is a ScriptableObject asset. It contains an array of AbilityProperties. AbilityProperty is also an ScriptableObject and acts as a "superclass". Unique AbilityProperties derive from AbilityProperty and implement a simple interface, which only holds a method called execute(). With a custom drawer you are able to attach unique AbilityProperties and tweak the values of every single one of them.
The problem
Now the big obvious flaw in this design is, when i tweak an AbilityProperty value in one Ability it will change in every other Ability too. Because there is only one AbilityProperty_Damage ScriptableObject. Or I have too create a ScriptableObject Asset for every property of every ability which seems nuts.
So anyone has an idea how to tackle this problem? Since I'm at the beginning I can redesign the whole thing.
The code
Sorry for the wierd formating but here i had an issue with the web editor:
[CreateAssetMenu(fileName = "Ability")] public class Ability : ScriptableObject { public Property[] properties; }
public interface IProperty { void execute(); }
public class AbilityProperty : ScriptableObject { public string propName; }
[CreateAssetMenu(fileName = "propDamage")] public class Property_Damage : Property, IProperty { public int damageAmont;
public void execute()
{
// some damage specific logic
}
}
using UnityEngine; using UnityEditor; using System.Reflection; [CustomPropertyDrawer(typeof(AbilityProperty))] public class AbilityPropertyDrawer : PropertyDrawer { public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { if (property.objectReferenceValue == null) return base.GetPropertyHeight(property, label);
var modifier = property.objectReferenceValue as AbilityProperty;
var fields = modifier.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
return base.GetPropertyHeight(property, label) * (float)(fields.Length + 1);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
position.height = base.GetPropertyHeight(property, label);
EditorGUI.PropertyField(position, property);
if (property.objectReferenceValue == null)
return;
var modifier = property.objectReferenceValue as AbilityProperty;
var fields = modifier.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
var sObj = new SerializedObject(property.objectReferenceValue);
position.x += 20f;
position.xMax -= 38f;
foreach (var field in fields)
{
position.y += position.height;
EditorGUI.PropertyField(position, sObj.FindProperty(field.Name));
}
sObj.ApplyModifiedProperties();
}
}
Answer by logicandchaos · Apr 27, 2020 at 04:02 PM
Awesome I made something similar to this in my current project! Was pretty proud of it :P I called them Character Actions though.. :P You will need to make a SO instance for each ability.. but that is normal for SOs, when you start using SO variables it becomes second nature. Instead of an interface I made an abstract base class with a virtual method Perform, but I think it is pretty much the same.. I wanted to be able to add and remove actions for different characters, nothing hard coded, extendable modular and more decoupled! But how I implemented it was pretty neat too I made an informal finite state machine that used fuzzy pattern matching to select the state. I did this by making another abstract class called Condition that is also an SO with a virtual method bool Check() so basically anything can be a condition, pressing a certain button, health level, anything! Then I made my actions have a list of conditions and a method bool CheckConditions(), then I can check if all the conditions are met. So then in the script that implements it all I have a list of CharacterActions that I sort based on how many conditions there are, I got the fuzzy pattern matching and sorting by number of conditions from a GDC talk on Left4Dead, you want the actions with the most conditions to fire off first.. then I loop through it all like this:
List<CharacterAction> characterActions;
bool canAct=true;
..
Update(){
if(canAct){
foreach(CharacterAction action in characterActions)
{
if(action.CheckConditions())
{
action.PerformAction();
canAct=false;
ResetCanAct(action.delay);
break;
}
}
}
}
void ResetCanAct(float p_value)
{
Invoke("InvokeReset",p_value);
}
void InvokeReset()
{
canAct=true;
}
// I just like Invoke should probably be a coroutine..
so you do have to make an SO for each ability, but it's works pretty good.
I also don't really see any issue in creating seperate SO instances for every "ability" you create. That way you can reuse the same SO when appropriate or if you need it to be different just create a copy of the shared one, rename it and tweak it as you need it. It's more of a matter how you organise them in your project. Since it's extremely easy to "follow" a SO asset reference it's quite simple to navigate. Though of course you can get crazy with editor tools to show certain things inline or perform some of the housekeeping stuff in editor code.
I can highly recommend this Unite Talk from 2017 to get a few ideas how a modular system could be setup. Note that Schell games is a relatively large studio. Working in a studio with a $$anonymous$$m is in general quite different than working indy on your own.
Your answer
Follow this Question
Related Questions
Cannot include ScriptableObject in asset bundle in Unity 5 0 Answers
Animator Editor Window disappear when generate AnimatorController inside other asset 0 Answers
Scriptable Object save List 0 Answers
Create List of custom class types, for use in custom editor 0 Answers
Is it possible to write a function in the backend of an editor script for a scriptableObject? 0 Answers