Which way to choose to design my weapons system?

At this point is more viable to just simple ask about it, so…here I go (it’s a really long post, but I would like explain what I’ve tried so far):

A few months ago, I started designing (and programming) my own weapon system. The approach was this: a system capable of creating from an axe to a submachine gun; extendable and scalable. Yeah, sounds a little extreme. The idea originally came up as just a “simple” system to create any gun I wanted (a pistol, shotgun, submachine, etc), and at that moment I decided to make it possible through Scriptable Objects. I just simple created different data objects, each with different settings, and then select the “weapon profile” I wanted to use.

Since every weapon in my game had the same behavior (shoot, reload, is it empty? does it have bullets left?, etc), I could use one script just to “load” the data from the “weapon profile”, and make the weapon act according to those values (fire rate, magazine limit, bullets left, auto, semi, and some other properties), while executing its respective methods (Shoot, Reload).

That system was good, I really liked it. However, it wasn’t scalable. As the project grew up, the system was getting messier, and harder to work with; to maintain it. Also, it was only limited to guns, or anything that has a fire rate and shoots something, and has limited ammo (a bow, maybe? lol). Also, my requirements changed, I needed to add an axe. So, I had to redesign the entire system.

The next I tried was to create an abstract weapon definition, so I could create different weapon implementations. I designed that based on the next assumption: you can attack and create damage with all weapons, but not all weapons need to be reloaded, nor get empty. We can safely say that all weapons need and Attack() method, but not all of them (like an axe), need a Reload() one.

So, a pistol has the Attack() method, plus the Reload() method. An axe just implements the Attack() one. It seems like the way to go but I was definitely missing something. This system was still using Scriptable Objects for some general properties (fire rate, magazine limit, etc).

I didn’t like it, still. It has pretty much the same issues that the other one. Maybe I just simply made a bad implementation. I honestly believe it was poorly designed since the beginning.

Last approach was Event/Component-based. Pretty sure those aren’t the terms…but anyway. Basically, I separate every task conforming the weapon into many different “behaviors”. Shoot, behavior, raycast behavior, reload behavior, this behavior, that behavior. Working side-by-side with an event system based on Scriptable Objects, I gotta say it was pretty neat. Using Unity Events, there was no necessity to directly reference a lot of stuff into all the behaviors from above. A lot of things could happen simultaneously every time a Unity Event got called (I had Shoot event, Reload event, and ADS event). This system allowed me to practically “build” a weapon, adding or removing certain behaviors, as I pleased. It was kind of modular I guess? This system was also using Scriptable Objects again, for general properties/values (fire rate, etc).

Now, with that system I was able to “build” an axe, and a pistol, and a shotgun, and well, lots of weapons! But…I wasn’t satisfied. It just wasn’t perfect. And apparently it wasn’t just for one thing…

Unity Events, in conjunction with the new input system, were only called once per-input trigger/started/performed. Almost perfect…but because of that flaw, I wasn’t able to made automatic guns. For that, I would need to call the OnShoot event continuously, while I press certain key. I didn’t found any way to achieve that behavior, so my guess is that by design, Unity Events can’t do that. I still think that maybe there is a way to do it, like changing something on the input itself, but before I start to experiment with a lot of random stuff, I decided to just simply come here and ask for some help.

Why? Well, let’s remember that this entire system is based on the assumption: you can attack and create damage with all weapons, but not all weapons need to be reloaded, nor get empty. Perhaps this assumption is the problem and the issue with my designs are merely a logic one.

The next uhhmm…sketches, are a visual guide of what I’m trying to achieve:

My design could be very wrong, and that’s why I need some help. If, theoretically speaking, there’s nothing wrong with my design, then:

  • I already know that I need a “component based” system, something similar to how Unity works. Kind of modular.
  • Those components are basically “behaviors”. Every weapon has different behaviors (event though both axe and pistol can attack, they perform a different attack).

Looking through the internet, I found the strategy pattern (I really liked this[Strategy (refactoring.guru)] post about it). In a reddit post ((1) Which is better for multiple weapons system: have 1 Input per weapon scripts or have multiple/all weapons in 1 script? : Unity3D (reddit.com)), someone in the comments mentions it, and that user precisely uses it for a weapon system.

But, yeah…I honestly find myself quite lost, and I don’t know what decision to make, or rather what decision would be right. Should I stick to my last design? Should I try again with another system?

Keep in mind that I also tried inheritance and interfaces, but I couldn’t figure it out how to apply it to my system.

Thanks in advance!

Call me a stick in the mud, but I’d create a system that works for my game as imagined currently and leave room for hand-scripting something wildly different if it comes up in the design later in the project. Trying to create a generic system that would work for every single game, in every single scenario, is going to leave you with nothing but headaches as you conceptualize more and more radically different designs trying to break it.

I’d use a simple interface to define a weapon with three possible actions. Then create two base classes that use that interface, one for melee and one for projectiles and use those as much as possible. If a radical design came up, I could either subclass again or create a new weapon that uses the interface.

public interface IWeapon
{
   void PrimaryAction();
   void SecondaryAction();
   void TertiaryAction();
}

public class ProjectileWeapon : IWeapon
{
   int maxAmmo;
   int ammo;

   public void PrimaryAction()
   {
       // shoot
       // subtract ammo
   }

   public void SecondaryAction()
   {
       // aim down the sights
   }

   public void TertiaryAction()
   {
       // reload
   }
}

public class MeleeWeapon : IWeapon
{
   public void PrimaryAction()
   {
       // raycast
       // deal damage if true
   }

   public void SecondaryAction()
   {
       // heavy attack?
   }

