Part of these game systems:
Weapon System
ScriptableObject-driven weapon system with melee and ranged attacks, cooldowns, ammo, and damage calculation.
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
Source Code
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
}