184 lines
5.6 KiB
C#
184 lines
5.6 KiB
C#
using System;
|
|
using UnityEngine;
|
|
|
|
namespace Arena.Combat
|
|
{
|
|
/// <summary>
|
|
/// Weapon that fires projectiles from a muzzle, limited by the fire interval and the ammo count.
|
|
/// Held by a Handle (usually a hand): snaps to it when enabled, then follows the orientation of the Owner
|
|
/// while the Owner is active, otherwise the orientation of the Handle. Can be dropped with physics.
|
|
/// The projectiles ignore the shooter and all its children.
|
|
/// </summary>
|
|
public class ProjectileLauncher : MonoBehaviour
|
|
{
|
|
[System.Serializable]
|
|
public class Settings
|
|
{
|
|
[Tooltip("Weapon data: damage, fire interval, ammo, projectile.")]
|
|
public WeaponData Weapon;
|
|
|
|
[Tooltip("Team hurt by the projectiles: Enemy for the player, Allied for an enemy, All for everyone.")]
|
|
public Team Target = Team.All;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public struct References
|
|
{
|
|
[Tooltip("Spawn point of the projectiles. Its forward axis gives the direction.")]
|
|
public Transform Muzzle;
|
|
|
|
[Tooltip("Shooter: the projectiles never hit it nor its children, and the weapon follows its orientation while it is active. Empty: the GameObject of this launcher.")]
|
|
public GameObject Owner;
|
|
|
|
[Tooltip("Transform that holds the weapon, usually a hand. The weapon takes its position and orientation when enabled.")]
|
|
public Transform Handle;
|
|
|
|
[Tooltip("Rigidbody of the weapon, kinematic while held. Drop makes it fall.")]
|
|
public Rigidbody Body;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class StateContainer
|
|
{
|
|
public int Ammo;
|
|
|
|
[Tooltip("True while an action blocks the shots: running, dash, stun... Set by the owner.")]
|
|
public bool Disabled;
|
|
|
|
public bool IsDropped;
|
|
}
|
|
|
|
#region FIELDS
|
|
|
|
[SerializeField] private Settings _settings;
|
|
[SerializeField] private References _references;
|
|
[SerializeField] private StateContainer _state;
|
|
|
|
/// <summary>
|
|
/// Raised with the current and max ammo each time the ammo changes.
|
|
/// </summary>
|
|
public event Action<int, int> OnAmmoChange;
|
|
|
|
public StateContainer State => _state;
|
|
public Transform Muzzle => _references.Muzzle;
|
|
public int MaxAmmo => _settings.Weapon.MaxAmmo;
|
|
|
|
private float _nextFireTime;
|
|
|
|
#endregion
|
|
|
|
#region UNITY LIFECYCLES
|
|
|
|
private void Awake()
|
|
{
|
|
_state.Ammo = MaxAmmo;
|
|
}
|
|
|
|
void OnEnable()
|
|
{
|
|
Transform handle = _references.Handle;
|
|
|
|
if (handle && !_state.IsDropped)
|
|
transform.SetPositionAndRotation(handle.position, handle.rotation);
|
|
}
|
|
|
|
void LateUpdate()
|
|
{
|
|
// LateUpdate: after the animation has moved the hand
|
|
if (_state.IsDropped)
|
|
return;
|
|
|
|
GameObject owner = _references.Owner;
|
|
|
|
if (owner && owner.activeInHierarchy)
|
|
transform.rotation = owner.transform.rotation;
|
|
else if (_references.Handle)
|
|
transform.rotation = _references.Handle.rotation;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PUBLIC METHODS
|
|
|
|
/// <summary>
|
|
/// Fires one projectile if the weapon is ready and has ammo. Returns true if a shot was fired.
|
|
/// </summary>
|
|
public bool TryFire()
|
|
{
|
|
if (_state.Disabled || _state.IsDropped || Time.time < _nextFireTime || _state.Ammo <= 0)
|
|
return false;
|
|
|
|
_nextFireTime = Time.time + _settings.Weapon.FireInterval;
|
|
|
|
SetAmmo(_state.Ammo - 1);
|
|
|
|
Transform muzzle = _references.Muzzle;
|
|
Projectile projectile = Instantiate(_settings.Weapon.ProjectilePrefab, muzzle.position, muzzle.rotation);
|
|
GameObject owner = _references.Owner ? _references.Owner : gameObject;
|
|
projectile.Launch(new DamageInfo(_settings.Weapon.Damage, _settings.Weapon.DamageType, owner, _settings.Target));
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Blocks or allows the shots. Called by the owner when an action prevents shooting.
|
|
/// </summary>
|
|
public void SetDisabled(bool isDisabled)
|
|
{
|
|
_state.Disabled = isDisabled;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lets go of the weapon: it leaves its handle and falls with physics. It can no longer fire.
|
|
/// </summary>
|
|
public void Drop()
|
|
{
|
|
if (_state.IsDropped)
|
|
return;
|
|
|
|
_state.IsDropped = true;
|
|
_state.Disabled = true;
|
|
transform.SetParent(null, true);
|
|
|
|
Rigidbody body = _references.Body;
|
|
|
|
if (!body)
|
|
{
|
|
Debug.LogWarning($"{name}: no Rigidbody referenced, the weapon cannot fall.", this);
|
|
return;
|
|
}
|
|
|
|
body.isKinematic = false;
|
|
body.useGravity = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds ammo without exceeding the weapon maximum.
|
|
/// </summary>
|
|
public void AddAmmo(int amount)
|
|
{
|
|
if (amount <= 0)
|
|
return;
|
|
|
|
SetAmmo(Mathf.Min(_state.Ammo + amount, MaxAmmo));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PRIVATE METHODS
|
|
|
|
private void SetAmmo(int value)
|
|
{
|
|
_state.Ammo = value;
|
|
OnAmmoChange?.Invoke(_state.Ammo, MaxAmmo);
|
|
}
|
|
|
|
[ContextMenu("Debug/Try Fire")]
|
|
private void DebugTryFire()
|
|
{
|
|
TryFire();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|