Thursday, September 19, 2019

c# - How to link my weapons with their corresponding ammo supply in the Unity Inspector?


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?



Answer




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 _inventory = new List();

// 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(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
}

No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...