Tag: colliders

Rigidbody Based Pick Up and Throw

Rigidbody Based Pick Up and Throw

As the Rigidbody version of the Player continues to develop (full functionality breakdown to follow) the concept of being able to pick up and throw interactable objects now has some solutions.

(Its also worth noting here that the prototype illustrated also has a change color on raycast hit function that I’ll cover elsewhere.)

This added functionality has been on the dev list for a while so its good to get a working prototype up and running.

Development and Code

The basic pseudo code looks something like this:

if isInteracting && !isHoldingObject
check the raycast hit object is Interactable
move the object to the player

isHoldingObject = true
toggle isInteracting

if IsInteracting && isHoldingObject
AddForce to rigidbody based on PlayerCamera transform

isHoldingObject = false
toggle isInteracting

That seems simple enough but I quickly ran into issues specifically around getting more used to handling rigidbodies in FixedUpdate().

This answer on Unity forum was a great help:
https://answers.unity.com/questions/1650650/throwing-object-with-force.html

But before the specifics, one obvious issue with my pseudo code was the move function would put the selected object on top the player – so firstly I needed to set up an offset that would also act as a target to which any selected object could be moved.

Easiest way to do that was to create an Empty +10u on player.z

  • Player hand is the target for any selected objects
  • Its a child of the player camera in order to inherit position and rotation that equates to the ‘look’ direction/rotation of the player. Used to set throw direction.
  • has Trigger that detects when the selected object has reached the ‘hold’ position
  • Layer is set to ‘Ignore Raycast’ (a built in Layer in Unity) that allows the raycast from the player to ignore this object
  • Attached script is used for OntriggerEnter mointoring

Once this was set up I was able to set up and work on the PickAndThrow script attached directly to the Player Object. After a few days of some serious wrestling with this script, below is a working prototype with plenty of commentary that explains the process step by step:

public class PickUpandThrow_PlayerRB : MonoBehaviour
{
   //Get references.....

    private void Start()
    {
        input = GetComponent<InputManager>();
        playerManager = GetComponent<PlayerManager>();
        cam = GetComponentInChildren<Camera>();
        audioSource = GetComponentInChildren<AudioSource>();

        playerHand = GameObject.Find("PlayerHand").transform;
        playerHand_CollisionDetector =
            FindObjectOfType<PlayerHand_CollisionDetector>();
    }

    private void FixedUpdate()
    {
        //Check player hand object for collision with hit RB:
        hitObjectIsAtHand = playerHand_CollisionDetector.hitInteractable;

        if (input.isInteracting && !isHoldingObject)
        {
            //we can pick up the object if we can see it:
            if (playerManager.rayCastHit != null
                && playerManager.rayCastHit.CompareTag("Interactable"))
            {
                //set the RB of target object received from the raycast
                //in PlayerManager:
                hitObjectRigidBody =
                   playerManager.rayCastHit.GetComponent<Rigidbody>();

                //toggle interact button
                input.isInteracting = false;

                //we are now holding the object
                isHoldingObject = true;

                //play audio pick up FX:
                //This clip needs to be adjusted based on difference between
                //hand and object (* pitch)
                audioSource.PlayOneShot(audioClip[0]);
            }
        }

        //Pick up the object
        if (isHoldingObject && !hitObjectIsAtHand)
        {
            //make isKinematic to cancel physics interactions:
            hitObjectRigidBody.isKinematic = true;

            //correct orientation ready for throwing
            hitObjectRigidBody.MoveRotation(Quaternion.Euler(Vector3.zero));

            //move towards player hand
            hitObjectRigidBody.MovePosition(Vector3.MoveTowards(
                hitObjectRigidBody.position,
                playerHand.position,
                pickUpSpeed * Time.fixedDeltaTime));
        }


        if (isHoldingObject && hitObjectIsAtHand)
        {
            //if object has reached player hand, disable its movement
            //by 'teleporting' it into position. 
            hitObjectRigidBody.MovePosition(playerHand.position);
        }

        if (input.isInteracting && isHoldingObject)
        {
            //at this point the object is being thrown so re-enable
            //all physics properties by setting isKinematic = false:
            hitObjectRigidBody.isKinematic = false;

            //Fire the object away (forward)
            //Add force relative to camera in order to take account
            //of rotation.x ('look up')
            hitObjectRigidBody.AddRelativeForce(
                (cam.transform.forward * throwSpeedForward)
                + (cam.transform.up * throwSpeedUp), ForceMode.Impulse);

            //we are no longer holding the object
            isHoldingObject = false;

            //toggle interact button
            input.isInteracting = false;

            audioSource.PlayOneShot(audioClip[1]);
        }
    }
}

One of the main issues here was getting the selected object to Move to the PlayerHand position. Thsi was a lack of understanding of how MovePosition) works – If the rigidbody has isKinematic set to false, RB.MovePosition works like transform.position=newPosition and ‘teleports’ the object to the new position, rather than performing a smooth transition over FixedTime.

