how to detect reach over shoulder?

i’d like to pick up ammo when i reach over my shoulder like in HL-A

i guess i could look at position of the none gun holding hand and see is its above and behind the camera but that seems like a hack. is their an elegant way to do it? thanks

actually i think i can put a trigger collider as a child of the XR rig behind the camera and simply detect stay events in there

1 Like

Yes Trigger events is a straightforward way to implement this. I would attach the trigger as a child of the Main Camera, this will make sure it moves when the player turns around.

If you’d like to integrate more with the XR interaction toolkit system, you can put a SimpleInteractable component on it and then use interaction layers to make it only react to Hover and Select events by the direct interactor (assuming you also have rays). That will help you detect when the user uses the grab action behind their back.

Depending on the headset and controller type, controllers sometimes lose tracking when they are not visible by some sensors, however usually it can estimate the controllers based on their acceleration sensors.

thanks i got it working but i think you are correct about the controller behind the head problem. what i find is holding the controller inside the trigger zone i get multiple entry and exit events which cases me to instantiate hundreds of ammo mags

so what i did was use a flag (actually timer = -1 is the flag, see below) in the trigger enter to detect first entry and only the instantiate the mag. i basically ignore exit triggers, instead i use a coroutine started when i instantiate the mag that waits about 1/2 sec. if the player hasn’t grabbed the mag in that time, i destroy it. so long as i keep getting entry triggers, i reset the coroutine timer to 0 so i can wait in the ammo zone as long as necessary

BTW, the instantiated mag is set to child of the hand so i know its location will be in hand even if i can’t see it

public class HandInAmmoZone : MonoBehaviour
{
    public GameObject magazinePrefab;

 
    float timer = -1;   // -1 means not running
    public IEnumerator DetectLeaveAmmonZone()
    {

        while (timer < 1)
        {
            // do nothing, just wait
            timer += Time.deltaTime;
            yield return null;
        }

        // timer expired, if mag not picked up, destroy it
        timer = -1; // so can start over again next entry

        // need to find of mag was grabbed
        if (!globals.magazineInHand)
            yield return null;

        globals.SetHUDText("in DetectLeaveAmmonZone destroy ");
        Destroy(globals.magazineInHand);
        globals.magazineInHand = null;
    }



    private void OnTriggerEnter(Collider other)
    {
        Transform t = other.transform.parent;
       // globals.SetHUDText("in other = " + other);
       // globals.SetHUDText("in OnTriggerEnter parent = " + t);
        if (!t)
            return;

        if (!t.CompareTag("LHand") && !t.CompareTag("RHand"))
            return;


        if (timer < 0)
        {
            // start timer ob first entry only (avoid multiple entry/exit events while inside)
            timer = 0;
            StartCoroutine(DetectLeaveAmmonZone());
        }
        else
        {
            // because timer running already, rest set it back to start time
            timer = 0;
            return;
        }

        if (t.CompareTag("LHand") || t.CompareTag("RHand"))
        {
            //activeHandInAmmoZone = ActiveHandInAmmoZone.RHandInAmmoZone;
            globals.SetHUDText("in OnTriggerEnter parent = " + t + "tag=" + t.tag);
            globals.magazineInHand = Instantiate(magazinePrefab, t.position, t.rotation, t);
        }

    }

actually, i found the trigger to be a pain in the a because it rotates with the head movement up and down which i don’t want. so i found a better way, attaching a script the hand that looks like this

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEngine.GraphicsBuffer;

public class HandInAmmoZone : MonoBehaviour
{
    Globals globals;
 
    public GameObject magazinePrefab;
    public GameObject magazineOnHand;

    bool inAmmonZone = false;

    // Start is called before the first frame update
    void Start()
    {
        globals = GameObject.FindObjectOfType<Globals>();
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        Transform camera = transform.parent.GetChild(0); // camera is 1st child of CameraOffset
        Vector3 heading = transform.position - camera.transform.position;
        float dot = Vector3.Dot(heading, camera.transform.forward);

        //globals.SetHUDText("HandInAmmoZone", "FixedUpdate", "dot = " + dot); 

        if (dot < 0)
        {
            //globals.SetHUDText("HandInAmmoZon", "FixedUpdate", "hand pos = " + handPos);
            if (inAmmonZone)
                return; // alread in, not interested
            inAmmonZone = true;
            globals.SetHUDText("HandInAmmoZon", "FixedUpdate", "ENTER ZONE hand dot = " + dot);

            magazineOnHand = Instantiate(magazinePrefab, transform.position, transform.rotation, transform);
        }
        else
        {
            if (!inAmmonZone)
                return; // alread out, not  interested
            inAmmonZone = false;
            globals.SetHUDText("HandInAmmoZon", "FixedUpdate", "EXIT ZONE hand dot = " + dot);

            if(magazineOnHand)
            {
                Destroy(magazineOnHand);    // leaving without grabbing
                magazineOnHand = null;
            }
             
        }
        
    }
}

it checks if the hand is behind the camera and if so, instants a mag which you can grab

to use the mag i use this script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.AR;

public class Magazine : MonoBehaviour
{
    Globals globals;
    public int numberOfBullets = 8;

    GameObject savedMagInHand;

    void Start()
    {
        globals = GameObject.FindObjectOfType<Globals>();
        globals.SetHUDText("Magazine", "Start", "");

        XRGrabInteractable grabInteractable = GetComponent<XRGrabInteractable>();
        grabInteractable.selectEntered.AddListener(Grab);
        grabInteractable.selectExited.AddListener(Release);
     
     
    }
    public void Grab(BaseInteractionEventArgs arg)
    {
        globals.SetHUDText("public class Magazine : MonoBehaviour\r\n", "Grab", "interactableObject = " + arg.interactableObject.transform);
        globals.SetHUDText("public class Magazine : MonoBehaviour\r\n", "Grab", "interactorObjectParent = " +
            arg.interactorObject.transform.parent);

        HandInAmmoZone handInAmmoZone = arg.interactorObject.transform.parent.GetComponent<HandInAmmoZone>();

        if (!handInAmmoZone)
        {
            globals.SetHUDText("Magazine", "Grab", "no handInAmmoZone");
            return;
        }

        if (!handInAmmoZone.magazineOnHand)
        {
            globals.SetHUDText("Magazine", "Grab", "no magazineInHand");
            return;
        }
        globals.SetHUDText("Magazine", "Grab", "parent = " + handInAmmoZone.magazineOnHand.transform.parent);
        savedMagInHand = handInAmmoZone.magazineOnHand;
        handInAmmoZone.magazineOnHand.transform.parent = null;
        globals.SetHUDText("Magazine", "Grab", "2 parent = " + handInAmmoZone.magazineOnHand.transform.parent);

        handInAmmoZone.magazineOnHand = null; // this will stop the timer in HandInAmmoZone from destroying the mag
     
    }
    public void Release(BaseInteractionEventArgs arg)
    {

        globals.SetHUDText("Magazine", "Release", "enter");
        if (!savedMagInHand)
            return;

        Rigidbody rb = savedMagInHand.GetComponent<Rigidbody>();
        rb.isKinematic = false;
        globals.SetHUDText("Magazine", "Release", "rb.isKinematic = " + rb.isKinematic);
        savedMagInHand = null;

    }
    public void MagEmpty()
    {
        transform.GetChild(1).gameObject.SetActive(false);
    }
}
1 Like