DragRigidbody - move object in Local Space, not World Space

I have been searching for a solution to this problem for a few days without any joy so any help would be fantastic!

I have the DragRigidBody script attached to my Player which is a child of my camera and is being moved forward, around curves etc. I would like to be able to drag the Player along the X and Y axes in his local space (Eg. so if he is facing a certain direction, his X axis will change).

What’s happening now is that he is moving along the X-axis in World Space, so basically left and right in World space, regardless of his direction.

Some more about the game setup;
It is a 3D shooter, where the camera is on ‘rails’ and move forward around different shaped tubes using markers along the way, facing in the correct forward direction. The game is in third person and so the Player is in front of the camera and can move along the X and Y axes (Up/Down/Left/Right). He is a Rigidbody and so has his Z-position constrained as well as his X and Z -rotations. He moves around the ‘track’ the same way as the camera, but instead of also doing the exact same path-following using the markers, I have instead made him a child of the camera to minimise the processing time, etc (game is for iOS). To take care of his rotation (so that he’s always always facing the correct direction), I have a ‘lookAt target’ object ahead of him, which is also a child of the camera (so Player is in-between Camera and lookAt target).

Please see the attached sketch (at bottom) to show the Player’s movement along the X-axis as he moves around a curved section of the track. The current movement (at top-left) shows that he moves from left to right (basically horizontally) regardless of where along the curve the camera (and so he) is. The correct movement is shown at top-right, where his X-axis changes at different points long the curve. The lower two boxes just show the different perspectives and the path markers.

