213 lines
6.9 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|