- Home /
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;
}
}
Answer by Bunny83 · Dec 05, 2012 at 07:41 PM
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.
Sorry, we have around 500 new questions and about 300 new users a day, so you can really get lost here ;)
I'll edit my answer...
No problem Bunny, thanks for taking the time to explain that - appreciate it! :)
Your suggestions sounds like it'll work really well for this purpose. I'll give it a try and let you know how it goes. Also, correct me if I'm wrong but using this method will also keep the Z-position of the Player relative to the camera (offset) constant too, right? Since ScreenToWorldPoint ignores the z-position (makes it 0).
As I'm removing the Rigidbody from the Player am I right to assume that it can now be kept as a child of the Camera?
Thanks
Sure, it can be the child of the camera. Well, it depends on how you use it ;) ScreenToWorldPoint doesn't ignore the z position. The z position you pass into the function is taken as the distance from the camera's origin in worldunits.
I'm still not sure if you player "looks" into a different position as the camera. If so ScreenToWorldPoint would move the player relative to camera and not relative to the player. That's why is suggested the Plane. It does quite the same thing, but projects the mouseposition onto an arbitary plane that isn't bound to the camera. The plane can be moved / rotated with the player object.
Here's an example:
public Transform Player;
void Update()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Plane plane = new Plane(-Player.forward, Player.position);
float dist;
if (plane.Raycast(ray, out dist))
{
Vector3 pos = ray.GetPoint(dist);
Player.position = pos;
}
}
Since we rebuild the plane each time from the players position, it might get slowly offset. It might be better to use a fix position relative to the camera, ideally the center on the path where the player would be when not moved manually.
Transform cam = Camera.main.transform;
Plane plane = new Plane(-Player.forward, cam.position + cam.forward * DesiredPlayerDistanceFromCam);
Your answer
Follow this Question
Related Questions
Drag Rigidbody 2 Answers
problem with dragrigidbody 0 Answers
How can i set AddRelativeForce relative to how fast/slow im moving my mouse? 2 Answers
Limiting the top speed of a rigidbody in 3d space? 1 Answer
Drag around riggidbodies with a "pin" 0 Answers