Working on a physics interaction system. What am I doing wrong?

So I’m working on an interaction system similar to Amnesia: The Dark Descent. I’ve ALMOST got everything working. When I pick up an object I’m able to toss (Moving my mouse and releasing LMB) it correctly, however as I change my orientation the horizontal throwing becomes inconsistent. The object gets tossed in the wrong direction horizontally.

I’m basing the direction to throw the object based on the Camera’s right, but ApplyForce doesn’t seem to want to cooperate with me.

Here’s my interaction code. It’s a little bit messy but I’m focusing on just getting it working before cleaning it up:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class PL_PhysicsGrab : MonoBehaviour {

    Camera cam;

    public LayerMask grabLM;

    private Rigidbody grabbedRB;

    [HideInInspector]
    public bool objGrabbed;
    private float carryDist;

    private float zoomIncrement = 2f;
    private float minZoomDist = 1.25f;
    private float maxZoomDist;

    float grabDist = 2f;


    [SerializeField]
    private Sprite grabCusror;
    [SerializeField]
    private Sprite normalCursor;
    [SerializeField]
    private Image CursorImgGO;
    private bool objHovered;
   

    float rotY;
    float prevY;
    float distY;

    float rotX;
    float prevX;
    float distX;

    // Use this for initialization
    void Start () {
        cam = Camera.main;
        prevY = cam.transform.rotation.y;
        prevX = cam.transform.rotation.x;
        maxZoomDist = grabDist;
    }
   
    // Update is called once per frame
    void Update () {

        rotY = cam.transform.rotation.y;
        rotX = cam.transform.rotation.x;
        distY = (rotY - prevY) * 200;
        distX = (rotX - prevX) * -200;

        if (Input.GetMouseButtonDown(0))
        {
            Grab();
        }
        else if (Input.GetMouseButtonUp(0) && grabbedRB)
        {
            Release();
        }

        if(Input.GetMouseButtonDown(1) && grabbedRB)
        {
            Throw();
        }

        Scroll();

        MoveObject();

        prevY = cam.transform.rotation.y;
        prevX = cam.transform.rotation.x;
    }

    private void FixedUpdate()
    {
        if (grabbedRB && grabbedRB.angularVelocity != Vector3.zero)
        {
            grabbedRB.angularVelocity = Vector3.MoveTowards(grabbedRB.angularVelocity, Vector3.zero, 1);
        }
    }

    void MoveObject()
    {
        if (objGrabbed)
        {
            grabbedRB.position = Vector3.Lerp(grabbedRB.position, cam.transform.position + cam.transform.forward * carryDist, Time.deltaTime * 35);
        }
    }

    void Scroll()
    {
        float wheel = Input.GetAxis("Mouse ScrollWheel");
        if (wheel != 0)
        {
            //carryDist = Mathf.Clamp(carryDist + wheel, minZoom, maxZoom);
            float dist = Vector3.Distance(cam.transform.position, grabbedRB.position);
            if(wheel < 0 && dist > minZoomDist)
            {
                carryDist += wheel;
            }else if(wheel > 0 && dist < maxZoomDist)
            {
                carryDist += wheel;
            }
        }
    }

    void Grab()
    {
        distX = 0;
        distY = 0;

        RaycastHit hit;
        Ray ray = cam.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));

        if (Physics.Raycast(ray, out hit, grabDist, grabLM))
        {
            Debug.Log("Grabbed " + hit.transform.name);
            grabbedRB = hit.transform.GetComponentInChildren<Rigidbody>();
            grabbedRB.velocity = Vector3.zero;
            carryDist = Vector3.Distance(cam.transform.position, grabbedRB.transform.position);

            objGrabbed = true;
            grabbedRB.useGravity = false;
        }
    }

    void Release()
    {
        Vector3 throwVector = (cam.transform.right * (distY)) + (Vector3.up * (distX));

        objGrabbed = false;
        grabbedRB.useGravity = true;
        grabbedRB.AddForce(throwVector, ForceMode.Impulse);
        //grabbedRB.velocity = throwVector;
        grabbedRB = null;
    }

    void Throw()
    {
        objGrabbed = false;
        grabbedRB.useGravity = true;

        grabbedRB.velocity = Vector3.zero;
        grabbedRB.AddForce(cam.transform.forward * 10, ForceMode.Impulse);

        grabbedRB = null;
    }
}

