You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

459 lines
20 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using NaughtyAttributes;
namespace Autohand {
[DefaultExecutionOrder(2)]
[HelpURL("https://app.gitbook.com/s/5zKO0EvOjzUDeT2aiFk3/auto-hand/grabbable/distance-grabbing")]
public class HandDistanceGrabber : MonoBehaviour {
[Header("Hands")]
[Tooltip("The primaryHand used to trigger pulling or flicking")]
public Hand primaryHand;
[Tooltip("This is important for catch assistance")]
public Hand secondaryHand;
[Header("Pointing Options")]
public Transform forwardPointer;
public LineRenderer line;
[Space]
public float maxRange = 5;
[Tooltip("Defaults to grabbable on start if none")]
public LayerMask layers;
[Space]
public Material defaultTargetedMaterial;
[Tooltip("The highlight material to use when pulling")]
public Material defaultSelectedMaterial;
[Header("Pull Options")]
public bool useInstantPull = false;
[Tooltip("If false will default to distance pull, set pullGrabDistance to 0 for instant pull on select")]
public bool useFlickPull = false;
[Tooltip("The magnitude of your hands angular velocity for \"flick\" to start")]
[ShowIf("useFlickPull")]
public float flickThreshold = 7f;
[Tooltip("The amount you need to move your hand from the select position to trigger the grab")]
[HideIf("useFlickPull")]
public float pullGrabDistance = 0.1f;
[Space]
[Tooltip("If this is true the object will be grabbed when entering the radius")]
public bool instantGrabAssist = true;
[Tooltip("The radius around of thrown object")]
public float catchAssistRadius = 0.2f;
[AutoToggleHeader("Show Events")]
public bool showEvents = true;
[ShowIf("showEvents")]
public UnityHandGrabEvent OnPull;
[ShowIf("showEvents")]
public UnityHandEvent StartPoint;
[ShowIf("showEvents")]
public UnityHandEvent StopPoint;
[ShowIf("showEvents"), Tooltip("Targeting is started when object is highlighted")]
public UnityHandGrabEvent StartTarget;
[ShowIf("showEvents")]
public UnityHandGrabEvent StopTarget;
[ Tooltip("Selecting is started when grab is selected on highlight object")]
[ShowIf("showEvents")]
public UnityHandGrabEvent StartSelect;
[ShowIf("showEvents")]
public UnityHandGrabEvent StopSelect;
List<CatchAssistData> catchAssisted;
DistanceGrabbable targetingDistanceGrabbable;
DistanceGrabbable selectingDistanceGrabbable;
float catchAssistSeconds = 3f;
bool pointing;
bool pulling;
Vector3 startPullPosition;
RaycastHit hit;
Quaternion lastRotation;
private RaycastHit selectionHit;
float selectedEstimatedRadius;
float startLookAssist;
bool lastInstantPull;
GameObject _hitPoint;
Coroutine catchAssistRoutine;
private DistanceGrabbable catchAsistGrabbable;
private CatchAssistData catchAssistData;
GameObject hitPoint {
get {
if(!gameObject.activeInHierarchy)
return null;
if(_hitPoint == null) {
_hitPoint = new GameObject();
_hitPoint.name = "Distance Hit Point";
return _hitPoint;
}
return _hitPoint;
}
}
void Start() {
catchAssisted = new List<CatchAssistData>();
if(layers == 0)
layers = LayerMask.GetMask(Hand.grabbableLayerNameDefault);
if(useInstantPull)
SetInstantPull();
}
private void OnEnable() {
primaryHand.OnTriggerGrab += TryCatchAssist;
if(secondaryHand != null)
secondaryHand.OnTriggerGrab += TryCatchAssist;
primaryHand.OnBeforeGrabbed += (hand, grabbable) => { StopPointing(); CancelSelect(); };
}
private void OnDisable() {
primaryHand.OnTriggerGrab -= TryCatchAssist;
if(secondaryHand != null)
secondaryHand.OnTriggerGrab -= TryCatchAssist;
primaryHand.OnBeforeGrabbed -= (hand, grabbable) => { StopPointing(); CancelSelect(); };
if(catchAssistRoutine != null) {
StopCoroutine(catchAssistRoutine);
catchAssistRoutine = null;
catchAsistGrabbable.grabbable.OnGrabEvent -= (hand, grabbable) => { if(catchAssisted.Contains(catchAssistData)) catchAssisted.Remove(catchAssistData); };
catchAsistGrabbable.OnPullCanceled -= (hand, grabbable) => { if(catchAssisted.Contains(catchAssistData)) catchAssisted.Remove(catchAssistData); };
}
}
void Update() {
CheckDistanceGrabbable();
if(lastInstantPull != useInstantPull) {
if(useInstantPull) {
useFlickPull = false;
pullGrabDistance = 0;
}
lastInstantPull = useInstantPull;
}
}
private void OnDestroy() {
Destroy(hitPoint);
}
public void SetInstantPull() {
useInstantPull = true;
}
public void SetPull(float distance) {
useInstantPull = false;
useFlickPull = false;
pullGrabDistance = distance;
}
public void SetFlickPull(float threshold) {
useInstantPull = false;
useFlickPull = true;
flickThreshold = threshold;
}
void CheckDistanceGrabbable() {
if(!pulling && pointing && primaryHand.holdingObj == null) {
bool didHit = Physics.SphereCast(forwardPointer.position, 0.03f, forwardPointer.forward, out hit, maxRange, layers);
DistanceGrabbable hitGrabbable;
GrabbableChild hitGrabbableChild;
if(didHit) {
if(hit.transform.CanGetComponent(out hitGrabbable)) {
if(targetingDistanceGrabbable == null || hitGrabbable.GetInstanceID() != targetingDistanceGrabbable.GetInstanceID())
{
StartTargeting(hitGrabbable);
}
}
else if(hit.transform.CanGetComponent(out hitGrabbableChild)) {
if(hitGrabbableChild.grabParent.transform.CanGetComponent(out hitGrabbable)) {
if(targetingDistanceGrabbable == null || hitGrabbable.GetInstanceID() != targetingDistanceGrabbable.GetInstanceID())
{
StartTargeting(hitGrabbable);
}
}
}
else if(targetingDistanceGrabbable != null && hit.transform.gameObject.GetInstanceID() != targetingDistanceGrabbable.gameObject.GetInstanceID())
{
StopTargeting();
}
}
else {
StopTargeting();
}
if(line != null) {
if(didHit) {
line.positionCount = 2;
line.SetPositions(new Vector3[] { forwardPointer.position, hit.point });
}
else {
line.positionCount = 2;
line.SetPositions(new Vector3[] { forwardPointer.position, forwardPointer.position + forwardPointer.forward * maxRange });
}
}
}
else if(pulling && primaryHand.holdingObj == null) {
if(useFlickPull) {
TryFlickPull();
}
else {
TryDistancePull();
}
}
else if(targetingDistanceGrabbable != null) {
StopTargeting();
}
}
public virtual void StartPointing() {
pointing = true;
StartPoint?.Invoke(primaryHand);
}
public virtual void StopPointing() {
pointing = false;
if(line != null) {
line.positionCount = 0;
line.SetPositions(new Vector3[0]);
}
StopPoint?.Invoke(primaryHand);
StopTargeting();
}
public virtual void StartTargeting(DistanceGrabbable target) {
if(target.enabled && primaryHand.CanGrab(target.grabbable)) {
if(targetingDistanceGrabbable != null)
StopTargeting();
targetingDistanceGrabbable = target;
targetingDistanceGrabbable?.grabbable.Highlight(primaryHand, GetTargetedMaterial(targetingDistanceGrabbable));
targetingDistanceGrabbable?.StartTargeting?.Invoke(primaryHand, target.grabbable);
StartTarget?.Invoke(primaryHand, target.grabbable);
}
}
public virtual void StopTargeting() {
targetingDistanceGrabbable?.grabbable.Unhighlight(primaryHand, GetTargetedMaterial(targetingDistanceGrabbable));
targetingDistanceGrabbable?.StopTargeting?.Invoke(primaryHand, targetingDistanceGrabbable.grabbable);
if(targetingDistanceGrabbable != null)
StopTarget?.Invoke(primaryHand, targetingDistanceGrabbable.grabbable);
else if(selectingDistanceGrabbable != null)
StopTarget?.Invoke(primaryHand, selectingDistanceGrabbable.grabbable);
targetingDistanceGrabbable = null;
}
public virtual void SelectTarget() {
if(targetingDistanceGrabbable != null) {
pulling = true;
startPullPosition = primaryHand.transform.localPosition;
lastRotation = transform.rotation;
selectionHit = hit;
if(catchAssistRoutine == null) {
hitPoint.transform.position = selectionHit.point;
hitPoint.transform.parent = selectionHit.transform;
}
selectingDistanceGrabbable = targetingDistanceGrabbable;
selectedEstimatedRadius = Vector3.Distance(hitPoint.transform.position, selectingDistanceGrabbable.grabbable.body.transform.position);
selectingDistanceGrabbable.grabbable.Unhighlight(primaryHand, GetTargetedMaterial(selectingDistanceGrabbable));
selectingDistanceGrabbable.grabbable.Highlight(primaryHand, GetSelectedMaterial(selectingDistanceGrabbable));
selectingDistanceGrabbable?.StartSelecting?.Invoke(primaryHand, selectingDistanceGrabbable.grabbable);
targetingDistanceGrabbable?.StopTargeting?.Invoke(primaryHand, selectingDistanceGrabbable.grabbable);
targetingDistanceGrabbable = null;
StartSelect?.Invoke(primaryHand, selectingDistanceGrabbable.grabbable);
StopPointing();
}
}
public virtual void CancelSelect() {
StopTargeting();
pulling = false;
selectingDistanceGrabbable?.grabbable.Unhighlight(primaryHand, GetSelectedMaterial(selectingDistanceGrabbable));
selectingDistanceGrabbable?.StopSelecting?.Invoke(primaryHand, selectingDistanceGrabbable.grabbable);
if(selectingDistanceGrabbable != null)
StopSelect?.Invoke(primaryHand, selectingDistanceGrabbable.grabbable);
selectingDistanceGrabbable = null;
}
public virtual void ActivatePull() {
if(selectingDistanceGrabbable) {
OnPull?.Invoke(primaryHand, selectingDistanceGrabbable.grabbable);
selectingDistanceGrabbable.OnPull?.Invoke(primaryHand, selectingDistanceGrabbable.grabbable);
if(selectingDistanceGrabbable.instantPull) {
selectingDistanceGrabbable.grabbable.body.velocity = Vector3.zero;
selectingDistanceGrabbable.grabbable.body.angularVelocity = Vector3.zero;
selectionHit.point = hitPoint.transform.position;
if (selectingDistanceGrabbable.grabbable.placePoint != null)
selectingDistanceGrabbable.grabbable.placePoint.Remove();
primaryHand.Grab(selectionHit, selectingDistanceGrabbable.grabbable);
CancelSelect();
selectingDistanceGrabbable?.CancelTarget();
}
else if(selectingDistanceGrabbable.grabType == DistanceGrabType.Velocity) {
catchAssistRoutine = StartCoroutine(StartCatchAssist(selectingDistanceGrabbable, selectedEstimatedRadius));
catchAsistGrabbable = selectingDistanceGrabbable;
if (selectingDistanceGrabbable.grabbable.placePoint != null)
{
selectingDistanceGrabbable.grabbable.placePoint.Remove();
}
selectingDistanceGrabbable.SetTarget(primaryHand.palmTransform);
}
else if(selectingDistanceGrabbable.grabType == DistanceGrabType.Linear) {
selectingDistanceGrabbable.grabbable.body.velocity = Vector3.zero;
selectingDistanceGrabbable.grabbable.body.angularVelocity = Vector3.zero;
selectionHit.point = hitPoint.transform.position;
if (selectingDistanceGrabbable.grabbable.placePoint != null)
selectingDistanceGrabbable.grabbable.placePoint.Remove();
primaryHand.Grab(selectionHit, selectingDistanceGrabbable.grabbable, GrabType.GrabbableToHand);
CancelSelect();
selectingDistanceGrabbable?.CancelTarget();
}
CancelSelect();
}
}
void TryDistancePull() {
if(Vector3.Distance(startPullPosition, primaryHand.transform.localPosition) > pullGrabDistance) {
ActivatePull();
}
}
void TryFlickPull() {
Quaternion deltaRotation = transform.rotation * Quaternion.Inverse(lastRotation);
lastRotation = transform.rotation;
var getAngle = 0f;
Vector3 getAxis = Vector3.zero;
deltaRotation.ToAngleAxis(out getAngle, out getAxis);
getAngle *= Mathf.Deg2Rad;
float speed = (getAxis * getAngle * (1f / Time.deltaTime)).magnitude;
if(speed > flickThreshold || useInstantPull) {
if(selectingDistanceGrabbable) {
ActivatePull();
}
}
}
Material GetSelectedMaterial(DistanceGrabbable grabbable) {
if(grabbable.ignoreHighlights)
return null;
return grabbable.selectedMaterial != null ? grabbable.selectedMaterial : defaultSelectedMaterial;
}
Material GetTargetedMaterial(DistanceGrabbable grabbable) {
if(grabbable.ignoreHighlights)
return null;
return grabbable.selectedMaterial != null ? grabbable.targetedMaterial : defaultTargetedMaterial;
}
void TryCatchAssist(Hand hand, Grabbable grab) {
for(int i = 0; i < catchAssisted.Count; i++) {
var distance = Vector3.Distance(hand.palmTransform.position + hand.palmTransform.forward * catchAssistRadius, catchAssisted[i].grab.transform.position) - catchAssisted[i].estimatedRadius;
if(distance < catchAssistRadius) {
Ray ray = new Ray(hand.palmTransform.position, hitPoint.transform.position - hand.palmTransform.position);
if(Physics.SphereCast(ray, 0.03f, out var catchHit, catchAssistRadius * 2, LayerMask.GetMask(Hand.grabbableLayerNameDefault, Hand.grabbingLayerName))) {
if(catchHit.transform.gameObject == catchAssisted[i].grab.gameObject) {
catchAssisted[i].grab.body.velocity = Vector3.zero;
catchAssisted[i].grab.body.angularVelocity = Vector3.zero;
hand.Grab(catchHit, catchAssisted[i].grab);
CancelSelect();
}
}
}
}
}
IEnumerator StartCatchAssist(DistanceGrabbable grab, float estimatedRadius) {
catchAssistData = new CatchAssistData(grab.grabbable, catchAssistRadius);
catchAssisted.Add(catchAssistData);
grab.grabbable.OnGrabEvent += (hand, grabbable) => { if(catchAssisted.Contains(catchAssistData)) catchAssisted.Remove(catchAssistData); };
grab.OnPullCanceled += (hand, grabbable) => { if(catchAssisted.Contains(catchAssistData)) catchAssisted.Remove(catchAssistData); };
if(instantGrabAssist) {
bool cancelInstantGrab = false;
var time = 0f;
primaryHand.OnTriggerRelease += (hand, grabbable) => { cancelInstantGrab = true; };
while(time < catchAssistSeconds && !cancelInstantGrab) {
time += Time.fixedDeltaTime;
if(TryCatch(primaryHand))
break;
bool TryCatch(Hand hand) {
var distance = Vector3.Distance(hand.palmTransform.position + hand.palmTransform.forward * catchAssistRadius, grab.transform.position) - estimatedRadius;
if(distance < catchAssistRadius) {
Ray ray = new Ray(hand.palmTransform.position, hitPoint.transform.position - hand.palmTransform.position);
var hits = Physics.SphereCastAll(ray, 0.03f, catchAssistRadius * 2, LayerMask.GetMask(Hand.grabbableLayerNameDefault, Hand.grabbingLayerName));
for(int i = 0; i < hits.Length; i++) {
if(hits[i].transform.gameObject == grab.gameObject) {
grab.grabbable.body.velocity = Vector3.zero;
grab.grabbable.body.angularVelocity = Vector3.zero;
hand.Grab(hits[i], grab.grabbable);
grab.CancelTarget();
CancelSelect();
return true;
}
}
}
return false;
}
yield return new WaitForEndOfFrame();
}
primaryHand.OnTriggerRelease -= (hand, grabbable) => { cancelInstantGrab = true; };
}
else
yield return new WaitForSeconds(catchAssistSeconds);
grab.grabbable.OnGrabEvent -= (hand, grabbable) => { if(catchAssisted.Contains(catchAssistData)) catchAssisted.Remove(catchAssistData); };
grab.OnPullCanceled -= (hand, grabbable) => { if(catchAssisted.Contains(catchAssistData)) catchAssisted.Remove(catchAssistData); };
if(catchAssisted.Contains(catchAssistData))
catchAssisted.Remove(catchAssistData);
catchAssistRoutine = null;
}
private void OnDrawGizmosSelected() {
if(primaryHand)
Gizmos.DrawWireSphere(primaryHand.palmTransform.position + primaryHand.palmTransform.forward * catchAssistRadius * 4 / 5f + primaryHand.palmTransform.up * catchAssistRadius * 1 / 4f, catchAssistRadius);
}
}
struct CatchAssistData {
public Grabbable grab;
public float estimatedRadius;
public CatchAssistData(Grabbable grab, float estimatedRadius) {
this.grab = grab;
this.estimatedRadius = estimatedRadius;
}
}
}