The slightly modified DragRigidBody script I am using (converted to C#) is as follows (I have left the commenting in for anyone that doesn’t know how the script works - feel free to correct any mistakes);

Thanks

using UnityEngine;
using System.Collections;

/* Note, this script is attached to the Player object in the scene. 
 * It is used to allow the user to drag the Player around the screen using the mouse / finger on mobile devices.
 * It also clamps the values/positions of the X and Y where the Player can be dragged (the edges of the walls, etc), 
 * as well as some other useful things. */

public class MovementController : MonoBehaviour
{
    public float Spring = 50.0F;
    public float Damper = 5.0F;
    public float Drag = 10.0F;
    public float AngularDrag = 5.0F;
    public float Distance = 0.2F;
    public bool AttachToCenterOfMass = false;

    private SpringJoint springJoint;
  
    //-----------------------------------------------------
    // Use this for initialization
    void Start()
    {

    }

    //-----------------------------------------------------
    // Update is called once per frame
    void Update()
    {    
        // Make sure the user pressed the mouse down
        if (!Input.GetMouseButtonDown(0))
        {
            return;
        }

        var mainCamera = FindCamera();

        // Create a RaycastHit object called hit
        RaycastHit hit;

        // Cast a ray from the mouse point in Screen Space, storing the information in the hit variable
        // If we don't hit anything..
        if (!Physics.Raycast(mainCamera.ScreenPointToRay(Input.mousePosition), out hit, 100))
            // ignore
            return;

        // if we hit a non-rigidbidy OR a non-kinematic object
        if (!hit.rigidbody || hit.rigidbody.isKinematic)
        {
            // ignore
            return;
        }    

        // if a springJoint object does not exsist..
        if (!springJoint)
        {
            // Create a Rigidbody dragger object
            GameObject go = new GameObject("Rigidbody dragger");

            // Create a Rigidbody and add the component to the dragger
            Rigidbody body = go.AddComponent("Rigidbody") as Rigidbody;

            // Add a SprintJoint component to the dragger
            springJoint = go.AddComponent("SpringJoint") as SpringJoint;

            // Set the Rigidbody to be Kinematic (ie. to be moveable)
            body.isKinematic = true;
        }

        // Set the position of the springJoint object to be the position that was hit by the mouse
        springJoint.transform.position = hit.point;

        // Iff we checked this option (AttachToCenterOfMass)..
        if (AttachToCenterOfMass)
        {
            //**		    var anchor = transform.TransformDirection(hit.rigidbody.centerOfMass) + hit.rigidbody.transform.position;

            // Set the anchor point of the object that scriopt is attached to.
            // This is the centre of mass + its position
            var anchor = hit.rigidbody.centerOfMass + hit.rigidbody.transform.position;

            //**		anchor = springJoint.transform.InverseTransformPoint(anchor);

            springJoint.anchor = anchor;
        }
       
        // if the collider that the ray hit is part of the 'Player' layer..
        if (hit.rigidbody.gameObject.layer == LayerMask.NameToLayer("Player"))
        {
            // Reset the anchor point
            springJoint.anchor = Vector3.zero;
        }

        // otherwise, do nothing
        else
            return;

        // Update the attributes of the springJoint object
        springJoint.spring = Spring;
        springJoint.damper = Damper;
        springJoint.maxDistance = Distance;
        springJoint.connectedBody = hit.rigidbody;

        //    	StartCoroutine ("DragObject", hit.distance);

        // Call the DragObject Coroutine, passing the distance that the hit variable has stored. 
        StartCoroutine(DragObject(hit.distance));
    }

    //-----------------------------------------------------
    IEnumerator DragObject(float distance)
    {
        float oldDrag = springJoint.connectedBody.drag;
        float oldAngularDrag = springJoint.connectedBody.angularDrag;
        springJoint.connectedBody.drag = Drag;
        springJoint.connectedBody.angularDrag = AngularDrag;
        Camera mainCamera = FindCamera();

        // While the mouse button is pressed down..
        while (Input.GetMouseButton(0))
        {
            // Assign the position in World Space to the 'ray' variable.
            // Note, ScreenPointToRay returns a ray going from camera through a screen point. Resulting ray is in world space, 
            // starting on the near plane of the camera and going through position's (x,y) pixel coordinates on the screen 
            // (position.z is ignored). Screenspace is defined in pixels. The bottom-left of the screen is (0,0); the right-top
            // is (pixelWidth,pixelHeight).
            var ray = mainCamera.ScreenPointToRay(Input.mousePosition);

            // Assign the distance from the ray that was touched to the position of the springJoint.
            // Note, 'GetPoint' returns a point at distance units along the ray.
            springJoint.transform.position = ray.GetPoint(distance);

            // Wait for the end of that frame
            yield return new WaitForEndOfFrame();
        }

        // if the springJoint connectedBody object exists..
        if (springJoint.connectedBody)
        {
            // Update the attributes of the connectedBody object
            springJoint.connectedBody.drag = oldDrag;
            springJoint.connectedBody.angularDrag = oldAngularDrag;
            springJoint.connectedBody = null;
        }
    }

    //-----------------------------------------------------
    Camera FindCamera()
    {
        if (camera)
            return camera;
        else
            return Camera.main;
    }
}

alt text

You should spend more words on how your actual setup looks like and what you actually want to do. Is it a 3d or 2d game (i guess 3d)? Is it a 3rd person, first person or top down game (i guess 3rd person)?

You said you have your player object as child of your camera and it’s moving forward? Having the player a child of the camera seems really strange, usually you have the camera follow. Also “he moves forward”, in the local space of the camere? Or do you move the camera?

This script does not move the object along world axis. It moves it relative to the camera. Also if you use a perspective camera it doesn’t move the object on a flat plane. The movement will happen on a curved (deformed) surface around the camera origin. That’s because you use a ray that always have the same length and always go through the camera’s origin. Because the ray starts at the near plane it’s not a perfect arc, but almost.

Maybe post a screenshot or paint a scribble of your situation. How the player can move / rotate how the camera is in relation to that and on which axis you want to move.

edit

I would say your problem is:

  • The player is a rigidbody and is a child of another object
  • The players movement constraint on z axis

Rigidbodies are always moved in world space. You can’t have it simulated in “local physics”. The rigidbodies velocity angular velocity and position are always in worldspace. When you lock the movement on the z axis (the world z-axis of course) your player can’t be move along the z axis by forces. It can still be moved on the z axis by “overriding” the physics and use the transform component.

A Rigidbody that is a child of another moving object will behave strange. The Rigidbody itself calculates it’s movement velocity based in FixedUpdate (the physics loop). Those calculations are, as already mentioned, in worldspace. Now when the parent object moves it transfers it’s motion to it’s child transforms. So the naturally physics based movement is overlayed with a non physics based offset.

Since you don’t actually use physics in your game (since you use a fix path), i wouldn’t use a Rigidbody at all.

I would suggest to use a Plane which position should be the center point on the path where the player should be, and the normal vector should be set to the opposite of the look direction of the player. So the Plane is the plane in which the player can move.

Now just use Plane.Raycast to get the mouseposition on this plane. Do either a smoothed lerping to this point of just set the player to this point.