0
\$\begingroup\$

I have an XR Grab Interactable wrench and the grab is working fine, as is movement. I'd like to use it to smash some chains, so I figured that I could use an OnCollisionEnter on the chains, and check against the tag. Except... it doesn't seem to actually register a collision at all unless I drop the wrench. What's the best way to detect a hit on a swing? And would it be possible to require a certain amount of speed of movement to trigger the collision?

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

The way I would do this is to cache the position and rotation of the wrench at the end of each Update or FixedUpdate (depending on when your wrench moves). At the start of the next step, calculate the position of the end of the wrench in its previous and current orientations, then check a capsule of space encompassing those two points, to see if it intersects your chain.

This will catch a collision at the start/end of the interval, as well as any object the end "skipped over" in between, approximating the swing as a straight-line movement.

You can repeat this check for points along the handle of the wrench too, to make sure you don't miss a chain when you over-reach with the tip.

You can also break this into sub-steps to better approximate the wrench's arc, if you find players are swinging it so fast that it swings through a significant fraction of the circle in a single step.

Lastly, you can estimate the impact velocity using the point's travel divided by the timestep.

Here's some example code that does this:

public class SwingDetector {

   [System.Serializable]
   public struct CheckPoint {
       public Vector3 localPosition;
       public float radius;
   }

   public LayerMask whatToHit;
   public CheckPoint[] points;

   Vector3 _oldPosition;
   Quaternion _oldOrientation;

   public bool CheckHit(float deltaTime, out RaycastaHit hit, out Vector3 velocity) {
       hit = default;
       velocity = default;
       bool hitSomething = false;

       foreach(var point in points) {
           Vector3 before = _oldPosition + _oldOrientation * point.localPosition;
           Vector3 after = transform.TransformPoint(point.localPosition);

           Vector3 travel = after - before;

           float distance = travel.magnitude;

           if(Physics.SphereCast(before, point.radius, travel, out hit, distance, whatToHit)) {
               velocity = travel / deltaTime;       
               hitSomething = true;
               break;
           }
       }
       transform.GetPositionAndRotation(out _oldPosition, out _oldOrientation);

       return hitSomething;
   }
}
\$\endgroup\$
0
\$\begingroup\$

Ultimately, I wound up using a Trigger check instead of Collision. It works, although I did not bother with implementing the velocity check.

private void OnTriggerEnter(Collider other)
{
    GameObject obj = other.gameObject;
    
    if (obj.CompareTag("SmashTool"))
    {
        // Since the chains can only be smashed if both locks have been removed
        if (_locks <= 0)
        {
            gameObject.GetComponent<MeshCollider>().enabled = false;
            gameObject.GetComponent<MeshRenderer>().enabled = false;
            Destroy(gameObject, 5f); // Leaving enough time for the sound file

            _audioSource.Play();

            _doorAnimator.SetTrigger("OpenDoor");
        }
        else
        {
            // Play sound?
            // Give hint?
        }
    }
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .