213 lines
6.9 KiB
C#

using System.Collections;
using UnityEngine;
using RR.Game2D.Core.Managers.Interfaces;
namespace RR.Game2D.Entities.Projectiles
{
[RequireComponent(typeof(Rigidbody2D), typeof(Collider2D))]
public abstract class Projectile2D : MonoBehaviour, ILaunchable
{
[Header("Базовые настройки")]
[SerializeField] protected float initialSpeed = 15f;
[SerializeField] protected float ignoreOwnerTime = 0.1f;
[SerializeField] protected float projectileDamage = 1f;
public float maxLifetime = 5f;
[Header("Столкновения/пробивания")]
[SerializeField] protected LayerMask hitLayers = ~0;
[SerializeField] protected int maxPierce = 0;
[Header("FX")]
[SerializeField] protected GameObject hitVfxPrefab;
[SerializeField] protected AudioClip hitSfx;
[SerializeField] protected float sfxVolume = 1f;
[Header("Другое")]
[SerializeField] private bool alignRotationToVelocity = true;
protected Rigidbody2D _rb;
protected Collider2D _col;
protected int _remainingPierce;
protected bool _launched;
protected float _launchTime;
public GameObject owner;
protected virtual void Awake()
{
_rb = GetComponent<Rigidbody2D>();
_col = GetComponent<Collider2D>();
}
protected virtual void OnEnable()
{
_remainingPierce = maxPierce;
}
protected virtual void FixedUpdate()
{
if (!_launched) return;
if (alignRotationToVelocity && _rb.linearVelocity.sqrMagnitude > 0.0001f)
{
float angle = Mathf.Atan2(_rb.linearVelocity.y, _rb.linearVelocity.x) * Mathf.Rad2Deg;
_rb.SetRotation(angle);
}
}
protected virtual void OnCollisionEnter2D(Collision2D collision)
{
if (!IsInHitLayer(collision.collider.gameObject.layer)) return;
Vector2 point = collision.GetContact(0).point;
OnHit(collision, point, collision.relativeVelocity);
}
protected virtual void OnTriggerEnter2D(Collider2D other)
{
if (!_col.isTrigger) return;
if (!IsInHitLayer(other.gameObject.layer)) return;
Vector2 point = other.ClosestPoint(transform.position);
OnHit(other, point, _rb.linearVelocity);
}
public virtual void Launch(Vector2 direction, float speed = -1f, GameObject thrower = null)
{
this.owner = thrower;
_launched = true;
float finalSpeed = (speed > 0f) ? speed : initialSpeed;
_rb.linearVelocity = direction.normalized * finalSpeed;
if (thrower != null && ignoreOwnerTime > 0f)
StartCoroutine(IgnoreOwnerTemporarily(thrower, ignoreOwnerTime));
_launchTime = Time.time;
}
/// <summary>
///
/// </summary>
/// <param name="who"></param>
/// <param name="time"></param>
protected virtual IEnumerator IgnoreOwnerTemporarily(GameObject who, float time)
{
Collider2D[] ownerColliders = who.GetComponentsInChildren<Collider2D>();
foreach (var oc in ownerColliders)
if (oc && _col) Physics2D.IgnoreCollision(_col, oc, true);
yield return new WaitForSeconds(time);
foreach (var oc in ownerColliders)
if (oc && _col) Physics2D.IgnoreCollision(_col, oc, false);
}
/// <summary>
///
/// </summary>
/// <param name="layer"></param>
/// <returns></returns>
protected bool IsInHitLayer(int layer)
{
return ((1 << layer) & hitLayers.value) != 0;
}
/// <summary>
///
/// </summary>
/// <param name="target"></param>
/// <param name="hitPoint"></param>
/// <param name="relativeVelocity"></param>
protected virtual void TryDealDamage(Collider2D target, Vector2 hitPoint, Vector2 relativeVelocity)
{
//var dmg = target.GetComponentInParent<IDamageable2D>();
//if (dmg != null)
//{
// Vector2 force = (rb != null && rb.linearVelocity.sqrMagnitude > 0.001f)
// ? rb.linearVelocity.normalized * knockback
// : Vector2.zero;
// dmg.ApplyDamage(damage, hitPoint, force, owner ?? gameObject);
//}
}
/// <summary>
///
/// </summary>
/// <param name="collision"></param>
/// <param name="point"></param>
/// <param name="relativeVelocity"></param>
protected virtual void OnHit(Collision2D collision, Vector2 point, Vector2 relativeVelocity)
{
TryDealDamage(collision.collider, point, relativeVelocity);
PlayFX(point);
if (_remainingPierce > 0)
{
_remainingPierce--;
return;
}
OnImpactFinal(collision);
}
/// <summary>
///
/// </summary>
/// <param name="otherCol"></param>
/// <param name="point"></param>
/// <param name="relativeVelocity"></param>
protected virtual void OnHit(Collider2D otherCol, Vector2 point, Vector2 relativeVelocity)
{
TryDealDamage(otherCol, point, relativeVelocity);
PlayFX(point);
if (_remainingPierce > 0)
{
_remainingPierce--;
return;
}
OnImpactFinal(otherCol);
}
/// <summary>
/// Метод воспроизведения эффектов в определённой точке
/// Может отрабатывать при любом поведении снаряда (столкновение, истечение времени жизни и т.д.)
/// </summary>
/// <param name="point">Точка воспроизведения FX</param>
protected virtual void PlayFX(Vector2 point)
{
if (hitVfxPrefab != null)
{
Instantiate(hitVfxPrefab, point, Quaternion.identity);
}
if (hitSfx != null)
{
AudioSource.PlayClipAtPoint(hitSfx, point, sfxVolume);
}
}
/// <summary>
///
/// </summary>
/// <param name="collision"></param>
protected virtual void OnImpactFinal(Collision2D collision) => Despawn();
/// <summary>
///
/// </summary>
/// <param name="other"></param>
protected virtual void OnImpactFinal(Collider2D other) => Despawn();
/// <summary>
///
/// </summary>
public virtual void Despawn()
{
Destroy(gameObject);
}
}
}