Files
dungeons/Assets/HurricaneVR/Framework/Scripts/Weapons/Guns/HVRCockingHandle.cs

467 lines
14 KiB
C#

using System.Collections;
using System.Collections.Generic;
using HurricaneVR.Framework.Core;
using HurricaneVR.Framework.Core.Grabbers;
using UnityEngine;
using UnityEngine.Events;
namespace HurricaneVR.Framework.Weapons.Guns
{
public class HVRCockingHandle : HVRGunPart
{
[Header("Settings")]
[Tooltip("Forward speed of the slide when released")]
public float ForwardSpeed = 10f;
[Tooltip("Faux difficulty for pulling back the handle")]
public float Difficulty = .05f;
[Tooltip("If the handle was popped open due to out of ammo, it will release immediately upon being grabbed")]
public bool ImmediateReleaseWhenOpen = true;
[Tooltip("If true, locks back even if the handle isn't reciprocating")]
public bool LockBackOverride;
public HVRCockingHandleType Type;
[Header("Lock Options")]
public bool LocksForward;
public bool TriggerUnlocks;
public float TriggerThreshold = .7f;
[Tooltip("Hand must move this fast to unlock the handle")]
public float AccelerationThreshold = 2f;
[Header("Components")]
public HVRGrabbable Grabbable;
[Tooltip("Bolt that moves with the charging handle, pump")]
public HVRGunBolt Bolt;
[Header("Editor Positions")]
[Tooltip("Position to reach to eject the chambered round")]
public Vector3 EjectPosition;
[Tooltip("Position to reach that will chamber a round.")]
public Vector3 ChamberRoundPosition;
public List<HVRGunPart> AnimatedParts = new List<HVRGunPart>();
[Header("Slide Events")]
public UnityEvent Released = new UnityEvent();
public UnityEvent EjectReached = new UnityEvent();
public UnityEvent ChamberRound = new UnityEvent();
private Transform _grabbedPositionTracker;
/// <summary>
/// The offset of the cocking handle when it's grabbed
/// </summary>
protected float GrabOffset { get; set; }
public bool EmptyOpen { get; protected set; }
public Vector3 ForwardPositionWorld => transform.parent.TransformPoint(ForwardPosition);
public Vector3 MaxPositionWorld => transform.parent.TransformPoint(BackwardPosition);
public Vector3 BackDirectionWorld => (MaxPositionWorld - ForwardPositionWorld).normalized;
private float _chamberedRequiredDistance;
private bool _chamberDistanceReached;
private float _maximumDistance;
private float _ejectDistance;
private bool _ejectDistanceReached;
private float _lockDistance;
private bool _lockDistanceReached;
private Vector3 _previousHandPosition;
private Vector3 _previousVelocity;
private Vector3 _handAcceleration;
private bool _locked = true;
private Coroutine _forwardRoutine;
public virtual bool LocksBack
{
get
{
if (Type == HVRCockingHandleType.Reciprocating)
{
return true;
}
return LockBackOverride;
}
}
protected virtual void Awake()
{
if (!Grabbable)
{
Grabbable = GetComponent<HVRGrabbable>();
}
if (Grabbable)
{
Grabbable.HandGrabbed.AddListener(OnGrabbed);
Grabbable.HandReleased.AddListener(OnReleased);
}
else
{
Debug.LogError($"{name} is missing it's grabbable component.");
}
var go = new GameObject("GrabTracker");
go.transform.parent = transform.parent;
_grabbedPositionTracker = go.transform;
_chamberedRequiredDistance = Vector3.Distance(ChamberRoundPosition, ForwardPosition);
_maximumDistance = Vector3.Distance(BackwardPosition, ForwardPosition);
_ejectDistance = Vector3.Distance(EjectPosition, ForwardPosition);
_lockDistance = _maximumDistance * .05f;
}
public void Enable()
{
Grabbable.CanBeGrabbed = true;
}
public void Disable()
{
Grabbable.CanBeGrabbed = false;
}
protected virtual void Update()
{
if (Grabbable.HandGrabbers.Count > 0)
{
UpdateHandTracking();
if (_locked)
{
if (CheckUnlock())
{
Unlock();
}
}
if (_locked && LocksForward)
{
return;
}
var pullDirection = (Grabbable.HandGrabbers[0].TrackedController.position - _grabbedPositionTracker.position);
var backDirection = (MaxPositionWorld - ForwardPositionWorld).normalized * 10;
var amount = Vector3.Dot(pullDirection, backDirection) * Difficulty;
if (amount + GrabOffset > 0)
{
UpdatePosition(amount);
}
else
{
transform.localPosition = ForwardPosition;
}
var distance = Vector3.Distance(transform.position, ForwardPositionWorld);
CheckEject(distance);
CheckChamberDistance(distance);
ClampPullBack(distance, BackDirectionWorld);
MoveBolt();
AnimateParts();
CheckLock(distance);
}
}
protected virtual void CheckLock(float distance)
{
if (!LocksForward)
return;
if (distance > _lockDistance && !_lockDistanceReached)
{
_lockDistanceReached = true;
}
else if (distance < _lockDistance && _lockDistanceReached)
{
Close();
_lockDistanceReached = false;
}
}
private void UpdatePosition(float amount)
{
transform.position = ForwardPositionWorld + BackDirectionWorld * (amount + GrabOffset);
}
public void Lock()
{
_locked = true;
//Debug.Log($"lock");
}
public void Unlock()
{
_locked = false;
//Debug.Log($"unlock");
}
protected virtual bool CheckUnlock()
{
if (!LocksForward)
return true;
if (TriggerUnlocks)
{
if (Grabbable.HandGrabbers.Count > 0)
{
if (Grabbable.HandGrabbers[0].Controller.Trigger > TriggerThreshold)
{
return true;
}
}
}
else
{
if (_handAcceleration.magnitude > AccelerationThreshold)
{
return true;
}
}
return false;
}
protected virtual void CheckEject(float distance)
{
if (distance > _ejectDistance && !_ejectDistanceReached)
{
EjectReached.Invoke();
_ejectDistanceReached = true;
}
else if (distance < _ejectDistance && _ejectDistanceReached)
{
_ejectDistanceReached = false;
}
}
protected virtual void CheckChamberDistance(float distance)
{
if (distance > _chamberedRequiredDistance)
{
if (!_chamberDistanceReached)
{
_chamberDistanceReached = true;
}
}
else if (distance < _chamberedRequiredDistance && _chamberDistanceReached)
{
_chamberDistanceReached = false;
ChamberRound.Invoke();
}
}
protected virtual void ClampPullBack(float distance, Vector3 backDirection)
{
if (distance > _maximumDistance)
{
transform.localPosition = BackwardPosition;
}
}
protected virtual void UpdateHandTracking()
{
if (!Grabbable.IsBeingHeld)
return;
if (Grabbable.PrimaryGrabber.IsHandGrabber)
{
var hand = Grabbable.HandGrabbers[0];
var velocity = hand.TrackedController.position - _previousHandPosition;
_handAcceleration = (velocity - _previousVelocity) / Time.deltaTime;
_previousVelocity = velocity;
_previousHandPosition = hand.TrackedController.position;
}
}
protected virtual void MoveBolt()
{
if (Bolt)
{
if (_ejectDistance == 0f)
{
return;
}
var percent = Vector3.Distance(transform.localPosition, ForwardPosition) / _maximumDistance;
if (percent > .90)
{
Bolt.IsPushedBack = false;
}
if (Bolt.IsPushedBack && percent < .90)
return;
Bolt.Move(percent);
}
}
protected virtual void AnimateParts()
{
var percent = Vector3.Distance(transform.localPosition, ForwardPosition) / _maximumDistance;
foreach (var part in AnimatedParts)
{
part.Animate(percent, CycleDirection.Backward);
}
}
public void Move(float percent)
{
transform.localPosition = Vector3.Lerp(ForwardPosition, BackwardPosition, percent);
}
protected virtual void OnGrabbed(HVRHandGrabber grabber, HVRGrabbable slide)
{
GrabOffset = 0f;
if (ImmediateReleaseWhenOpen && EmptyOpen && LocksBack)
{
if (Bolt)
{
Bolt.IsPushedBack = false;
}
Grabbable.ForceRelease();
}
_previousHandPosition = grabber.TrackedController.position;
_previousVelocity = Vector3.zero;
if (Type == HVRCockingHandleType.Pump)
{
_grabbedPositionTracker.position = grabber.GrabPoint.position;
}
else
{
_grabbedPositionTracker.localPosition = transform.parent.InverseTransformPoint(_previousHandPosition);
GrabOffset = Vector3.Distance(ForwardPositionWorld, transform.position);
}
}
protected virtual void OnReleased(HVRHandGrabber grabber, HVRGrabbable slide)
{
EmptyOpen = false;
if (Type != HVRCockingHandleType.Pump)
{
Close();
}
}
public void Close()
{
if (_forwardRoutine != null)
return;
StartCoroutine(ForwardRoutine());
}
protected virtual IEnumerator ForwardRoutine()
{
Grabbable.CanBeGrabbed = false;
while (true)
{
var distance = Vector3.Distance(transform.localPosition, ForwardPosition);
var travel = ForwardSpeed * Time.deltaTime;
if (distance < travel)
{
transform.localPosition = ForwardPosition;
MoveBolt();
AnimateParts();
break;
}
transform.localPosition = Vector3.MoveTowards(transform.localPosition, ForwardPosition, travel);
AnimateParts();
MoveBolt();
yield return null;
}
_forwardRoutine = null;
Grabbable.CanBeGrabbed = true;
if (_chamberDistanceReached)
{
ChamberRound.Invoke();
}
Released.Invoke();
_chamberDistanceReached = false;
_ejectDistanceReached = false;
EmptyOpen = false;
if (LocksForward)
{
Lock();
}
}
public virtual void PushBack()
{
if (LocksBack)
{
transform.localPosition = EjectPosition;
EmptyOpen = true;
_chamberDistanceReached = true;
if (Bolt)
{
Bolt.Move(1f);
}
}
}
public override void Animate(float percent, CycleDirection direction)
{
if (Type == HVRCockingHandleType.Reciprocating)
{
Move(percent);
}
}
//public void OnDrawGizmosSelected()
//{
// Gizmos.color = Color.green;
// Gizmos.DrawCube(ForwardPositionWorld, new Vector3(.025f, .025f, .025f));
// Gizmos.color = Color.red;
// Gizmos.DrawCube(MaxPositionWorld, new Vector3(.025f, .025f, .025f));
// Gizmos.color = Color.yellow;
// Gizmos.DrawCube(transform.parent.TransformPoint(EjectPosition), new Vector3(.025f, .025f, .025f));
// Gizmos.color = Color.cyan;
// Gizmos.DrawCube(transform.parent.TransformPoint(ChamberRoundPosition), new Vector3(.025f, .025f, .025f));
//}
}
public enum HVRCockingHandleType
{
Reciprocating, NonReciprocating, Pump
}
}