Any help is appreciated, and I’m more than happy to clear anything up that might be confusing. Thanks!

I don’t know if we’re allowed to bump threads on here, but I am really stumped.

I only glanced at the code, but why the camera’s right? Why not relative to the object itself? If it stays fixed in the player’s hands/claws/telekinetic power, the directions should also rotate with the view.

Also: A day and a half bump seems perfectly acceptable. It’s the 5-60 minute bumps which annoy :wink:

1 Like

Well I tried throwing based on the object’s right, however I wanted to eventually have the player be able to rotate the object (to inspect things), and in that situation it could be a bit inconsistent. My line of thinking was that if the player is flicking their mouse right, then they want to throw it to their right, although apparently that doesnt’t work as straightforward as I had initially thought.

At one point I did use transform.LookAt to have the object always face the player, but that caused my framerate to plummet for some reason.

Thanks for the reply :slight_smile:

What is release? Is that throw horizontal? So you want to throw something to right?

Anyways it’s probably because you are using rotation which is a quaternion.
So the xyzw are not what you think they are.

You should be using one of the Euler values to get rotation in a unit that makes sense.
Also if you do that you may need to worry about wrapping. Depending on how your rotation is setup.
0 - 360, 0 - 360.

You could just store the last direction and then get the angle between them to scale I am guessing your force of the release?

1 Like

So release is if you let go of LMB while moving your mouse. If you’re moving your mouse up, it will throw the object up. If you’re moving the mouse right it will throw right. Sort of as if the object is inheriting the velocity of your mouse when you let go of it. That’s release. Throw is RMB and that just throws the object forward with a bit of force.

Thanks for the rotation heads up, I always forget about Euler angles. I will give that a try and report back.

@daxiongmao You’re my hero! Switching over to Euler angles did the trick. It’s always the little things that always seem to do it.

Here’s how the updated (and commented) code turned out. Thanks again!

using UnityEngine;
using UnityEngine.UI;

//! Thanks to daxiongmao for the help!
public class PL_PhysicsGrabv2 : MonoBehaviour {

    // Player's Camera
    Camera cam;

    // Layermask containing the layers we can grab.
    public LayerMask grabLM;

    // The rigidbody we have grabbed
    private Rigidbody grabbedRB;

    // Has an object been grabbed?
    [HideInInspector]
    public bool objGrabbed;
    // How far should the object be from our face?
    private float carryDist;

    // Zoom stuff
    private float zoomIncrement = 2f;
    private float minZoomDist = 1.25f;
    private float maxZoomDist;

    // Distance from which we can grab an object.
    float grabDist = 2f;

    // For the Cursor Manager.
    private bool objHovered;

    // Previous Camera Rotation & Current Camera Rotation.
    private Vector3 prevRotation;
    private Vector3 currentRotation;

    // Unlike the previous version of this script, we're going to name these intuitively.
    // xDist is the distance the mouse has moved horizontally in 2d space, yDist is vertical.
    private float xDist;
    private float yDist;

    void Start () {
        cam = Camera.main;
        maxZoomDist = grabDist;
        prevRotation = cam.transform.rotation.eulerAngles;
    }
   
    void Update () {

        currentRotation = cam.transform.rotation.eulerAngles;

        //! This looks strange, but remember how the Unity axis are set up.
        //! Y in our situation is LR and X is UD. Confusing, but simple once you remember it.
        xDist = currentRotation.y - prevRotation.y;
        yDist = currentRotation.x - prevRotation.x;
        // Invert yDist to make things work properly. Can be done on above line but I like to be explicitly clear or I'll forget.
        yDist = -yDist;

        // Check our inputs.
        ReadInput();

        // Allows us to bring the object closer & further away.
        Scroll();

        // Moves the picked up object (if we have one).
        MoveObject();

        prevRotation = cam.transform.rotation.eulerAngles;
    }

