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#
459 lines
20 KiB
C#
6 months ago
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|