The other referenced script of interest here is the PlayerHandCollisionDetector attached (quite logically) to the PlayerHand:

public class PlayerHand_CollisionDetector : MonoBehaviour
{
    PickUpandThrow_PlayerRB pickUpandThrow;
    public bool hitInteractable;

    public void Start()
    {
        //Get the RB from pickUpAndThrow NOT playerManager!!!!
        pickUpandThrow = FindObjectOfType<PickUpandThrow_PlayerRB>();
    }

    private void OnTriggerEnter(Collider collider)
    {
        if (pickUpandThrow.hitObjectRigidBody != null)
        {
            //Check if the incoming collider is the SAME as the stored
            //rigidbody from pickUpandThrow - the RB is constant as its
            //NOT being updated by the raycast.

            //This comparison will avoid false reading from the bool that arose
            //from using the rayCastHit object from playerManager
            if (collider.transform == pickUpandThrow.hitObjectRigidBody.transform)
            {
                //toggle the hit  - BECAUSE THIS IS A TRIGGER!!!!!
                hitInteractable = !hitInteractable;
            }
        }
    }
}

Its easily seen some of the issues I had here! Just making the point that the rigidbody we need to compare is derived from the PickUpandThrow class as the reference there is stable. As opposed to getting the reference from the PlayerManager where the reference is being updated by the raycast.

Summing up

While this setup is functional enough there are some issues that need to be ironed out:

Kinematic Objects don’t care about your colliders!!!
  • selected object can move through other colliders when being ‘carried’ by the player. This due to rigidbody.isKinematic set to true on selection. A solution that comes to mind is to create another empty child of the Player with a box collider that dynamically scales to cover the area of Player + Object. This collider can be activated when hitObjectIsAtHand ?
  • thrown objects sometimes go in unexpected/inaccurate directions. This is due to the AddForce function of the rigiidbody being calculated using cam.transformDirection * a hard coded value, instead of calculating the up value based on camera.rotation.x
  • the standalone nature of this element of the player will be addressed by refactoring and using it as a called method from PlayerManger. In this way, PlayerManager makes conditional calls to other classes which ‘should’ help with efficiency.
Custom Colliders for RigidBodies

Custom Colliders for RigidBodies

While making good progress developing a new RB version of the player (which I’ll post about elsewhere), I stumbled on the issue that the Daleks solved back in the 80s – stairs!!!

After some searching through the forums I found the simplest solution (these things usually end up being quite simple) that I wanted to record here.

The issue is that when importing/using custom objects, Unity will automatically generate a mesh collider for the object using the objects’ base mesh. Thats great and makes for realistic interactions…if you’re using the character controller. Not so great if using a RB based controller as the collision will stop the player dead.

At the start of this project that was one of my reasons for going with a CC based controller, but I wanted to experiment with a RB controller and I’ve found that I prefer it – especially for physics interactions that seem much more fluid, realistic and immersive.

At this point, my RB player is using about 150 lines of code in a player manager along with a few classes ( of < 60 lines of code each) handling things like movement, power updates, raycasts, and writing some outputs to the UI. This compares to my CC main class that was > 880+ lines of code plus extra classes for raycasting etc.

In fact the new RB controller does everything the old CC based controller did, but with a better ‘feel’ , less code (therefore less bug tracking issues) and less (Ahem…..) ‘physics malfunctions‘.

  • I use the term ‘less physics malfunctions’ there as opposed to ‘no physics malfunctions……where the term ‘malfunction’ is best defined as shouting ‘What the hell is going on!!!!’ quite loudly…….

So thats all good apart from the stairs.

The solution is simply to replace the mesh collider generated by Unity with a (or series of) primitive collider(s) (in this case box colliders) as required to ‘approximate’ the overall shape of the mesh.

Colliders are added as Empty children of the mesh and adjusted using their transforms to create a RB friendly shape:

Hierarchy view of stairs and colliders
Side view: fist collider is just a box (in green) that covers the last (topmost) stair
Side View: 2nd collider is a rotated box collider (in green) that changes the ‘surface’ into a ramp
Example of RB player navigating stairs. Left stairs have mesh collider, right stairs have simplified colliders that create a ramp.

So thats it – the simple primitive based series of colliders transforms the stairs into a ramp that an RB can climb.

Some extra trickery can help achieve ‘stair-ness’ such as running a movement script/animation on the camera/player on entering the collider that takes into account current velocity (now easily accessible from the RB player!) and adds an appropriate bob/move/climb action.

Just to prove the usefulness of this approach (esp as a dev tool) here’s the custom collider for the arrow attached to the RB player that I’m using to debug and test the controller:

Hierarchy of colliders
View of colliders (in green) on arrow

The only issue with this that I immediately foresee is the depth to which the 2nd collider ‘juts’ through the ground plane. Not a problem in my sandbox scene but may become a problem when developing an environment that has multiple levels. In that case the colliders may start to act on objects that are below them and provoke some weird behaviour. One solution may be to add more, gradually smaller colliders to the stairs so the depth of ground penetration can be minimised.