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

324 lines
9.8 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;
[DisallowMultipleComponent]
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(Collider2D))]
public class PickableProjectile : BasicProjectile
{
public static PickableProjectile Instance { get; private set; }
public enum State { Held, Airborne, Grounded, Returning }
public State CurrentState { get; private set; } = State.Grounded;
[Header("Pickup/Attach")]
public Vector3 heldLocalOffset = new(0.25f, 0.1f, 0f);
public bool snapExactlyToAttachPoint = true;
public float ignoreOwnerTimeOnThrow = 0.25f;
public string attachPointChildName = "HandPoint";
[Header("Physics on ground")]
public float sleepVelocity = 0.05f;
public bool lockWhenLanded = true;
[Header("Bouncing")]
public int maxBounces = 3;
public float minBounceSpeed = 0.8f;
[Range(0f, 1f)] public float restitution = 0.6f;
[Range(0f, 1f)] public float tangentDamping = 0.85f;
[Range(0f, 1f)] public float groundUpTolerance = 0.25f;
public float stickDelayAfterImpact = 0.03f;
[Tooltip("Старт возврата, если дальше этой дистанции от точки руки.")]
public float recallStartDistance = 8f;
[Tooltip("На этой дистанции от руки считаем, что прилетел и подбираем.")]
public float recallArriveDistance = 1f;
public float recallMaxSpeed = 16f;
public float recallAcceleration = 60f;
[Header("Gizmos")]
public bool drawGizmos;
public Color gizmoColor = new Color(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;
protected override void Awake()
{
base.Awake();
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
}
private void OnDestroy()
{
if (Instance == this) Instance = null;
}
private void Start()
{
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
col.isTrigger = false;
if (CurrentState != State.Held)
BecomeGrounded(null);
}
protected override void FixedUpdate()
{
base.FixedUpdate();
if (!lockWhenLanded && CurrentState == State.Airborne)
{
if (rb.linearVelocity.sqrMagnitude < (sleepVelocity * sleepVelocity))
BecomeGrounded(null);
}
// Бумеранг только если снаряд когда-то уже был в руке
if (hadBeenHeld && CurrentState != State.Held)
HandleRecallToPlayer();
}
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;
}
base.OnCollisionEnter2D(collision);
if (CurrentState == State.Airborne)
{
if (TryBounce(collision))
{
lastImpactTime = Time.time;
return;
}
BecomeGrounded(collision);
}
}
public bool TryPickup(GameObject newHolder, Transform optionalAttachPoint = null)
{
if (CurrentState == State.Held) return true;
owner = newHolder;
lastKnownHolder = newHolder;
holderAttachPoint = optionalAttachPoint ?? FindAttachPoint(newHolder);
var fireScript = newHolder.GetComponent<PlayerFireScript>();
if (holderAttachPoint == null && fireScript != null)
holderAttachPoint = fireScript.attachPoint;
if (holderAttachPoint == null)
holderAttachPoint = newHolder.transform;
rb.linearVelocity = Vector2.zero;
rb.angularVelocity = 0f;
rb.bodyType = RigidbodyType2D.Kinematic;
col.enabled = false;
transform.SetParent(holderAttachPoint, worldPositionStays: true);
transform.position = holderAttachPoint.TransformPoint(heldLocalOffset);
transform.rotation = holderAttachPoint.rotation;
transform.localScale = baseLocalScale;
if (fireScript != null) fireScript.BindProjectile(this);
StartCoroutine(IgnoreOwnerTemporarily(owner, ignoreOwnerTimeOnThrow));
CurrentState = State.Held;
launched = false;
hadBeenHeld = true;
return true;
}
private Transform ResolveAttachPoint(GameObject holder, Transform preferred = null)
{
if (preferred) return preferred;
var fs = holder.GetComponent<PlayerFireScript>();
if (fs && fs.attachPoint) return fs.attachPoint;
return holder.transform;
}
public void Launch(Vector2 direction, float speed, GameObject throwOwner = null)
{
if (CurrentState == State.Held)
transform.SetParent(null, worldPositionStays: true);
owner = throwOwner;
lastKnownHolder = throwOwner;
holderAttachPoint = null;
rb.bodyType = RigidbodyType2D.Dynamic;
col.enabled = true;
launched = true;
Vector2 dir = direction.sqrMagnitude > 0.0001f ? direction.normalized : Vector2.right;
float finalSpeed = (speed > 0f) ? speed : initialSpeed;
rb.linearVelocity = Vector2.zero;
rb.angularVelocity = 0f;
rb.AddForce(dir * finalSpeed * rb.mass, ForceMode2D.Impulse);
transform.right = dir;
transform.localScale = baseLocalScale;
if (throwOwner != null && ignoreOwnerTimeOnThrow > 0f)
StartCoroutine(IgnoreOwnerTemporarily(throwOwner, ignoreOwnerTimeOnThrow));
CurrentState = State.Airborne;
bounceCounter = 0;
}
protected override void OnImpactFinal(Collision2D collision)
{
if (!TryBounce(collision))
BecomeGrounded(collision);
}
protected override void OnImpactFinal(Collider2D other)
{
if (Time.time - lastImpactTime > stickDelayAfterImpact)
BecomeGrounded(null);
}
private bool TryBounce(Collision2D collision)
{
if (Time.time - lastImpactTime < stickDelayAfterImpact)
return true;
var v = rb.linearVelocity;
float speed = v.magnitude;
if (speed < minBounceSpeed) return false;
Vector2 avgNormal = Vector2.up;
if (collision.contactCount > 0)
{
avgNormal = Vector2.zero;
for (int i = 0; i < collision.contactCount; i++)
avgNormal += collision.GetContact(i).normal;
avgNormal.Normalize();
}
Vector2 reflected = Vector2.Reflect(v, avgNormal) * restitution;
Vector2 n = avgNormal;
Vector2 vn = Vector2.Dot(reflected, n) * n;
Vector2 vt = reflected - vn;
reflected = vn + vt * tangentDamping;
rb.linearVelocity = reflected;
rb.position += n * 0.005f;
bounceCounter++;
if (bounceCounter >= maxBounces)
return false;
CurrentState = State.Airborne;
return true;
}
private void BecomeGrounded(Collision2D collision)
{
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;
transform.SetParent(null, true);
bounceCounter = 0;
}
private Transform FindAttachPoint(GameObject who)
{
if (string.IsNullOrWhiteSpace(attachPointChildName)) return null;
var t = who.transform.Find(attachPointChildName);
return t;
}
private void HandleRecallToPlayer()
{
GameObject playerGo = lastKnownHolder;
if (playerGo == null || !playerGo.activeInHierarchy)
playerGo = (GameManager.Instance && GameManager.Instance.playerObj)
? GameManager.Instance.playerObj
: GameObject.FindWithTag("Player");
if (!playerGo) return;
var hand = ResolveAttachPoint(playerGo);
Vector2 toHand = (Vector2)hand.position - rb.position;
float dist = toHand.magnitude;
if (dist > recallStartDistance)
{
if (CurrentState != State.Returning)
{
CurrentState = State.Returning;
rb.bodyType = RigidbodyType2D.Dynamic;
col.enabled = true;
}
Vector2 desired = toHand.normalized * recallMaxSpeed;
rb.linearVelocity = Vector2.MoveTowards(
rb.linearVelocity, desired, recallAcceleration * Time.fixedDeltaTime);
if (rb.linearVelocity.sqrMagnitude > 0.01f)
transform.right = rb.linearVelocity.normalized;
if (dist <= recallArriveDistance)
TryPickup(playerGo);
}
else if (CurrentState == State.Returning)
{
CurrentState = rb.bodyType == RigidbodyType2D.Dynamic ? State.Airborne : State.Grounded;
}
}
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
if (!drawGizmos) return;
Gizmos.color = gizmoColor;
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawCube(new Vector3(heldLocalOffset.x, heldLocalOffset.y, 0), new Vector3(0.12f, 0.12f, 0.02f));
}
#endif
}