Weapon Parent File

The script weapon is what every gun used by the player in the game will inherit from. This script contains variables that control the weapon such as fire rate, ammo, damage, whether the player can reload or shoot the gun as well as having a reference to the fire point of the weapon (where the bullet will be instantiated from e.g. the barrel) and finally the sounds for the gun shooting and reloading.

public class Weapon : MonoBehaviour
{
    [SerializeField] protected float fireRate;
    [SerializeField] public float MaxAmmo;
    [SerializeField] protected float ReloadTime;
    //[SerializeField] protected float CoolDownRate;
    [SerializeField] public float CurrentAmmo;
    [SerializeField] protected float Damage;
    [SerializeField] protected bool Reloading;
    [SerializeField] protected bool IsCoolingDown;
    [SerializeField] public bool CanShoot;

    [SerializeField] protected Transform FirePoint;

    protected AudioSource source;
    [SerializeField] protected AudioClip ShootingSound;
    [SerializeField] protected AudioClip ReloadingSound;

    protected void InstatiatePistolAmmo(Bullet ammo) {
        ammo.transform.rotation = FirePoint.rotation;
        Instantiate(ammo, FirePoint.position, FirePoint.rotation);
        ammo.target = GunRotate.Instance.CrossHair.transform.position;
    }
}

Pistol

This is derived from the previously mentioned weapon class, when the player press left click on the mouse, if the gun currently has ammo, CanShoot is true, reloading is false and the player is not shooting then the coroutine FireRate is started this instatiates a bullet from the firepoint of the weapon, plays the appropriate sounds and waits the amount of time set in fireRate, once this time has been elapsed CanShoot is set to true meaning the player can shoot once again. If the current ammo of the gun is zero or the player presses R when the gun has less than max ammo then the gun will be reloaded and the appropriate sounds and animations will also play.

public class Pistol : Weapon {

    [SerializeField] private GameObject ammo;

    private void Start() {
        CurrentAmmo = MaxAmmo;
        Reloading = false;
        CanShoot = true;
        source = this.GetComponent<AudioSource>();
    }

    private void Update() {
        if(CurrentAmmo > MaxAmmo) {
            CurrentAmmo = MaxAmmo;
        }

        if (Input.GetKeyDown(KeyCode.Mouse0) && CurrentAmmo > 0 && CurrentAmmo <= MaxAmmo && CanShoot == true && Reloading == false && PlayerController.Instance.isDashing == false) {
            Debug.Log("Shooting");
            Debug.Log("Current Ammo " + CurrentAmmo);
            CurrentAmmo--;
            StartCoroutine("FireRate");      
        } else if (CurrentAmmo <= 0 && Reloading == false) {
            Debug.Log("Reloading...");
            StartCoroutine(Reload());
        }

        if(Input.GetKeyDown(KeyCode.R) && CurrentAmmo < MaxAmmo && CurrentAmmo != 0 && Reloading == false) {
            StartCoroutine(Reload());
        }
    }

    /// <summary>
    /// This shoots the bullets out the gun but also is the fire rate of the gun
    /// </summary>
    /// <returns></returns>
    IEnumerator FireRate() {
        CanShoot = false;
        source.clip = ShootingSound;
        source.Play();

        GameObject Bullet = Instantiate(ammo, FirePoint.position, FirePoint.rotation);
        Bullet.GetComponent<Rigidbody2D>().AddForce(FirePoint.right * 15, ForceMode2D.Impulse);

        yield return new WaitForSeconds(fireRate);
        CanShoot = true;
    }

    IEnumerator Reload() {   
        Reloading = true;
        CanShoot = false;
        yield return new WaitForSeconds(1.0f);
        source.clip = ReloadingSound;
        source.Play();
        this.gameObject.GetComponent<SpriteRenderer>().color = Color.red;
        yield return new WaitForSeconds(ReloadTime - 1.0f);
        this.gameObject.GetComponent<SpriteRenderer>().color = Color.white;
        CurrentAmmo = MaxAmmo;
        Reloading = false;
        CanShoot = true;
    }

Shotgun

This script works exactly the same as the pistol script but the only difference is due to this being a shotgun there needs to be spread on the bullets meaning multiple bullets are instantiated at once. This works by first defining a int called NumberOfProjectiles which is how many bullets we want to spawn in the spread, from here this is looped and a new rotation is placed on the z axis each loop which makes it appear the the bullets are being shot in a spread.

public class Shotgun : Weapon
{
    [SerializeField] private GameObject ammo;
    private float SpreadAngle = 20f;
    private int NumberOfProjectiles = 6;
    [SerializeField] private float range;

    private void Start() {
        CurrentAmmo = MaxAmmo;
        Reloading = false;
        CanShoot = true;
        source = this.GetComponent<AudioSource>();
    }

    private void Update() {
        if (CurrentAmmo > MaxAmmo) {
            CurrentAmmo = MaxAmmo;
        }

        if (Input.GetKeyDown(KeyCode.Mouse0) && CurrentAmmo > 0 && CurrentAmmo <= MaxAmmo && CanShoot == true && Reloading == false && PlayerController.Instance.isDashing == false) {
            Debug.Log("Shooting");
            Debug.Log("Current Ammo " + CurrentAmmo);
            CurrentAmmo--;
            StartCoroutine("FireRate");
        } else if (CurrentAmmo <= 0 && Reloading == false) {
            Debug.Log("Reloading...");
            StartCoroutine(Reload());
        }

        if (Input.GetKeyDown(KeyCode.R) && CurrentAmmo < MaxAmmo && CurrentAmmo != 0 && Reloading == false) {
            StartCoroutine(Reload());
        }
    }

