1
\$\begingroup\$

In my game I would like to implement a universal ammo and weapon system that would be usable for any kind of weapon. So I declared an ammo class:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class Ammo {

     public int MaxAmmo;
     public int CurrentAmmo;

     public Ammo(int maxAmmo)
     {
         MaxAmmo = maxAmmo;
     }

     public void MaxOutType()
     {
         CurrentAmmo = MaxAmmo;
     }

     public void GetAmmo(int amount)
     {
         CurrentAmmo += amount;
     }

     public void UseAmmo(int numOf)
     {
         CurrentAmmo -= numOf;
     }
 }

Then I created a script to hold different Ammo types:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AmmoSystem : MonoBehaviour
{
    [SerializeField]
    public Ammo Basic = new Ammo(200);
    public Ammo Rockets = new Ammo(50);
    public Ammo AirRockets = new Ammo(75);
    public Ammo Cells = new Ammo(150);     
 }

To organize my project I decided to make two weapon scripts: one for nonphysical(Raycast) projectiles called RaycastingWeapon and an another for physical ones(like a rocket launcher) called PhysicalProjectileWeapon

My plan was to create unique weapons using these two scripts from the inspector.

These would use different kinds of ammo but would share the same script with different properties. The essential aspect here is that I would like to be able to select an ammo type from the inspector using a dropdown menu for example.

Is it possible somehow or are there a better way of doing that?

\$\endgroup\$
1
  • \$\begingroup\$ I suggest you drop your Ammo class in Code Review. There are some things about it that can be improved to help make your code more readable. \$\endgroup\$
    – Evorlor
    Commented Jul 3, 2017 at 13:59

1 Answer 1

4
\$\begingroup\$

You probably don't actually want your weapon to hold a reference to the instance representing your ammo stock of its type. That makes it very difficult to set up a weapon prefab for a pickup/powerup, or a weapon that can be used by multiple players/NPCs. You'd have to make the weapon a part of the character using it at all times, because it needs this umbilical cord connecting it to its corresponding ammo pouch.

For a real weapon, we don't figure out which ammo it needs by following its pipe to to corresponding ammo box. Instead, the weapon has features on it like a chamber, magazine, or battery slot, that indicate what kind of supply it needs.

We can model this by having the weapon expose an ammo type that it needs, which is matched by an ammo type in our inventory / pickups / etc.

A quick & robust way to create a dropdown in Unity with a fixed set of options like this is to create an enum:

public enum AmmoType : int {
    Basic,
    Rockets,
    AirRockets,
    Cells
}

Then your weapon class can look like...

public class Weapon : MonoBehaviour {

    public AmmoType ammo;
    public int ammoPerShot = 1;

    // Here using dependency injection, so the weapon doesn't need to know where the
    // ammo is coming from - supplying the ammo is the job of whoever is firing it.
    public bool Fire(AmmoInventory ammo) {            
       int shotsFired = ammo.Spend(ammoType, ammoPerShot);
       // Do firing effects...

       return shotsFired > 0;
    }
}

In the Inspector, the ammo dropdown will look like this:

Ammo type dropdown

Next I'll show how we can make an inventory script that automatically updates to changes in your enum if you add, remove, or rearrange ammo types. It looks like this:

Example of inventory editor with named groupings of maxCapacity and stock for each ammo type

We'll make sure it supports things like:

  • Reporting the current amount of ammo carried of a given type
  • Collecting ammo of a particular type (without exceeding a max)
  • Spending ammo of a particular type (without going below zero)

public class AmmoInventory : MonoBehaviour {

    [System.Serializable]
    public struct AmmoEntry {
// Include name field for editing convenience only - not needed in-game.
#if UNITY_EDITOR
        [HideInInspector]
        public string name;
#endif
        public int maxCapacity;
        public int stock;
    }

    // Keep a private list of ammo stocks, so we can enforce capacity rules.
    [SerializeField]
    List<AmmoEntry> _inventory = new List<AmmoEntry>();

    // Since our enum is "really" an integer, we can use it
    // as an index to jump straight to the entry we want.
    public int GetStock(AmmoType type) {
        return _inventory[(int)type].stock;
    }

    // Returns amount collected, so you can choose to not consume
    // pickups if you're already full (ie. return value is zero).
    public int Collect(AmmoType type, int amount) {
        AmmoEntry held = _inventory[(int)type];
        int collect = Mathf.Min(amount, held.maxCapacity - held.stock);
        held.stock += collect;
        _inventory[(int)type] = held;
        return collect;
    }

    // Returns the amount actually spent, in case firing a full round
    // would drop us below 0 ammo, you can scale down the last shot.
    // You could also implement a TrySpend that aborts for insufficient ammo.
    public int Spend(AmmoType type, int amount) {
        AmmoEntry held = _inventory[(int)type];
        int spend = Mathf.Min(amount, held.stock);
        held.stock -= spend;
        _inventory[(int)type] = held;
        return spend;
    }

// Ensure our inventory list always matches the enum in the event of code changes.
// You could also use a custom editor to maintain this more efficiently.
#if UNITY_EDITOR
    void Reset() { OnValidate(); }
    void OnValidate() {
        var ammoNames = System.Enum.GetNames(typeof(AmmoType));
        var inventory = new List<AmmoEntry>(ammoNames.Length);        
        for(int i = 0; i < ammoNames.Length; i++) {
            var existing = _inventory.Find(
                (entry) => { return entry.name == ammoNames[i]; });
            existing.name = ammoNames[i];
            existing.stock = Mathf.Min(existing.stock, existing.maxCapacity);
            inventory.Add(existing);
        }
        _inventory = inventory;
    }
#endif
}
\$\endgroup\$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .