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:
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:
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
}