    IEnumerator FireRate() {
        CanShoot = false;

        source.clip = ShootingSound;
        source.Play();
        yield return new WaitForSeconds(0.25f);
        for (int i = 0; i < NumberOfProjectiles; i++) {

            float angleStep = SpreadAngle / NumberOfProjectiles;
            float aimingAngle = FirePoint.rotation.eulerAngles.z;
            float centeringOffset = (SpreadAngle / 2) - (angleStep / 2);

            float currentBulletAngle = angleStep * i;

            Quaternion rotation = Quaternion.Euler(new Vector3(0, 0, aimingAngle + currentBulletAngle - centeringOffset));
            GameObject bullet = Instantiate(ammo, FirePoint.transform.position, rotation);

            Rigidbody2D rb = bullet.GetComponent<Rigidbody2D>();
            rb.AddForce(bullet.transform.right * 15, ForceMode2D.Impulse);
        }

        yield return new WaitForSeconds(fireRate - 0.25f);
        CanShoot = true;
    }

    IEnumerator Reload() {
        
        Reloading = true;
        CanShoot = false;
        yield return new WaitForSeconds(1.0f);
        source.clip = ReloadingSound;
        source.Play();
        this.gameObject.GetComponent<SpriteRenderer>().color = Color.red;
        yield return new WaitForSeconds(ReloadTime - 1.0f);
        this.gameObject.GetComponent<SpriteRenderer>().color = Color.white;
        CurrentAmmo = MaxAmmo;
        Reloading = false;
        CanShoot = true;
    }

Bullets

This bullet is attached to the bullet prefab and once the bullet is instantiated and tracks how long the bullet has been alive and also tracks the collisions of the bullet, if it enters a trigger with the enemy, it will do damage to the enemy and then destroy it self but if it comes into contact with a wall the bullet will be destroyed.

public class Bullet : MonoBehaviour
{
    [SerializeField] protected float BulletSpeed; // how fast the bullet travels
    private Rigidbody2D rigidBody;
    [SerializeField] protected float LiveTime; // time before it is destroyed
    [SerializeField] public Vector3 target;
    [SerializeField] private int Damage; // damage it will do to a target

    private void Start() {
        rigidBody = GetComponent<Rigidbody2D>();
    }

    private void Update() {
        LiveTime -= Time.deltaTime;
        if(LiveTime <= 0f) {
            Destroy(gameObject);
        }
    }

    private void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.CompareTag("Enemy")) {
            collision.gameObject.GetComponent<Enemy>().Health -= Damage;
            Destroy(this.gameObject);
        }

        if (collision.gameObject.CompareTag("Wall")) {
            Destroy(this.gameObject);
        }
    }

Weapon Manager

This script was responsible for tracking the weapons of the player, this script knew which weapons the player had picked up as well as allowing the player to switch to weapons using the number keys on the keyboard.

public class WeaponManager : MonoBehaviour
{
    public static WeaponManager Instance { get; private set; }

    public GameObject[] OneHandedWeapos;

    public List<GameObject> Weapons = new List<GameObject>();

    [SerializeField] public GameObject CurrentlyEquippedWeapon;

    [SerializeField] private Image WeaponUI;
    [SerializeField] private Sprite PistolUI;
    [SerializeField] private Sprite ShotgunUI;
    [SerializeField] private TextMeshProUGUI AmmoCounter;
    

    private void Awake() {

        if (Instance != null && Instance != this) {
            Destroy(gameObject);
        }

        Instance = this;

        DontDestroyOnLoad(this);
    }

    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if(CurrentlyEquippedWeapon == null) {
            WeaponUI.gameObject.SetActive(false);
        } else {
            WeaponUI.gameObject.SetActive(true);
        }
        if (CurrentlyEquippedWeapon != null) {
            AmmoCounter.text = CurrentlyEquippedWeapon.GetComponent<Weapon>().CurrentAmmo + " / " + CurrentlyEquippedWeapon.GetComponent<Weapon>().MaxAmmo;
        }
            

        if (Input.GetKeyDown(KeyCode.Alpha1)) {
            if (CurrentlyEquippedWeapon != null) {
                CurrentlyEquippedWeapon.SetActive(false);
            }    
            Weapons[0].SetActive(true);
            PlayerController.Instance.SetOneHandedWeapon(OneHandedWeapons.PISTOL);
            PlayerController.Instance.UpdatePlayerOneHandedWeapon();
            CurrentlyEquippedWeapon = Weapons[0];
            CurrentlyEquippedWeapon.GetComponent<Pistol>().CanShoot = true;
            WeaponUI.sprite = PistolUI; 
        } else if (Input.GetKeyDown(KeyCode.Alpha2)) {
            if (CurrentlyEquippedWeapon != null) {
                CurrentlyEquippedWeapon.SetActive(false);
            }
            Weapons[1].SetActive(true);
            CurrentlyEquippedWeapon = Weapons[1];
            PlayerController.Instance.SetOneHandedWeapon(OneHandedWeapons.PISTOL);
            PlayerController.Instance.UpdatePlayerOneHandedWeapon();
            CurrentlyEquippedWeapon.GetComponent<Shotgun>().CanShoot = true;
            WeaponUI.sprite = ShotgunUI;

        }
    }

    public void EquipOneHandedWeapon(GameObject weapon) {
        if(CurrentlyEquippedWeapon != null) {
            CurrentlyEquippedWeapon.SetActive(false);
            CurrentlyEquippedWeapon = null;
        }

        weapon.SetActive(true);
        if(weapon.name == "Pistol") {
            PlayerController.Instance.SetOneHandedWeapon(OneHandedWeapons.PISTOL);
        }
        PlayerController.Instance.UpdatePlayerOneHandedWeapon();
        CurrentlyEquippedWeapon = weapon;
    }
}