Danro/Assets/Resources/Scripts/Projectiles/PickableProjectile.cs

480 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using RR.Common.Interfaces;
using RR.Game2D.Core;
using RR.Game2D.Behaviour;
using RR.Game2D.Core.Interfaces;
namespace RR.Game2D.Entities.Projectiles
{
[DisallowMultipleComponent]
[RequireComponent(typeof(Rigidbody2D),
typeof(CapsuleCollider2D))]
public class PickableProjectile : PhysicsProjectile, IPickable, IBouncy, IRecallable
{
public static PickableProjectile Instance { get; private set; }
public enum State { Held, Airborne, Grounded, Returning }
public State CurrentState { get; private set; } = State.Grounded;
[Header("Подбор и прикрепление")]
[SerializeField] private Vector3 heldLocalOffset = new(0.25f, 0.1f, 0f);
[SerializeField] private float heldScale = 1.5f;
[SerializeField] private float ignoreOwnerTimeOnThrow = 0.1f;
[Header("Приземление")]
[SerializeField] private float groundVelocity = 1f;
[SerializeField] private float maxGroundSlideTime = 1f;
[SerializeField] private bool lockWhenLanded = true;
[Header("Отскоки")]
[SerializeField] private int maxBounces = 3;
[SerializeField] private float minBounceSpeed = 0.8f;
[SerializeField] private float stickDelayAfterImpact = 0.03f;
[Header("Возврат")]
[SerializeField, Range(0, 100)] int recallScreenMarginPercent = 5;
[SerializeField] private float recallArriveDistance = 1f;
[SerializeField] private float recallSpeed = 16f;
[SerializeField] private float recallStartDistance = 8f;
[Header("Gizmos")]
[SerializeField] private bool drawGizmos;
[SerializeField] private Color gizmoColor = new (0.2f, 0.8f, 1f, 0.35f);
private Transform holderAttachPoint;
private int bounceCounter = 0;
private float lastImpactTime = -999f;
private Vector3 baseLocalScale = Vector3.one;
private bool hadBeenHeld = false;
private GameObject lastKnownHolder;
private float lastGroundTouchTime;
private GameObject jumpPad;
// -------------------------------------------
// Методы Unity
//
//---------------------------
// Singleton
//---------------------------
protected override void Awake()
{
rb = GetComponent<Rigidbody2D>();
col = GetComponent<CapsuleCollider2D>();
col.isTrigger = true;
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
jumpPad = new GameObject("Projectile Jump Pad");
jumpPad.AddComponent<BoxCollider2D>();
jumpPad.AddComponent<JumpPad2D>();
jumpPad.GetComponent<BoxCollider2D>().isTrigger = true;
Vector3 jumpPadScale = jumpPad.transform.localScale;
jumpPadScale.y = 0.1f;
jumpPad.transform.localScale = jumpPadScale;
jumpPad.SetActive(false);
jumpPad.GetComponent<JumpPad2D>().PlayerJumped += OnPlayerJumped;
}
private void OnDestroy()
{
if (Instance == this) Instance = null;
Destroy(jumpPad);
}
//---------------------------
//
//---------------------------
private void Start()
{
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
col.isTrigger = false;
if (CurrentState != State.Held)
BecomeGrounded();
}
protected override void FixedUpdate()
{
base.FixedUpdate();
if (!lockWhenLanded && CurrentState == State.Airborne)
{
if (rb.linearVelocity.sqrMagnitude < (groundVelocity * groundVelocity))
BecomeGrounded();
}
if ((CurrentState == State.Airborne || CurrentState == State.Returning) && rb.linearVelocity.sqrMagnitude > 0.0001f)
transform.up = rb.linearVelocity.normalized;
if (CurrentState == State.Airborne && rb.linearVelocity.magnitude < groundVelocity)
BecomeGrounded();
if (hadBeenHeld && CurrentState != State.Held)
Recall();
}
protected override void OnCollisionEnter2D(Collision2D collision)
{
var otherRb = collision.rigidbody ? collision.rigidbody : collision.otherRigidbody;
var otherGO = (collision.collider ? collision.collider.attachedRigidbody?.gameObject : null)
?? otherRb?.gameObject
?? collision.gameObject;
if (otherGO && otherGO.CompareTag("Player") && CurrentState != State.Held)
{
if (TryPickup(otherGO)) return;
}
if (!IsInHitLayer(collision.collider.gameObject.layer)) return;
Vector2 point = collision.GetContact(0).point;
OnHit(collision, point, collision.relativeVelocity);
if (CurrentState == State.Airborne)
{
if (TryBounce(collision))
{
lastImpactTime = Time.time;
return;
}
BecomeGrounded();
}
if (collision.collider.CompareTag("Ground"))
lastGroundTouchTime = Time.time;
}
private void OnCollisionStay2D(Collision2D collision)
{
if (collision.collider.CompareTag("Ground"))
{
if (Time.time - lastGroundTouchTime > maxGroundSlideTime)
BecomeGrounded();
}
}
protected override void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
if (TryPickup(other.gameObject)) return;
}
else
{
base.OnTriggerEnter2D(other);
}
}
//
//
// -------------------------------------------
// -------------------------------------------
// Переопределенные методы родителей
//
protected override void OnHit(Collision2D collision, Vector2 point, Vector2 relativeVelocity)
{
//TryDealDamage(collision.collider, point, relativeVelocity);
PlayFX(point);
OnImpactFinal(collision);
}
protected override void OnImpactFinal(Collision2D collision)
{
if (!TryBounce(collision))
BecomeGrounded();
}
protected override void OnImpactFinal(Collider2D other)
{
if (Time.time - lastImpactTime > stickDelayAfterImpact)
BecomeGrounded();
}
//
//
// -------------------------------------------
// -------------------------------------------
// Подбор
//
public bool TryPickup(GameObject newOwner, Transform optionalAttachPoint = null)
{
if (CurrentState == State.Held) return true;
owner = newOwner;
lastKnownHolder = newOwner;
holderAttachPoint = optionalAttachPoint ?? FindAttachPoint(newOwner);
var fireScript = newOwner.GetComponent<PlayerFireScript>();
if (holderAttachPoint == null && fireScript != null)
holderAttachPoint = fireScript.attachPoint;
if (holderAttachPoint == null)
holderAttachPoint = newOwner.transform;
rb.linearVelocity = Vector2.zero;
rb.angularVelocity = 0f;
rb.bodyType = RigidbodyType2D.Kinematic;
col.enabled = false;
transform.SetParent(holderAttachPoint);
transform.SetPositionAndRotation(holderAttachPoint.TransformPoint(heldLocalOffset), new Quaternion(0, 90f, 0, 0));
transform.localScale = baseLocalScale * heldScale;
if (fireScript != null) fireScript.BindProjectile(this);
//StartCoroutine(IgnoreOwnerTemporarily(owner, ignoreOwnerTimeOnThrow));
CurrentState = State.Held;
launched = false;
hadBeenHeld = true;
jumpPad.SetActive(false);
return true;
}
/// <summary>
/// Метод для получения точки привязки на игроке.
/// </summary>
/// <param name="holder">Игрок, который держит снаряд</param>
/// <param name="preferred">Опциональная точка привязки. При передаче, снаряд будет привязан на нее</param>
/// <returns>Transform attachPoint - точка привязки</returns>
private Transform FindAttachPoint(GameObject holder, Transform preferred = null)
{
if (preferred) return preferred;
PlayerFireScript fireScript = holder.GetComponent<PlayerFireScript>();
if (fireScript && fireScript.attachPoint) return fireScript.attachPoint;
return holder.transform;
}
//
//
// -------------------------------------------
// -------------------------------------------
// Отскок
//
public bool TryBounce(Collision2D collision)
{
if (Time.time - lastImpactTime < stickDelayAfterImpact) return true;
if (bounceCounter > maxBounces) return false;
Vector2 velocityIn = rb.linearVelocity;
float speed = velocityIn.magnitude;
if (speed < minBounceSpeed) return false;
Vector2 direction = Vector2.Reflect(velocityIn, collision.contacts[0].normal);
rb.linearVelocity = col.sharedMaterial.bounciness * direction;
if (collision.collider.CompareTag("Ground") || collision.collider.CompareTag("Trap"))
bounceCounter++;
return true;
}
//
//
// -------------------------------------------
// -------------------------------------------
// Запуск
//
public override void Launch(Vector2 direction, float speed, GameObject throwOwner = null)
{
if (CurrentState == State.Held)
transform.SetParent(null);
owner = throwOwner;
lastKnownHolder = throwOwner;
holderAttachPoint = null;
rb.bodyType = RigidbodyType2D.Dynamic;
col.enabled = true;
col.isTrigger = false;
launched = true;
Vector2 launchDirection = direction.sqrMagnitude > 0.0001f ? direction.normalized : Vector2.right;
float finalSpeed = (speed > 0f) ? speed : initialSpeed;
rb.linearVelocity = Vector2.zero;
rb.angularVelocity = 0f;
rb.AddForce(finalSpeed * rb.mass * launchDirection, ForceMode2D.Impulse);
transform.up = launchDirection;
transform.localScale = baseLocalScale;
if (throwOwner != null && ignoreOwnerTimeOnThrow > 0f)
StartCoroutine(IgnoreOwnerTemporarily(throwOwner, ignoreOwnerTimeOnThrow));
CurrentState = State.Airborne;
bounceCounter = 0;
}
//
//
// -------------------------------------------
// -------------------------------------------
// Возвращение
//
public void Recall()
{
GameObject playerGo = lastKnownHolder;
if (playerGo == null || !playerGo.activeInHierarchy)
playerGo = (GameManager.Instance && GameManager.Instance.PlayerObj)
? GameManager.Instance.PlayerObj
: GameObject.FindWithTag("Player");
if (!playerGo) return;
var attachPoint = FindAttachPoint(playerGo);
bool isVisible;
var mainCamera = Camera.main;
float margin = recallScreenMarginPercent / 100f;
if (mainCamera)
{
Vector3 viewPoint = mainCamera.WorldToViewportPoint(transform.position);
isVisible = viewPoint.x >= -margin && viewPoint.x <= 1f + margin
&& viewPoint.y >= -margin && viewPoint.y <= 1f + margin;
}
else
{
Vector2 toOwnerTest = (Vector2)attachPoint.position - rb.position;
isVisible = (toOwnerTest.magnitude <= recallStartDistance);
}
Vector2 toOwner = (Vector2)attachPoint.position - rb.position;
float dist = toOwner.magnitude;
if (CurrentState != State.Returning)
{
if (!isVisible)
{
CurrentState = State.Returning;
rb.bodyType = RigidbodyType2D.Dynamic;
col.enabled = false;
}
else
{
return;
}
}
Vector2 dir = (dist > 0.001f) ? (toOwner / dist) : rb.linearVelocity.normalized;
rb.linearVelocity = dir * recallSpeed;
if (rb.linearVelocity.sqrMagnitude > 0.01f)
transform.up = rb.linearVelocity.normalized;
if (dist <= recallArriveDistance)
TryPickup(playerGo);
}
//
//
// -------------------------------------------
// -------------------------------------------
// Собственные незаисимые методы
//
/// <summary>
/// Метод для заморозки снаряда на земле.
/// </summary>
/// <param name="collision">Коллизия, с которой столкнулся снаряд</param>
private void BecomeGrounded()
{
launched = false;
CurrentState = State.Grounded;
if (lockWhenLanded)
{
rb.linearVelocity = Vector2.zero;
rb.angularVelocity = 0f;
rb.bodyType = RigidbodyType2D.Kinematic;
}
else
{
rb.bodyType = RigidbodyType2D.Dynamic;
}
col.enabled = true;
col.isTrigger = true;
transform.SetParent(null, true);
bounceCounter = 0;
MoveJumpPad();
jumpPad.SetActive(true);
}
/// <summary>
/// Метод для перемещения прыжковой платформы Тамагочика
/// </summary>
private void MoveJumpPad()
{
float sizeX = col.bounds.size.y;
float sizeY = col.bounds.size.x;
Vector3 jumpPadPos = transform.position;
float jumpPadY = Mathf.Sqrt(sizeX * sizeX * Mathf.Pow(Mathf.Cos(transform.localEulerAngles.z), 2) + sizeY * sizeY * Mathf.Pow(Mathf.Sin(transform.localEulerAngles.z), 2));
jumpPadPos.y = jumpPadPos.y + 0.5f * jumpPadY;
jumpPad.transform.position = jumpPadPos;
}
/// <summary>
/// Метод для обработки событий при отпрыгивания игрока от прыжковой платформы.
/// </summary>
/// <param name="playerObj"></param>
private void OnPlayerJumped(GameObject playerObj)
{
TryPickup(playerObj);
}
//
//
// -------------------------------------------
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
Collider2D col = GetComponent<Collider2D>();
if (!drawGizmos) return;
Gizmos.color = gizmoColor;
Gizmos.matrix = col.localToWorldMatrix;
//Gizmos.DrawCube(new Vector3(heldLocalOffset.x, heldLocalOffset.y, 0), new Vector3(0.12f, 0.12f, 0.02f));
//Gizmos.DrawSphere(new Vector3(col.bounds.max.y, col.bounds.center.x, 0), 0.5f);
}
#endif
}
}