feat: Enemies
This commit is contained in:
@@ -3,7 +3,7 @@ using UnityEngine;
|
||||
namespace Arena.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// Everything a hit carries: amount, type and source. Passed to IDamageable.TakeDamage.
|
||||
/// Everything a hit carries: amount, type, source and targeted team. Passed to IDamageable.TakeDamage.
|
||||
/// </summary>
|
||||
public readonly struct DamageInfo
|
||||
{
|
||||
@@ -16,11 +16,33 @@ namespace Arena.Combat
|
||||
/// </summary>
|
||||
public readonly GameObject Source;
|
||||
|
||||
public DamageInfo(int amount, DamageType type, GameObject source = null)
|
||||
/// <summary>
|
||||
/// Team hurt by this damage: Allied for enemy shots, Enemy for player shots, All for everyone.
|
||||
/// </summary>
|
||||
public readonly Team Target;
|
||||
|
||||
public DamageInfo(int amount, DamageType type, GameObject source = null, Team target = Team.All)
|
||||
{
|
||||
Amount = amount;
|
||||
Type = type;
|
||||
Source = source;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if this damage can hurt an IDamageable of the given team.
|
||||
/// </summary>
|
||||
public bool CanHit(Team team)
|
||||
{
|
||||
return CanHit(Target, team);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if a damage targeting a team can hurt an IDamageable of another team.
|
||||
/// </summary>
|
||||
public static bool CanHit(Team target, Team team)
|
||||
{
|
||||
return target == Team.All || team == Team.All || team == target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ namespace Arena.Combat
|
||||
|
||||
public DamageType DamageType;
|
||||
|
||||
[Tooltip("Team hurt by the zone: Allied for an enemy attack, Enemy for a player attack, All for everyone.")]
|
||||
public Team Target = Team.All;
|
||||
|
||||
[Tooltip("Damage multiplier by distance to the center. 0: center, 1: edge of the sphere.")]
|
||||
public AnimationCurve Falloff = AnimationCurve.Linear(0f, 1f, 1f, 0f);
|
||||
|
||||
@@ -133,7 +136,7 @@ namespace Arena.Combat
|
||||
{
|
||||
target = other.GetComponentInParent<IDamageable>();
|
||||
|
||||
if (target == null)
|
||||
if (target == null || !CanHit(target))
|
||||
return false;
|
||||
|
||||
// The owner never hurts itself
|
||||
@@ -150,7 +153,12 @@ namespace Arena.Combat
|
||||
return;
|
||||
|
||||
GameObject source = _references.Owner ? _references.Owner : gameObject;
|
||||
target.TakeDamage(new DamageInfo(amount, _settings.DamageType, source));
|
||||
target.TakeDamage(new DamageInfo(amount, _settings.DamageType, source, _settings.Target));
|
||||
}
|
||||
|
||||
private bool CanHit(IDamageable target)
|
||||
{
|
||||
return DamageInfo.CanHit(_settings.Target, target.Team);
|
||||
}
|
||||
|
||||
private Vector3 GetCenter()
|
||||
|
||||
@@ -14,6 +14,9 @@ namespace Arena.Combat
|
||||
{
|
||||
[Tooltip("Optional prefab spawned in place of the object: debris, explosion...")]
|
||||
public GameObject SpawnOnDestroyed;
|
||||
|
||||
[Tooltip("All: hurt by everyone. Allied: only by enemy damage. Enemy: only by player damage.")]
|
||||
public Team Team = Team.All;
|
||||
}
|
||||
|
||||
#region FIELDS
|
||||
@@ -47,6 +50,8 @@ namespace Arena.Combat
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public Team Team => _settings.Team;
|
||||
|
||||
public void TakeDamage(DamageInfo damage)
|
||||
{
|
||||
_health.TakeDamage(damage);
|
||||
|
||||
@@ -5,6 +5,11 @@ namespace Arena.Combat
|
||||
/// </summary>
|
||||
public interface IDamageable
|
||||
{
|
||||
/// <summary>
|
||||
/// Side of this object. A damage only hurts the team it targets (see DamageInfo.CanHit).
|
||||
/// </summary>
|
||||
Team Team { get; }
|
||||
|
||||
void TakeDamage(DamageInfo damage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ using UnityEngine;
|
||||
namespace Arena.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// Moves straight ahead and damages the first IDamageable it touches.
|
||||
/// Moves straight ahead and damages the first IDamageable of the targeted team it touches.
|
||||
/// It goes through the other teams: no friendly fire.
|
||||
/// Needs a trigger collider and a kinematic Rigidbody.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
@@ -36,12 +37,21 @@ namespace Arena.Combat
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
// Ignore the shooter and the other trigger zones
|
||||
if (other.isTrigger || other.gameObject == _damage.Source)
|
||||
// Ignore the other trigger zones, and the shooter with all its children
|
||||
if (other.isTrigger || IsFromShooter(other))
|
||||
return;
|
||||
|
||||
if (other.TryGetComponent(out IDamageable target))
|
||||
// The collider can be on a child of the IDamageable: arm, head, model...
|
||||
IDamageable target = other.GetComponentInParent<IDamageable>();
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
// A friend is crossed, not hit
|
||||
if (!_damage.CanHit(target.Team))
|
||||
return;
|
||||
|
||||
target.TakeDamage(_damage);
|
||||
}
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
@@ -61,5 +71,15 @@ namespace Arena.Combat
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private bool IsFromShooter(Collider other)
|
||||
{
|
||||
GameObject shooter = _damage.Source;
|
||||
return shooter && other.transform.IsChildOf(shooter.transform);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ 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.
|
||||
/// 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
|
||||
{
|
||||
@@ -14,6 +16,9 @@ namespace Arena.Combat
|
||||
{
|
||||
[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]
|
||||
@@ -21,12 +26,26 @@ namespace Arena.Combat
|
||||
{
|
||||
[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
|
||||
@@ -41,6 +60,7 @@ namespace Arena.Combat
|
||||
public event Action<int, int> OnAmmoChange;
|
||||
|
||||
public StateContainer State => _state;
|
||||
public Transform Muzzle => _references.Muzzle;
|
||||
public int MaxAmmo => _settings.Weapon.MaxAmmo;
|
||||
|
||||
private float _nextFireTime;
|
||||
@@ -54,6 +74,28 @@ namespace Arena.Combat
|
||||
_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
|
||||
@@ -63,7 +105,7 @@ namespace Arena.Combat
|
||||
/// </summary>
|
||||
public bool TryFire()
|
||||
{
|
||||
if (Time.time < _nextFireTime || _state.Ammo <= 0)
|
||||
if (_state.Disabled || _state.IsDropped || Time.time < _nextFireTime || _state.Ammo <= 0)
|
||||
return false;
|
||||
|
||||
_nextFireTime = Time.time + _settings.Weapon.FireInterval;
|
||||
@@ -72,10 +114,43 @@ namespace Arena.Combat
|
||||
|
||||
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));
|
||||
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>
|
||||
|
||||
13
Assets/_Content/Scripts/Combat/Team.cs
Normal file
13
Assets/_Content/Scripts/Combat/Team.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Arena.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// Side of an IDamageable, and side targeted by a damage.
|
||||
/// A damage hits an IDamageable when one of the two is All, or when both are the same.
|
||||
/// </summary>
|
||||
public enum Team
|
||||
{
|
||||
All,
|
||||
Allied,
|
||||
Enemy
|
||||
}
|
||||
}
|
||||
2
Assets/_Content/Scripts/Combat/Team.cs.meta
Normal file
2
Assets/_Content/Scripts/Combat/Team.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 270a21a512abe8c45b7fa4900f5ef45a
|
||||
Reference in New Issue
Block a user