Want more features? Check out the with advanced scriptableobject weapon system with combo chains, weapon switching, damage types, critical hits, and upgrade modifier slots.
Part of these game systems:
intermediate Health & Combat

Weapon System

ScriptableObject-driven weapon system with melee and ranged attacks, cooldowns, ammo, and damage calculation.

Unity 2022.3+ · 3.8 KB · WeaponSystem.cs

How to Use

1

Create weapon ScriptableObjects: Assets > Create > Weapons > Weapon Data

2

Configure melee (range, angle) or ranged (projectile, ammo)

3

Attach WeaponSystem to your player

4

Assign attack point Transform (muzzle or hand)

5

Set initial weapon or call EquipWeapon() at runtime

6

Left-click to attack (configurable)

7

Hook OnAmmoChanged to UI, OnAttack to animation/SFX

Features

  • ScriptableObject weapon definitions created via Assets menu
  • Supports both melee (range + angle cone) and ranged (projectile) attack types
  • Attack rate cooldown and ammo management with reload
  • Melee uses OverlapSphere with angle check for arc-based hits
  • UnityEvents for equip, attack, ammo change, and out-of-ammo
  • Editor gizmo visualization for melee attack range

When to Use This

Ideal for RPGs, hack-and-slash games, and FPS titles that need swappable weapons with different behaviors. The ScriptableObject approach lets designers create and tweak weapons without touching code. Pair with Health System for damage dealing and Projectile System for ranged attacks.

Common Mistakes

Don't forget to assign the attackPoint Transform — it defaults to the object's own transform, which may not match your muzzle or hand position. For ranged weapons, the projectilePrefab must have a Projectile component attached. The meleeAngle is the full arc (90 means 45 degrees on each side of forward), and meleeHitLayers must include enemy layers.

Source Code

WeaponSystem.cs
C#
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// ScriptableObject weapon definition. Create weapons via Assets menu.
/// </summary>
[CreateAssetMenu(menuName = "Weapons/Weapon Data")]
public class WeaponData : ScriptableObject
{
    [Header("Info")]
    public string weaponName = "Weapon";
    public Sprite icon;

    [Header("Type")]
    public bool isMelee = true;

    [Header("Damage")]
    public float damage = 10f;
    public float attackRate = 1f; // attacks per second

    [Header("Melee")]
    public float meleeRange = 2f;
    public float meleeAngle = 90f;
    public LayerMask meleeHitLayers = ~0;

    [Header("Ranged")]
    public GameObject projectilePrefab;
    public float projectileSpeed = 20f;
    public int maxAmmo = 30;
    public int ammoPerShot = 1;
}

/// <summary>
/// Weapon controller. Handles attacking, cooldowns, and ammo.
/// Attach to the player or weapon holder.
/// </summary>
public class WeaponSystem : MonoBehaviour
{
    [Header("Current Weapon")]
    [SerializeField] private WeaponData currentWeapon;

    [Header("References")]
    [SerializeField] private Transform attackPoint;

    [Header("Events")]
    public UnityEvent<WeaponData> OnWeaponEquipped;
    public UnityEvent OnAttack;
    public UnityEvent<int, int> OnAmmoChanged; // current, max
    public UnityEvent OnOutOfAmmo;

    private float cooldownTimer;
    private int currentAmmo;

    /// <summary>Currently equipped weapon data.</summary>
    public WeaponData CurrentWeapon => currentWeapon;

    /// <summary>Current ammo count (ranged only).</summary>
    public int CurrentAmmo => currentAmmo;

    private void Start()
    {
        if (currentWeapon != null)
            EquipWeapon(currentWeapon);
    }

    private void Update()
    {
        if (cooldownTimer > 0f)
            cooldownTimer -= Time.deltaTime;

        if (Input.GetMouseButton(0) && cooldownTimer <= 0f)
        {
            Attack();
        }
    }

    /// <summary>
    /// Equip a weapon by setting its data.
    /// </summary>
    public void EquipWeapon(WeaponData weapon)
    {
        currentWeapon = weapon;

        if (!weapon.isMelee)
        {
            currentAmmo = weapon.maxAmmo;
            OnAmmoChanged?.Invoke(currentAmmo, weapon.maxAmmo);
        }

        OnWeaponEquipped?.Invoke(weapon);
    }

    /// <summary>
    /// Perform an attack with the current weapon.
    /// </summary>
    public void Attack()
    {
        if (currentWeapon == null || cooldownTimer > 0f) return;

        if (currentWeapon.isMelee)
            MeleeAttack();
        else
            RangedAttack();

        cooldownTimer = 1f / currentWeapon.attackRate;
        OnAttack?.Invoke();
    }

    private void MeleeAttack()
    {
        Transform origin = attackPoint != null ? attackPoint : transform;
        Collider[] hits = Physics.OverlapSphere(origin.position, currentWeapon.meleeRange, currentWeapon.meleeHitLayers);

        foreach (var hit in hits)
        {
            if (hit.gameObject == gameObject) continue;

            // Angle check
            Vector3 dirToTarget = (hit.transform.position - origin.position).normalized;
            float angle = Vector3.Angle(origin.forward, dirToTarget);

            if (angle <= currentWeapon.meleeAngle * 0.5f)
            {
                HealthSystem health = hit.GetComponent<HealthSystem>();
                if (health != null)
                    health.TakeDamage(currentWeapon.damage);
            }
        }
    }

    private void RangedAttack()
    {
        if (currentAmmo < currentWeapon.ammoPerShot)
        {
            OnOutOfAmmo?.Invoke();
            return;
        }

        currentAmmo -= currentWeapon.ammoPerShot;
        OnAmmoChanged?.Invoke(currentAmmo, currentWeapon.maxAmmo);

        if (currentWeapon.projectilePrefab != null)
        {
            Transform origin = attackPoint != null ? attackPoint : transform;
            GameObject proj = Instantiate(
                currentWeapon.projectilePrefab,
                origin.position,
                origin.rotation
            );

            Projectile projectile = proj.GetComponent<Projectile>();
            if (projectile != null)
            {
                // Projectile handles its own speed from its own settings
            }
        }
    }

    /// <summary>Add ammo to the current weapon.</summary>
    public void AddAmmo(int amount)
    {
        if (currentWeapon == null || currentWeapon.isMelee) return;
        currentAmmo = Mathf.Min(currentAmmo + amount, currentWeapon.maxAmmo);
        OnAmmoChanged?.Invoke(currentAmmo, currentWeapon.maxAmmo);
    }

    /// <summary>Reload to max ammo.</summary>
    public void Reload()
    {
        if (currentWeapon == null || currentWeapon.isMelee) return;
        currentAmmo = currentWeapon.maxAmmo;
        OnAmmoChanged?.Invoke(currentAmmo, currentWeapon.maxAmmo);
    }

#if UNITY_EDITOR
    private void OnDrawGizmosSelected()
    {
        if (currentWeapon == null || !currentWeapon.isMelee) return;
        Transform origin = attackPoint != null ? attackPoint : transform;
        Gizmos.color = new Color(1f, 0.3f, 0.3f, 0.4f);
        Gizmos.DrawWireSphere(origin.position, currentWeapon.meleeRange);
    }
#endif
}
Ready for more? Weapon System Pro Advanced ScriptableObject weapon system with combo chains, weapon switching, damage types, critical hits, and upgrade modifier slots.