    private void FixedUpdate()
    {
        // If we've picked up an object and it is rotating (We've picked up a rolling ball for example)
        if (grabbedRB && grabbedRB.angularVelocity != Vector3.zero)
        {
            // Move towards an angular Velocity of 0 so the object stops rotating.
            grabbedRB.angularVelocity = Vector3.MoveTowards(grabbedRB.angularVelocity, Vector3.zero, 1);
        }

        // TODO: Make object turn to face player.
        //! The interesting part with this will be allowing the object to be rotated (Say a puzzle clue is hidden on the bottom)
        //! Perhaps have an empty parent and just rotate the graphic?
    }

    private void ReadInput()
    {
        if (Input.GetMouseButtonDown(0))
        {
            // Pick up the object.
            Grab();
        }
        else if (Input.GetMouseButtonUp(0) && grabbedRB)
        {
            // Drop the object, applying forces in the direction the mouse is moving.
            // This lets us toss the object just a little bit and makes things feel a bit better than simply dropping it.
            Release();
        }

        if (Input.GetMouseButtonDown(1) && grabbedRB)
        {
            // Toss the object forward with some force.
            Throw();
        }
    }

    void MoveObject()
    {
        if (objGrabbed)
        {
            // Smoothly moves the grabbed object to directly in front of us (with a Z offset equal to the carry distance).
            grabbedRB.position = Vector3.Lerp(grabbedRB.position, cam.transform.position + cam.transform.forward * carryDist, Time.deltaTime * 35);
        }
    }

    void Grab()
    {
        RaycastHit hit;
        Ray ray = cam.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));

        if (Physics.Raycast(ray, out hit, grabDist, grabLM))
        {
            // Set our RigidBody reference to the Rigidbody belonging to the collider.
            grabbedRB = hit.transform.GetComponentInChildren<Rigidbody>();
            // Set the RB's velocity to 0 to prevent jittering.
            grabbedRB.velocity = Vector3.zero;
            // Sets our initial carry distance to however far away we were.
            // This will make it easier to slightly move objects.
            carryDist = Vector3.Distance(cam.transform.position, grabbedRB.transform.position);

            objGrabbed = true;
            grabbedRB.useGravity = false;
        }
    }

    void Release()
    {
        // We need to clamp our yDist otherwise we can throw objects with superhuman strength!
        yDist = Mathf.Clamp(yDist, -6, 6);

        // TODO: Use the object's right. This will require us to eventually have our objects as children of an empty parent
        //! that always faces the player. Shouldn't be too tough, right? :)
        Vector3 throwVector = (cam.transform.right * xDist) + (Vector3.up * yDist);

        objGrabbed = false;
        grabbedRB.useGravity = true;
        grabbedRB.AddForce(throwVector, ForceMode.Impulse);
        grabbedRB = null;
    }

    void Throw()
    {
        // Turning gravity back on and resetting our boolean.
        objGrabbed = false;
        grabbedRB.useGravity = true;

        // Set the velocity to 0 again just in case.
        grabbedRB.velocity = Vector3.zero;

        // Adds force to the RB, causing it to fling forward.
        // Good for throwing objects at targets.
        grabbedRB.AddForce(cam.transform.forward * 10, ForceMode.Impulse);

        // Reset our RB reference. We're no longer holding it.
        // TODO: Move this & some other stuff into its own function.
        grabbedRB = null;
    }

    void Scroll()
    {
        float wheel = Input.GetAxis("Mouse ScrollWheel");
        if (wheel != 0)
        {
            //carryDist = Mathf.Clamp(carryDist + wheel, minZoom, maxZoom);
            // This is just to make sure we don't zoom past our min & max zoom values.
            float dist = Vector3.Distance(cam.transform.position, grabbedRB.position);
            if (wheel < 0 && dist > minZoomDist)
            {
                carryDist += wheel;
            }
            else if (wheel > 0 && dist < maxZoomDist)
            {
                carryDist += wheel;
            }
        }
    }
}

And also @orb I believe that’s what I’m going to end up doing: Having an empty GameObject that is always facing the player while a child gameobject containing the graphic is rotated. That way I can use the Object’s right and things will most likely feel a lot better. Thank you as well! :slight_smile: