I’m attempting to make a PvE Hero Shooter, akin to a mixture of TF2’s MvM and games like Overwatch or Marvel Rivals. I figured that the best way to do the various characters is to use Scriptable Objects to handle their stats and attacks, however the way I currently have it set up, while functional, is not very sustainable in the long run.
Currently, I have my Stats scriptable object contain references for 3 active abilities and a passive ability.
In my Attack scriptable object, I use an enum to set what type of ability it is, then in the Player script, I detect which ability type the enum is for each ability slot, then activate a function in the Player script.
if (CanM1())
{
m1Cooldown = data.mouseOne.rof;
if (data.mouseOne.type == AttackType.Projectile) Projectile(data.mouseOne, m1ing);
if (data.mouseOne.type == AttackType.Melee) Melee(data.mouseOne, m1ing);
if (data.mouseOne.type == AttackType.Movement) Movement(data.mouseOne, m1ing);
if (data.mouseOne.type == AttackType.Shield) Shield(data.mouseOne, m1ing);
}
I know there’s definitely a better way of doing this, but I’m not sure how. I’m fairly new to using Scriptable Objects, so would it be better to make different Scriptable Objects for the different attack types and have those contain the functions? If I do that, how would I activate the functions from my Player Script?
This is all very helpful for understanding more about Scriptable Objects, but I’m hoping to future proof this in a way that I would be able to add new Ability Types without having to edit any existing scripts. If I had a SO for all Projectile abilities, and another for all Melee abilities, is there a way to call a Function in the Player Script, say mouseOne.Fire();, and it activates no matter which Scriptable Object is set as mouseOne? Or is there something similar?
Jokes aside though: Your design is confusing architecture for configuration.
Instead going through each ability and looking for a matching type every single time you press the associated key/button, instead associate a key/button to an ‘ability slot’ and then assign the appropriate SO to that slot. This allows you to completely decouple ability logic from key/button presses that trigger them.
As a bonus you now have the ability to easily create a UI that allows the player to adjust their ability button mappings as well as the ability to ‘future proof’ yourself by allowing absolutely any valid ability to be mapped to any slot of any character at any time. At this point if you absolutely require that each key/button maps to a specific class of ability (in your example mouse1 is projectile, mouse2 is melee, etc…) then it simply becomes a matter of configuring your SO and prefabs that way. Leverage those SOs hot-swappable nature and save yourself the pain of recompiling every time you want to test a different skill loadout!
What you’re discussing can be solved in multiple ways, most of which involve polymorphism. You could use the strategy pattern, the command pattern, or other similar approaches. If you need the methods to remain within the player class, you could call a method in the Scriptable Object (SO) that takes the player as a parameter and then invokes the appropriate method on the player. However, I don’t recommend this last approach, using polymorphism is generally the better solution.
None of these methods inherently future-proof anything in your code. They solve certain problems while introducing others, and it takes experience to understand which methods improve specific areas and which might make things worse.
That said, if you’re aiming to write game code, you should already be familiar with polymorphism, it’s a fundamental concept in OOP. My suggestion is to gain experience by learning basic coding principles first. Focus on learning basic C# and OOP outside of Unity, before diving back in.
Instead of tackling a PvE Hero Shooter, akin to a mixture of TF2’s MvM and games like Overwatch or Marvel Rivals, it would be wiser to start with simpler projects, so you can try the above methods in simpler scenarios first. My usual recommendation is to begin with tic-tac-toe, then move on to Tetris, Pac-Man, and finally a basic platformer.
Like others said there is no way to 100% future proof your code, so that you never have to touch your existing abilities again.
But one big thing you can already do now is to prepare abilities that are not “fire and forget”.
In most of the SO tutorials I see simple examples with some kind of abstract “Use” method, where you press a key and logic is triggered and that works fine for “fire and forget” like shooting a raycast to do damage (guns) or starting a projectile.
But in many games you have abilities which you can “hold” so you press the key, the ability activates and then does something over multiple frames while you hold the key and maybe a finish action when you finally release the key. Like a shield or flamethrower or whatever. So instead of a simple “Use” method you suddenly need 3 methos “ActivateAbility”, “TickAbility” and “DeactiveateAbility”. Maybe you also need a way to cancel the action early, for example the player is still holding the key, but you run out of ammo for the active ability.
Which would cause you to touch you previously written “fire and forget” abilities, if you don’t prepare from it from the beginning.
These are the kind of things you should think about before writting big systems like ability system, quest system or whatever. Requirements will still change later, but at least you have a basic foundation.