Starter
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
using UnityEngine;
|
||||
using Arena.Characters;
|
||||
|
||||
namespace Arena.Interactions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gives ammo to the player. Apply is meant to be linked to a PlayerEvent.
|
||||
/// </summary>
|
||||
public class AmmoEffect : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Settings
|
||||
{
|
||||
[Tooltip("Ammo given to the player.")]
|
||||
[Min(1)] public int Amount = 10;
|
||||
}
|
||||
|
||||
#region Fields
|
||||
|
||||
[SerializeField] private Settings _settings;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void Apply()
|
||||
{
|
||||
if (Player.Instance != null)
|
||||
Player.Instance.Launcher.AddAmmo(_settings.Amount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3831749972a75dc4dbe47f8a551785fc
|
||||
26
Assets/_Content/Scripts/Combat/DamageInfo.cs
Normal file
26
Assets/_Content/Scripts/Combat/DamageInfo.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Arena.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// Everything a hit carries: amount, type and source. Passed to IDamageable.TakeDamage.
|
||||
/// </summary>
|
||||
public readonly struct DamageInfo
|
||||
{
|
||||
public readonly int Amount;
|
||||
|
||||
public readonly DamageType Type;
|
||||
|
||||
/// <summary>
|
||||
/// Object that dealt the damage: shooter, damage zone... Can be null.
|
||||
/// </summary>
|
||||
public readonly GameObject Source;
|
||||
|
||||
public DamageInfo(int amount, DamageType type, GameObject source = null)
|
||||
{
|
||||
Amount = amount;
|
||||
Type = type;
|
||||
Source = source;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/_Content/Scripts/Combat/DamageInfo.cs.meta
Normal file
2
Assets/_Content/Scripts/Combat/DamageInfo.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2876a0af3a1b47e4dbd254a5fdb33f9f
|
||||
12
Assets/_Content/Scripts/Combat/DamageType.cs
Normal file
12
Assets/_Content/Scripts/Combat/DamageType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Arena.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// Nature of a damage, carried by a DamageInfo.
|
||||
/// </summary>
|
||||
public enum DamageType
|
||||
{
|
||||
Physical,
|
||||
Energy,
|
||||
Digital
|
||||
}
|
||||
}
|
||||
2
Assets/_Content/Scripts/Combat/DamageType.cs.meta
Normal file
2
Assets/_Content/Scripts/Combat/DamageType.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f2a3b56adabf304a994febc81bd5489
|
||||
171
Assets/_Content/Scripts/Combat/DamageZone.cs
Normal file
171
Assets/_Content/Scripts/Combat/DamageZone.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Arena.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// Sphere that damages every IDamageable inside it, less and less towards its edge.
|
||||
/// Continuous: hits every tick while a target stays inside (contact damage, toxic area).
|
||||
/// Instant: hits everything in the radius once, when Explode is called (explosion).
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(SphereCollider), typeof(Rigidbody))]
|
||||
public class DamageZone : MonoBehaviour
|
||||
{
|
||||
public enum Mode
|
||||
{
|
||||
Continuous,
|
||||
Instant
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class Settings
|
||||
{
|
||||
[Tooltip("Continuous: damage every tick while a target stays inside. Instant: one hit on everything in the radius, when Explode is called.")]
|
||||
public Mode Mode;
|
||||
|
||||
[Tooltip("Damage at the center of the sphere.")]
|
||||
[Min(0)] public int Damage = 10;
|
||||
|
||||
public DamageType DamageType;
|
||||
|
||||
[Tooltip("Damage multiplier by distance to the center. 0: center, 1: edge of the sphere.")]
|
||||
public AnimationCurve Falloff = AnimationCurve.Linear(0f, 1f, 1f, 0f);
|
||||
|
||||
[Tooltip("Continuous mode: time between two hits on the same target, in seconds.")]
|
||||
[Min(0.05f)] public float TickInterval = 0.5f;
|
||||
|
||||
[Tooltip("Instant mode: explode as soon as the object is enabled, for an explosion prefab.")]
|
||||
public bool ExplodeOnEnable = true;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private struct References
|
||||
{
|
||||
public SphereCollider Zone;
|
||||
|
||||
[Tooltip("Never hurt by the zone and given as damage source, usually the object that carries it.")]
|
||||
public GameObject Owner;
|
||||
}
|
||||
|
||||
#region FIELDS
|
||||
|
||||
[SerializeField] private Settings _settings;
|
||||
[SerializeField] private References _references;
|
||||
|
||||
private readonly Dictionary<IDamageable, float> _nextHitTimes = new Dictionary<IDamageable, float>();
|
||||
private readonly HashSet<IDamageable> _explosionTargets = new HashSet<IDamageable>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region UNITY LIFECYCLES
|
||||
|
||||
void Reset()
|
||||
{
|
||||
// Called when the component is added: a trigger sphere that physics can detect
|
||||
_references.Zone = GetComponent<SphereCollider>();
|
||||
_references.Zone.isTrigger = true;
|
||||
GetComponent<Rigidbody>().isKinematic = true;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (_settings.Mode == Mode.Instant && _settings.ExplodeOnEnable)
|
||||
Explode();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
_nextHitTimes.Clear();
|
||||
}
|
||||
|
||||
void OnTriggerStay(Collider other)
|
||||
{
|
||||
if (_settings.Mode != Mode.Continuous || !TryGetTarget(other, out IDamageable target))
|
||||
return;
|
||||
|
||||
if (_nextHitTimes.TryGetValue(target, out float nextHitTime) && Time.time < nextHitTime)
|
||||
return;
|
||||
|
||||
_nextHitTimes[target] = Time.time + _settings.TickInterval;
|
||||
Hit(target, other.transform.position);
|
||||
}
|
||||
|
||||
void OnTriggerExit(Collider other)
|
||||
{
|
||||
if (TryGetTarget(other, out IDamageable target))
|
||||
_nextHitTimes.Remove(target);
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!_references.Zone)
|
||||
return;
|
||||
|
||||
Gizmos.color = new Color(1f, 0.45f, 0.35f, 0.8f);
|
||||
Gizmos.DrawWireSphere(GetCenter(), GetRadius());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
/// <summary>
|
||||
/// Hits once every IDamageable in the radius. Can be linked to a UnityEvent.
|
||||
/// </summary>
|
||||
public void Explode()
|
||||
{
|
||||
_explosionTargets.Clear();
|
||||
Collider[] colliders = Physics.OverlapSphere(GetCenter(), GetRadius(), ~0, QueryTriggerInteraction.Ignore);
|
||||
|
||||
foreach (Collider other in colliders)
|
||||
{
|
||||
// A target with several colliders is hit only once
|
||||
if (TryGetTarget(other, out IDamageable target) && _explosionTargets.Add(target))
|
||||
Hit(target, other.transform.position);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private bool TryGetTarget(Collider other, out IDamageable target)
|
||||
{
|
||||
target = other.GetComponentInParent<IDamageable>();
|
||||
|
||||
if (target == null)
|
||||
return false;
|
||||
|
||||
// The owner never hurts itself
|
||||
return !_references.Owner || ((Component)target).gameObject != _references.Owner;
|
||||
}
|
||||
|
||||
private void Hit(IDamageable target, Vector3 targetPosition)
|
||||
{
|
||||
float distance = Vector3.Distance(GetCenter(), targetPosition);
|
||||
float falloff = _settings.Falloff.Evaluate(Mathf.Clamp01(distance / GetRadius()));
|
||||
int amount = Mathf.RoundToInt(_settings.Damage * falloff);
|
||||
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
GameObject source = _references.Owner ? _references.Owner : gameObject;
|
||||
target.TakeDamage(new DamageInfo(amount, _settings.DamageType, source));
|
||||
}
|
||||
|
||||
private Vector3 GetCenter()
|
||||
{
|
||||
return transform.TransformPoint(_references.Zone.center);
|
||||
}
|
||||
|
||||
private float GetRadius()
|
||||
{
|
||||
// The sphere radius follows the largest scale axis
|
||||
Vector3 scale = transform.lossyScale;
|
||||
float maxScale = Mathf.Max(Mathf.Abs(scale.x), Mathf.Abs(scale.y), Mathf.Abs(scale.z));
|
||||
return Mathf.Max(_references.Zone.radius * maxScale, 0.01f);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
2
Assets/_Content/Scripts/Combat/DamageZone.cs.meta
Normal file
2
Assets/_Content/Scripts/Combat/DamageZone.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86310e0cb69dcf54cb7343a4e704cb12
|
||||
79
Assets/_Content/Scripts/Combat/Destructible.cs
Normal file
79
Assets/_Content/Scripts/Combat/Destructible.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Arena.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// Object destroyed when its health points reach zero: crate, barrel, turret...
|
||||
/// Raises OnDestroyed, spawns an optional prefab (debris, explosion with an instant DamageZone), then disappears.
|
||||
/// </summary>
|
||||
public class Destructible : MonoBehaviour, IDamageable
|
||||
{
|
||||
[System.Serializable]
|
||||
private class Settings
|
||||
{
|
||||
[Tooltip("Optional prefab spawned in place of the object: debris, explosion...")]
|
||||
public GameObject SpawnOnDestroyed;
|
||||
}
|
||||
|
||||
#region FIELDS
|
||||
|
||||
[SerializeField] private Settings _settings;
|
||||
[SerializeField] private Health _health;
|
||||
|
||||
/// <summary>
|
||||
/// Raised once, when the object is destroyed by damage.
|
||||
/// </summary>
|
||||
public event Action<Destructible> OnDestroyed;
|
||||
|
||||
public Health Health => _health;
|
||||
|
||||
#endregion
|
||||
|
||||
#region UNITY LIFECYCLES
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_health.Initialize();
|
||||
_health.OnDied += HandleDied;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
_health.OnDied -= HandleDied;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public void TakeDamage(DamageInfo damage)
|
||||
{
|
||||
_health.TakeDamage(damage);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private void HandleDied()
|
||||
{
|
||||
OnDestroyed?.Invoke(this);
|
||||
|
||||
if (_settings.SpawnOnDestroyed)
|
||||
Instantiate(_settings.SpawnOnDestroyed, transform.position, transform.rotation);
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[ContextMenu("Debug/Take 25 Physical Damage")]
|
||||
private void DebugTakeDamage()
|
||||
{
|
||||
TakeDamage(new DamageInfo(25, DamageType.Physical, gameObject));
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
2
Assets/_Content/Scripts/Combat/Destructible.cs.meta
Normal file
2
Assets/_Content/Scripts/Combat/Destructible.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a59b2dd5620afef449293e11a903ae07
|
||||
@@ -1,34 +0,0 @@
|
||||
using UnityEngine;
|
||||
using Arena.Characters;
|
||||
|
||||
namespace Arena.Interactions
|
||||
{
|
||||
/// <summary>
|
||||
/// Heals the player. Apply is meant to be linked to a PlayerEvent.
|
||||
/// </summary>
|
||||
public class HealEffect : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Settings
|
||||
{
|
||||
[Tooltip("Hit points given to the player.")]
|
||||
[Min(1)] public int Amount = 25;
|
||||
}
|
||||
|
||||
#region Fields
|
||||
|
||||
[SerializeField] private Settings _settings;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void Apply()
|
||||
{
|
||||
if (Player.Instance != null)
|
||||
Player.Instance.Health.Heal(_settings.Amount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b9353836241da04e9bb64cc87f89745
|
||||
@@ -3,81 +3,85 @@ using UnityEngine;
|
||||
|
||||
namespace Arena.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// Hit points of an entity. Serialized inside the component that owns it (Player, Enemy),
|
||||
/// which calls Initialize in its Awake.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Health
|
||||
{
|
||||
#region Fields
|
||||
/// <summary>
|
||||
/// Health points of an entity. Serialized inside the component that owns it (Player, Destructible),
|
||||
/// which calls Initialize in its Awake.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Health
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
[Tooltip("Hit points when full.")]
|
||||
[Min(1)] public int Max = 100;
|
||||
[Tooltip("Health points when full.")]
|
||||
[Min(1)] public int Max = 100;
|
||||
|
||||
[Tooltip("Current hit points, visible for debugging.")]
|
||||
[SerializeField] private int _current;
|
||||
[Tooltip("Current health points, visible for debugging.")]
|
||||
[SerializeField] private int _current;
|
||||
|
||||
/// <summary>
|
||||
/// Raised with the current and max hit points each time they change.
|
||||
/// </summary>
|
||||
public event Action<int, int> OnChange;
|
||||
/// <summary>
|
||||
/// Raised with the current and max health points each time they change.
|
||||
/// </summary>
|
||||
public event Action<int, int> OnChange;
|
||||
|
||||
/// <summary>
|
||||
/// Raised once, when the hit points reach zero.
|
||||
/// </summary>
|
||||
public event Action OnDied;
|
||||
/// <summary>
|
||||
/// Raised once, when the health points reach zero.
|
||||
/// </summary>
|
||||
public event Action OnDied;
|
||||
|
||||
public int Current => _current;
|
||||
public bool IsDead => _current <= 0;
|
||||
public int Current => _current;
|
||||
public bool IsDead => _current <= 0;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
#region PUBLIC METHODS
|
||||
|
||||
/// <summary>
|
||||
/// Fills the hit points. Called by the owner in its Awake.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
SetCurrent(Max);
|
||||
}
|
||||
/// <summary>
|
||||
/// Fills the health points. Called by the owner in its Awake.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
SetCurrent(Max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes hit points and raises OnDied the first time they reach zero.
|
||||
/// </summary>
|
||||
public void TakeDamage(int amount)
|
||||
{
|
||||
if (IsDead || amount <= 0)
|
||||
return;
|
||||
/// <summary>
|
||||
/// Removes health points and raises OnDied the first time they reach zero.
|
||||
/// Returns the health points actually removed.
|
||||
/// </summary>
|
||||
public int TakeDamage(DamageInfo damage)
|
||||
{
|
||||
if (IsDead || damage.Amount <= 0)
|
||||
return 0;
|
||||
|
||||
SetCurrent(_current - amount);
|
||||
int previous = _current;
|
||||
SetCurrent(_current - damage.Amount);
|
||||
|
||||
if (IsDead)
|
||||
OnDied?.Invoke();
|
||||
}
|
||||
if (IsDead)
|
||||
OnDied?.Invoke();
|
||||
|
||||
/// <summary>
|
||||
/// Gives hit points back without exceeding Max. Has no effect once dead.
|
||||
/// </summary>
|
||||
public void Heal(int amount)
|
||||
{
|
||||
if (IsDead || amount <= 0)
|
||||
return;
|
||||
return previous - _current;
|
||||
}
|
||||
|
||||
SetCurrent(_current + amount);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gives health points back without exceeding Max. Has no effect once dead.
|
||||
/// </summary>
|
||||
public void Heal(int amount)
|
||||
{
|
||||
if (IsDead || amount <= 0)
|
||||
return;
|
||||
|
||||
#endregion
|
||||
SetCurrent(_current + amount);
|
||||
}
|
||||
|
||||
#region Private Methods
|
||||
#endregion
|
||||
|
||||
private void SetCurrent(int value)
|
||||
{
|
||||
_current = Mathf.Clamp(value, 0, Max);
|
||||
OnChange?.Invoke(_current, Max);
|
||||
}
|
||||
#region PRIVATE METHODS
|
||||
|
||||
#endregion
|
||||
}
|
||||
private void SetCurrent(int value)
|
||||
{
|
||||
_current = Mathf.Clamp(value, 0, Max);
|
||||
OnChange?.Invoke(_current, Max);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
namespace Arena.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// Anything that can receive damage: player, enemy, destructible object.
|
||||
/// </summary>
|
||||
public interface IDamageable
|
||||
{
|
||||
void TakeDamage(int amount);
|
||||
}
|
||||
/// <summary>
|
||||
/// Anything that can receive damage: player, destructible object.
|
||||
/// </summary>
|
||||
public interface IDamageable
|
||||
{
|
||||
void TakeDamage(DamageInfo damage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,67 +2,64 @@ using UnityEngine;
|
||||
|
||||
namespace Arena.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// Moves straight ahead and damages the first IDamageable it touches.
|
||||
/// Needs a trigger collider and a kinematic Rigidbody.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class Projectile : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Settings
|
||||
{
|
||||
[Tooltip("Speed, in meters per second.")]
|
||||
[Min(0f)] public float Speed = 20f;
|
||||
/// <summary>
|
||||
/// Moves straight ahead and damages the first IDamageable it touches.
|
||||
/// Needs a trigger collider and a kinematic Rigidbody.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class Projectile : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Settings
|
||||
{
|
||||
[Tooltip("Speed, in meters per second.")]
|
||||
[Min(0f)] public float Speed = 20f;
|
||||
|
||||
[Tooltip("Time before an unused projectile is destroyed, in seconds.")]
|
||||
[Min(0.1f)] public float Lifetime = 2f;
|
||||
}
|
||||
[Tooltip("Time before an unused projectile is destroyed, in seconds.")]
|
||||
[Min(0.1f)] public float Lifetime = 2f;
|
||||
}
|
||||
|
||||
#region Fields
|
||||
#region FIELDS
|
||||
|
||||
[SerializeField] private Settings _settings;
|
||||
[SerializeField] private Settings _settings;
|
||||
|
||||
private int _damage;
|
||||
private GameObject _owner;
|
||||
private DamageInfo _damage;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
#region UNITY LIFECYCLES
|
||||
|
||||
private void Update()
|
||||
{
|
||||
transform.position += transform.forward * _settings.Speed * Time.deltaTime;
|
||||
}
|
||||
private void Update()
|
||||
{
|
||||
transform.position += transform.forward * _settings.Speed * Time.deltaTime;
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
// Ignore the shooter and the other trigger zones
|
||||
if (other.isTrigger || other.gameObject == _owner)
|
||||
return;
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
// Ignore the shooter and the other trigger zones
|
||||
if (other.isTrigger || other.gameObject == _damage.Source)
|
||||
return;
|
||||
|
||||
if (other.TryGetComponent(out IDamageable target))
|
||||
target.TakeDamage(_damage);
|
||||
if (other.TryGetComponent(out IDamageable target))
|
||||
target.TakeDamage(_damage);
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
#region PUBLIC METHODS
|
||||
|
||||
/// <summary>
|
||||
/// Starts the projectile. Called right after Instantiate by whoever fires it.
|
||||
/// </summary>
|
||||
/// <param name="damage">Damage dealt to the IDamageable it touches.</param>
|
||||
/// <param name="owner">Shooter, ignored by the projectile.</param>
|
||||
public void Launch(int damage, GameObject owner)
|
||||
{
|
||||
_damage = damage;
|
||||
_owner = owner;
|
||||
Destroy(gameObject, _settings.Lifetime);
|
||||
}
|
||||
/// <summary>
|
||||
/// Starts the projectile. Called right after Instantiate by whoever fires it.
|
||||
/// </summary>
|
||||
/// <param name="damage">Damage dealt to the IDamageable it touches. Its source is the shooter, ignored by the projectile.</param>
|
||||
public void Launch(DamageInfo damage)
|
||||
{
|
||||
_damage = damage;
|
||||
Destroy(gameObject, _settings.Lifetime);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,105 +3,106 @@ using UnityEngine;
|
||||
|
||||
namespace Arena.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// Fires projectiles from a muzzle, limited by the fire interval and the ammo count.
|
||||
/// Placed on the root of the shooter, which the projectiles ignore.
|
||||
/// </summary>
|
||||
public class ProjectileLauncher : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Settings
|
||||
{
|
||||
[Tooltip("Weapon data: damage, fire interval, ammo, projectile.")]
|
||||
public WeaponData Weapon;
|
||||
}
|
||||
/// <summary>
|
||||
/// Fires projectiles from a muzzle, limited by the fire interval and the ammo count.
|
||||
/// Placed on the root of the shooter, which the projectiles ignore.
|
||||
/// </summary>
|
||||
public class ProjectileLauncher : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Settings
|
||||
{
|
||||
[Tooltip("Weapon data: damage, fire interval, ammo, projectile.")]
|
||||
public WeaponData Weapon;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct References
|
||||
{
|
||||
[Tooltip("Spawn point of the projectiles. Its forward axis gives the direction.")]
|
||||
public Transform Muzzle;
|
||||
}
|
||||
[System.Serializable]
|
||||
public struct References
|
||||
{
|
||||
[Tooltip("Spawn point of the projectiles. Its forward axis gives the direction.")]
|
||||
public Transform Muzzle;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class StateContainer
|
||||
{
|
||||
public int Ammo;
|
||||
}
|
||||
[System.Serializable]
|
||||
public class StateContainer
|
||||
{
|
||||
public int Ammo;
|
||||
}
|
||||
|
||||
#region Fields
|
||||
#region FIELDS
|
||||
|
||||
[SerializeField] private Settings _settings;
|
||||
[SerializeField] private References _references;
|
||||
[SerializeField] private StateContainer _state;
|
||||
[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;
|
||||
/// <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 int MaxAmmo => _settings.Weapon.MaxAmmo;
|
||||
public StateContainer State => _state;
|
||||
public int MaxAmmo => _settings.Weapon.MaxAmmo;
|
||||
|
||||
private float _nextFireTime;
|
||||
private float _nextFireTime;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
#region UNITY LIFECYCLES
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_state.Ammo = MaxAmmo;
|
||||
}
|
||||
private void Awake()
|
||||
{
|
||||
_state.Ammo = MaxAmmo;
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
#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 (Time.time < _nextFireTime || _state.Ammo <= 0)
|
||||
return false;
|
||||
/// <summary>
|
||||
/// Fires one projectile if the weapon is ready and has ammo. Returns true if a shot was fired.
|
||||
/// </summary>
|
||||
public bool TryFire()
|
||||
{
|
||||
if (Time.time < _nextFireTime || _state.Ammo <= 0)
|
||||
return false;
|
||||
|
||||
_nextFireTime = Time.time + _settings.Weapon.FireInterval;
|
||||
SetAmmo(_state.Ammo - 1);
|
||||
_nextFireTime = Time.time + _settings.Weapon.FireInterval;
|
||||
|
||||
Transform muzzle = _references.Muzzle;
|
||||
Projectile projectile = Instantiate(_settings.Weapon.ProjectilePrefab, muzzle.position, muzzle.rotation);
|
||||
projectile.Launch(_settings.Weapon.Damage, gameObject);
|
||||
return true;
|
||||
}
|
||||
SetAmmo(_state.Ammo - 1);
|
||||
|
||||
/// <summary>
|
||||
/// Adds ammo without exceeding the weapon maximum.
|
||||
/// </summary>
|
||||
public void AddAmmo(int amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return;
|
||||
Transform muzzle = _references.Muzzle;
|
||||
Projectile projectile = Instantiate(_settings.Weapon.ProjectilePrefab, muzzle.position, muzzle.rotation);
|
||||
projectile.Launch(new DamageInfo(_settings.Weapon.Damage, _settings.Weapon.DamageType, gameObject));
|
||||
return true;
|
||||
}
|
||||
|
||||
SetAmmo(Mathf.Min(_state.Ammo + amount, MaxAmmo));
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds ammo without exceeding the weapon maximum.
|
||||
/// </summary>
|
||||
public void AddAmmo(int amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
#endregion
|
||||
SetAmmo(Mathf.Min(_state.Ammo + amount, MaxAmmo));
|
||||
}
|
||||
|
||||
#region Private Methods
|
||||
#endregion
|
||||
|
||||
private void SetAmmo(int value)
|
||||
{
|
||||
_state.Ammo = value;
|
||||
OnAmmoChange?.Invoke(_state.Ammo, MaxAmmo);
|
||||
}
|
||||
#region PRIVATE METHODS
|
||||
|
||||
[ContextMenu("Debug/Try Fire")]
|
||||
private void DebugTryFire()
|
||||
{
|
||||
TryFire();
|
||||
}
|
||||
private void SetAmmo(int value)
|
||||
{
|
||||
_state.Ammo = value;
|
||||
OnAmmoChange?.Invoke(_state.Ammo, MaxAmmo);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
[ContextMenu("Debug/Try Fire")]
|
||||
private void DebugTryFire()
|
||||
{
|
||||
TryFire();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
using UnityEngine;
|
||||
using Arena.Characters;
|
||||
|
||||
namespace Arena.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Single entry point of the UI: connects the views to the player and switches screens.
|
||||
/// </summary>
|
||||
public class UiManager : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public struct References
|
||||
{
|
||||
[Tooltip("In-game screen: health, ammo.")]
|
||||
public GameObject Hud;
|
||||
public HealthView HealthView;
|
||||
public AmmoView AmmoView;
|
||||
public GameOverView GameOverView;
|
||||
}
|
||||
|
||||
#region Fields
|
||||
|
||||
[SerializeField] private References _references;
|
||||
|
||||
private Player _player;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Start: every Awake has run, so Player.Instance is set
|
||||
_player = Player.Instance;
|
||||
|
||||
if (_player == null)
|
||||
{
|
||||
Debug.LogError("UiManager: no Player in the scene.");
|
||||
return;
|
||||
}
|
||||
|
||||
_references.HealthView.Bind(_player.Health);
|
||||
_references.AmmoView.Bind(_player.Launcher);
|
||||
_player.Health.OnDied += HandlePlayerDied;
|
||||
|
||||
_references.Hud.SetActive(true);
|
||||
_references.GameOverView.Hide();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_player != null)
|
||||
_player.Health.OnDied -= HandlePlayerDied;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void HandlePlayerDied()
|
||||
{
|
||||
_references.Hud.SetActive(false);
|
||||
_references.GameOverView.Show();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c6cb5ac8e4f8d0479afda241378ff0b
|
||||
@@ -2,31 +2,35 @@ using UnityEngine;
|
||||
|
||||
namespace Arena.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// Settings of a weapon, shared by every launcher that uses it.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "WeaponData_new", menuName = "Arena/Combat/Weapon Data")]
|
||||
public class WeaponData : ScriptableObject
|
||||
{
|
||||
#region Fields
|
||||
/// <summary>
|
||||
/// Settings of a weapon, shared by every launcher that uses it.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "WeaponData_new", menuName = "Arena/Combat/Weapon Data")]
|
||||
public class WeaponData : ScriptableObject
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
[Tooltip("Damage dealt by one projectile.")]
|
||||
[SerializeField, Min(0)] private int _damage = 10;
|
||||
[Tooltip("Damage dealt by one projectile.")]
|
||||
[SerializeField, Min(0)] private int _damage = 10;
|
||||
|
||||
[Tooltip("Time between two shots, in seconds.")]
|
||||
[SerializeField, Min(0.05f)] private float _fireInterval = 0.2f;
|
||||
[Tooltip("Type of the damage dealt by the projectiles.")]
|
||||
[SerializeField] private DamageType _damageType;
|
||||
|
||||
[Tooltip("Ammo when the weapon is full.")]
|
||||
[SerializeField, Min(1)] private int _maxAmmo = 30;
|
||||
[Tooltip("Time between two shots, in seconds.")]
|
||||
[SerializeField, Min(0.05f)] private float _fireInterval = 0.2f;
|
||||
|
||||
[Tooltip("Prefab fired by the weapon.")]
|
||||
[SerializeField] private Projectile _projectilePrefab;
|
||||
[Tooltip("Ammo when the weapon is full.")]
|
||||
[SerializeField, Min(1)] private int _maxAmmo = 30;
|
||||
|
||||
public int Damage => _damage;
|
||||
public float FireInterval => _fireInterval;
|
||||
public int MaxAmmo => _maxAmmo;
|
||||
public Projectile ProjectilePrefab => _projectilePrefab;
|
||||
[Tooltip("Prefab fired by the weapon.")]
|
||||
[SerializeField] private Projectile _projectilePrefab;
|
||||
|
||||
#endregion
|
||||
}
|
||||
public int Damage => _damage;
|
||||
public DamageType DamageType => _damageType;
|
||||
public float FireInterval => _fireInterval;
|
||||
public int MaxAmmo => _maxAmmo;
|
||||
public Projectile ProjectilePrefab => _projectilePrefab;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user