216 lines
7.0 KiB
C#

// Projectile2D.cs
using System.Collections;
using UnityEngine;
namespace RR.Game2D.Entities.Projectiles
{
[RequireComponent(typeof(Rigidbody2D), typeof(Collider2D))]
public abstract class Projectile2D : MonoBehaviour
{
[Header("Скорость")]
public float initialSpeed = 15f;
[Header("Скорость")]
public float maxLifetime = 5f;
[Header("Столкновения/пробивания")]
public LayerMask hitLayers = ~0;
public int maxPierce = 0;
[Header("Гравитация/физика")]
public bool useGravity = false;
public float gravityScale = 1f;
[Header("FX")]
[SerializeField] protected GameObject hitVfxPrefab;
[SerializeField] protected AudioClip hitSfx;
[SerializeField] protected float sfxVolume = 1f;
[Header("Другое")]
public bool alignRotationToVelocity = true;
protected float damage = 10f;
public float knockback = 0f;
protected Rigidbody2D rb;
protected Collider2D col;
protected int remainingPierce;
protected bool launched;
public GameObject owner;
protected virtual void Awake()
{
rb = GetComponent<Rigidbody2D>();
col = GetComponent<Collider2D>();
// Базовые настройки физики
rb.bodyType = RigidbodyType2D.Dynamic;
rb.gravityScale = useGravity ? gravityScale : 0f;
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
col.isTrigger = false;
}
protected virtual void OnEnable()
{
remainingPierce = maxPierce;
//if (maxLifetime > 0f)
// Invoke(nameof(Despawn), maxLifetime);
}
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)
{
Collider2D other = collision.collider;
if (!IsInHitLayer(other.gameObject.layer)) return;
Vector2 point = collision.GetContact(0).point;
HandleHit(other, point, collision.relativeVelocity, isTriggerEvent: false, collision);
}
protected virtual void OnTriggerEnter2D(Collider2D other)
{
if (!col.isTrigger) return;
if (!IsInHitLayer(other.gameObject.layer)) return;
Vector2 point = other.ClosestPoint(transform.position);
HandleHit(other, point, rb.linearVelocity, isTriggerEvent: true, collision: null);
}
/// <summary>
///
/// </summary>
/// <param name="direction"></param>
/// <param name="speed"></param>
/// <param name="owner"></param>
/// <param name="ignoreOwnerTime"></param>
public virtual void Launch(Vector2 direction, float speed = -1f, GameObject owner = null, float ignoreOwnerTime = 0.1f)
{
this.owner = owner;
launched = true;
float finalSpeed = (speed > 0f) ? speed : initialSpeed;
rb.linearVelocity = direction.normalized * finalSpeed;
if (owner != null && ignoreOwnerTime > 0f)
StartCoroutine(IgnoreOwnerTemporarily(owner, ignoreOwnerTime));
}
/// <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="point"></param>
/// <param name="relativeVelocity"></param>
/// <param name="isTriggerEvent"></param>
/// <param name="collision"></param>
protected void HandleHit(Collider2D target, Vector2 point, Vector2 relativeVelocity, bool isTriggerEvent, Collision2D collision)
{
TryDealDamage(target, point, relativeVelocity);
OnHit(collision, point, relativeVelocity);
if (remainingPierce > 0)
{
remainingPierce--;
return;
}
if (isTriggerEvent) OnImpactFinal(target);
else OnImpactFinal(collision);
}
/// <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)
{
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);
}
}
}