   public void TertiaryAction()
   {
       // heavier attack? charge attack? nothing?
   }
}

You can go further down the chain, like HitscanGun and Launcher, to have more concrete logic.

I agree with GroZZler that - especially for small projects - making a be all end all isn’t always necessary.

That said, the most reasonable straightforward way I’ve found to make a simple and scalable system with my current experience is as so:

1: Create an Abstract Base Weapon ScriptableObject class - this defines the most basic abstract properties, like weapon name.
2: Child ScriptableObject classes for different broad weapon types (eg: firearm, melee, etc). These will be the scriptableobjects with your CreateAssetMenu attribute.
3: Interfaces to define implemented functionality. This could be functionality such as PrimaryAction, SecondaryAction, etc, like Grozzler suggest. Or it could be more explicit functionality such as Reload, etc. Or both! That comes down to how you structure your logic.
4: A container monobehaviour script on your player or whatever that has a variable for your base Weapon class. That way polymorphism lets you put any child weapon inside, and then your logic can check for interfaces and act accordingly.

You can also reuse the base concept of this system in a number of ways. I use the same kind of implementation for an inventory system in a current project of mine. I have a Base_Item Scriptable object class, all items inherent from this class, and they all implement various interfaces to define what the items can and can’t do (eg: ISellable, ICraftable, IConsumable).

At some point I tried something similar, but I honestly don’t remember why I didn’t keep it. The only thing I’m doing exactly as you’ve described, are the steps 1 and 2 (because I’m using Scriptable Objects). I guess I’ll give it another try

This is pretty much what I did, except that I didn’t use interfaces. Well, I first tried using them. Basically, I created different interfaces, IAttack, IReload, IADS. Again, this is based on my “behaviors” design. A weapon can implement all of those behaviors, as well as only one of them (IAttack). But since every weapon needed to implement IAttack, it was more logical just to make Attack as a base method for every weapon, then, if needed, it could implement IReload and IADS.

I really enjoyed reading this, especially since I’ve put some time into my own FPS controller system lately. But I was thinking that your system would be a lot simpler if you treated ranged and melee weapons separately, instead of trying to unify them.

Totally with you Groz… solve the problem you have today. If you try to solve the problem you think you will have,

a) you won’t have that exact problem
b) it will be a completely unanticipated problem
c) you’ll waste time
d) you’ll make fresh problems just from your pre-problem-solving

I agree with this. I think sometimes there is a tendency to make things “too generic”… I am guilty of it myself. At some point you can go down a rabbit hole… Where every axe and every gun is a Weapon, and now Every weapon is also an item! And every item is also a “Thing” and next thing you know you’re basically writing a class called GameObject… You see where I’m going here. I would keep melee and ranged weapons separate.

The beauty of the component design is that any GameObject can be anything. The Components that a GameObject has define what it is. Here’s an example with a few GameObjects and Components:

Axe GameObject:

  • Axe Component

  • Swing()

  • Weapon Component

  • AttackRate

  • Item Component

  • PickUp()

  • Rigidbody

  • Collider

Pistol GameObject:

  • Gun Component

  • Shoot()

  • Weapon Component

  • Item Component

  • Rigidbody

  • Collider

Stationary Machine Gun GameObject:

  • Gun Component
  • Weapon Component
  • Collider

GameObjects can be as generic or as specific as you want them to be. It all depends on which Components they have and the functionality that those components provide. And if for whatever reason at runtime if you decide to rip off the stationary machine gun Doom style, you can always simply AddComponent.

Yeeeahh…I totally agree on that, honestly. I know it’s way simpler, and I could’ve started from there, but, yeah…

I want to think that I actually learned something valuable from this, and that this time I didn’t waste my time at all. You see, I tend to overthink a lot this kind of things, leading me to create something way more complex than it should be. I have a big problem deciding when things should stay simple, and when they need to be complex.

Oh yeah, that’s precisely what I wanted to follow in my designs.

It’s kinda hard to know when you should stop abstracting things, isn’t it? :smile:

You are the one calling the event, so you can call it every frame if you want. You are only calling it on Input.GetButtonDown or something.

You could have two events, OnShootDown, and OnShootUpdate, called by GetButtonDown and GetButton.

I tried that too. With the old input system, using GetButtonDown or GetButton made the difference, but they don’t exist in the new input system, instead, there’s action.performed, action.triggered, action.started, action.cancelled; Unfortunately, none of them worked. That’s why I thought it’s something by design, not necessarily from UnityEvents, but from the new input system.

What I would do is create my own abstraction of whichever input system you’re using, which keeps track of your input states.

So…keep the weapon design simple and inject steroids to the input system?

Have anyone figured a good way to design the input system? I also want my weapons to have semi, full auto, charge,… but I still haven’t figured out how to use the new input system.

Just get started. Put a stake in the ground. Stop dithering. Make your game.

Here’s how: first list the user intents you need:

  • fire
  • reload
  • switch weapons

Make temp variables for those inputs, then connect SOME kind of input and fill those variables.

NOW… you’re done. Get to work and make your game. That’s really all there is.

If you need more intents, go back and add them in, refactor.

The important part is to MOVE FORWARD.

You will not learn (or do) gamedev by necro-posting to threads from years ago.

When you have an ACTUAL problem, here is:

How to report your problem productively in the Unity3D forums:

http://plbm.com/?p=220

This is the bare minimum of information to report:

  • what you want
  • what you tried
  • what you expected to happen
  • what actually happened, log output, variable values, and especially any errors you see
  • links to documentation you used to cross-check your work (CRITICAL!!!)

If you post a code snippet, ALWAYS USE CODE TAGS:

How to use code tags: https://discussions.unity.com/t/481379