Using ScriptableObjects to build a simple gun system

I’m quite new to Unity, but not new to programming. For the demo of the game I’d like to create, I’d want to make 2 different characters, one that deals with raycast/hitscan guns, and the other works with projectile guns (grenades, etc.). The primary mechanic of the demo I plan to build deals a lot with upgrading these default guns until they functionally feel like new weapons. I’ve been doing a ton of reading/listening/watching about this sort of task and it seems like ScriptableObjects seems to be the way to approach this. Because of my newness to Unity, I am very confused on how I’d set such a system up. This is what I had in mind:

  • WeaponData : ScriptableObject → fire rate, damage, etc.

  • RaycastWeaponData : WeaponData

  • ProjectileWeaponData : WeaponData → projectile prefab reference, set projectile speed, etc.

  • WeaponUpgradeData : ScriptableObject

  • FireRateUpgrade : WeaponUpgradeData

  • etc…

And then for the MonoBehaviors:

  • WeaponBase : MonoBehavior → (OnPrimaryFire(), OnAltFire(), Reload(), etc.)

  • WeaponGrenadeLauncher : WeaponBase

  • WeaponRifle

and each MonoBehavior class would grab data from the respective ScriptableObjects to set up the gun. Does this sound like a correct implementation of ScriptableObjects, and are there better ways to build a weapon system like this? I feel like I’m overthinking this, but I’m not entirely sure. If anyone has any suggestions on how to make this more robust or even simplify it a bit more, I would love to know. Sadly, I can’t find really any examples about using this sort of system on a gun system anywhere, and I feel like I’ve looked everywhere!

Using Scriptable Objects for holding data is good approach imo.
The thing I would thought about is using inheritance for your weapons. Maybe if you are willing to use flat hierachy with WeaponBase and many derived classes deriving only WeaponBase, then this is ok approach, but if you are going to make deep inheritance hierachies like WeaponBase → BulletWeapon → Rifle/Pistol, then it will probably be a mess.

And I wouldn’t use inheritance for data too. If you do that, you will usually end up having data in derived class which is not used. Just creat many structs with things which 100% will be used together for example Damage(float Value, float CriticalValue) and compose bigger structs from them. If you need to handle some data types as one thing, then add interface(s) which will have properties returning fields.

Avoid inheritance, use composition, it will make your life easier, because you will be able to create Rocket-Launcher with Raycast Alt-Fire for example and wouldn’t think from which class you need to derive or which data you should use. You will have class which uses only things they need.
It will be probably a bit more code, because your classes won’t get fields or methods magically from base class, but result will be less complex and more flexible in terms of architecture.

I did something similar but I only had one Weapon MonoBehavior and used ScriptableObjects for the weapon “Fire Mode logic” (Raycast, Projectile, etc.), Im not sure if FireMode is the best name for it, but eh.

Btw I would usually use interfaces for this, but you can’t drag & drop them in the Unity editor, so I used abstract ScriptableObject instead

The abstract FireMode SO had a StartFire(Weapon), UpdateFire(Weapon, float deltaTime), StopFire(Weapon)
I guess you could skip Start/Update/Stop and use just one Fire(Weapon) method if you only care about single shot weapons, but I had weapons with automatic gun behavior, so I used start/update/finish.

The specific logic (for example Raycast) would have it’s on SO class which implement these abstracts methods.

The Weapon had a reference to one of the FireMode SO and the Weapon had similar StartFire/UpdateFire/StopFire methods which do nothing else then call the method from the current FireMode and pass itself to the method.

The important part is that the FireMode SO are shared by multiple weapons, you should not store any weapon specific state data in the FireMode itself, instead the state data (like cooldown, bullet count) should be stored on the Weapon itself and the FireMode needs a way to interact with these data, for example a ConsumeBullet(int count) method, this is also the reason why the Weapon passes itself to the SO methods.

So the Weapon itself had almost no logic and you could also change the FireMode at runtime.

One last hint, don’t try to find the “best” or “correct” solution, you could implement these kind of things in so many different ways and all of them could work, the more you search, the more different implementations you will find, so my tip is to just pick one you like and then start implementing and only search for a different or “better” solution if you notice that your current implementation has some limitation.

2 Likes

This approach made the concept click with me, after reading this I actually threw something together very quickly and couldn’t believe how simple it was after how much I confused myself last night, haha. Thank you so very much!

I really appreciate your advice about finding the “best” solution. For some reason, I’ve developed this sort of perfectionism when programming that’s really detrimental to my abilities, but I’m working on getting through it :slight_smile:

I have one last question for you, if you do not mind. As far as handling the Player with the weapon, what approach did you take? I was thinking about making a PlayerShooting MonoBehavior and attaching it to the Player GameObject. I would get a reference to the Weapon MonoBehavior, and then just handle input, instantiate the weapon, etc. from the PlayerShooting class. This seems as concise as that can get to me, but I just wanted to verify with you.

Thank you so much again for your advice! :slight_smile: