I have a problem making a rigidbody move with the mouse in 3d space.
My mouse drag script works fine in the X and Y, but I also need to make
it move in the Z since the scene is 3d. What I am doing is to cast a ray
against a table that the objects rest upon so that when the ray hits the
table, the rigidbody should move to the mouse position. When the
pointer is above the table, it will only move in the X and Y again.
Here is the code that I have so far for the Drag function:
// Drag selected rigid body with mouse
private void DragObject(GameObject go, float distance) {
// Freeze rotation of the selected object
selectedObject.rigidbody.freezeRotation = true;
// If ray collides with the stone table layer
// (This is how the stone moves when the ray hits the table)
if (Physics.Raycast(ray, out hit, 100, stoneTableMask)) {
// Get vector to mouse position (change to world coordinates)
mouseVector = mainCamera.ScreenToWorldPoint(Input.mousePosition);
// Translate the selected object to the new vector using velocity
go.rigidbody.velocity = mouseVector * Time.deltaTime * .5f;
}
// If ray does NOT collide with the stone table layer
// (This is how the object moves when the ray is not hitting the table)
else if (Physics.Raycast(ray, out hit, 100)) {
// Get vector to mouse position
forceVector = (ray.GetPoint(fixedDistance) - go.transform.position);
// Translate object to the mouse position with velocity
go.rigidbody.velocity = forceVector * Time.deltaTime * 150;
}
}
The code sort of works, but instead of moving to the mouse position, the object raises up in the Y
for some reason. I hope someone can help me work this out because I don’t have much hair left.
I want the objects to slide along the surface of the table when the ray is casting against it.
But when the mouse is high enough that the ray is no longer hitting the table, I want the
object to stop moving along the surface of the table and only move up/down and side to side.
So, I guess I want it to only move in screen coordinates when the ray is not hitting the
table, and world coordinates when it is? I even tried this for the mouseVector:
// Get vector to mouse position (change to world coordinates)
mouseVector = new Vector3(ray.origin.x, ray.origin.y, ray.origin.z);
But it still does the same thing which makes me think that maybe there is something missing
in the translation to world point? Or, maybe the local rotation is off and the axis isn’t set right.
Without some of the definitions of the variables, I’m taking a guess here. I think I can see what you are trying to do, but this may be wildly wrong. Not tested in Unity, but please let me know how it goes.
// Drag selected rigid body with mouse
private void DragObject(GameObject go, float distance)
{
// Freeze rotation of the selected object
selectedObject.rigidbody.freezeRotation = true;
// Get vector to mouse position (change to world coordinates)
Vector3 direction = mainCamera.ScreenToWorldPoint(Input.mousePosition) - mainCamera.transform.position;
// Used for the hit
RaycastHit hit;
// If ray collides with the stone table layer
// (This is how the stone moves when the ray hits the table)
if (Physics.Raycast(mainCamera.transform.position, direction, out hit, 100, stoneTableMask))
{
// Translate the selected object to the new vector using velocity
go.rigidbody.velocity = hit.point * Time.deltaTime * 0.5f;
}
// If ray does NOT collide with the stone table layer
// (This is how the object moves when the ray is not hitting the table)
else
{
// Get vector to mouse position
Vector3 forceVector = (direction.normalized * fixedDistance) - go.transform.position);
// Translate object to the mouse position with velocity
go.rigidbody.velocity = forceVector * Time.deltaTime * 150;
}
}
I kind of see what you are trying to do there but that code returned an error:
Assets/_Scripts/DragRigidbody.cs(106,35): error CS1061: Type UnityEngine.RaycastHit' does not contain a definition for position’ and no extension method position' of type UnityEngine.RaycastHit’ could be found (are you missing a using directive or an assembly reference?)
When I changed it to hit.point, it behaved erratically. I’m not sure what it was trying to do,
but the object either sat and wiggled or it jumped off the screen somewhere and would
not move with the mouse.
Sorry, the code was untested. “hit.point” is the correct member I intended. I updated the above code. The Raycast will output the RaycastHit variable and the hit.point is the point on the collider where the Raycast hit the object. In theory this should be the point on the table you are talking about, where you want the object to move to. The issue with my code is that the…
… is forcing it to move based on a velocity. Looking back over it I think it should be.
// Drag selected rigid body with mouse
private void DragObject(GameObject go, float distance)
{
// Freeze rotation of the selected object
selectedObject.rigidbody.freezeRotation = true;
// Get vector to mouse position (change to world coordinates)
Vector3 direction = mainCamera.ScreenToWorldPoint(Input.mousePosition) - mainCamera.transform.position;
// Used for the hit
RaycastHit hit;
// If ray collides with the stone table layer
// (This is how the stone moves when the ray hits the table)
if (Physics.Raycast(mainCamera.transform.position, direction, out hit, 100, stoneTableMask))
{
// Get the direction of the point hit on the table
Vector3 hitDirection = hit.point - go.transform.position;
// Translate the selected object to the new vector using velocity
go.rigidbody.velocity = hitDirection.normalized * Time.deltaTime * 0.5f;
}
// If ray does NOT collide with the stone table layer
// (This is how the object moves when the ray is not hitting the table)
else
{
// Get vector to mouse position
Vector3 forceVector = (direction.normalized * fixedDistance) - go.transform.position);
// Translate object to the mouse position with velocity
go.rigidbody.velocity = forceVector * Time.deltaTime * 150;
}
}
Everything about dragging should be first, relative to the desired direction of travel. This can be a fixed direction or dynamic direction. If it is fixed then you need to express your location’s based on the planar field of that direction, dynamic usually refers to the dynamics of the camera.
Example:
A fixed direction creates a Plane 1 unit above the table * the highest stack count. Your Mouse then raycasts to that plane, giving you a location.
A dynamic direction creates a Plane X units from the camera’s facing, in the direction of camera.forward. This gives a flat surface to drag the stone across, albeit never in the direction needed to “stack” anything.
The whole point is that the mouse only has X and Y, you have to choose how to use those two to get your Vector3.
At no point are we using physics.raycast, or masks. This makes the job easier.
@ LordAelfric,
That code didn’t produce any errors, but the object either just wiggled or it acted as though it
were shot from a cannon. I don’t understand what is happening there. I don’t know if it thinks the
point is somewhere off in space or what.
@ BigMisterB,
This movement should follow the mouse. When the ray cast at the mouse position hits the table
(which is sort of rounded like a large stone), the object should move to a spot on that table where the
ray hits it. (Well, it should follow the mouse, which I believe is what I don’t understand how to make it
do). When the ray is not hitting the table (which is when the stone is picked up far enough above the
table), the stone will no longer move backward and forward but instead move like it were in 2d (right
and left only). The idea is to get the objects to a point near the center of the table so they can be
more easily stacked on each other.
To clarify a couple things, I have to use raycast because of the platform that this app is designed for,
and the mask should make it easier to discern where the mouse pointer is. I have to determine that
since the table is the only point of reference to determine how the objects should move while the
mouse is dragging them.
This sentence sounds a little like my original approach which didn’t work out for me:
“The whole point is that the mouse only has X and Y, you have to choose how to use those
two to get your Vector3”.
Can you expand on that a little? I’m not quite sure what you meant.
OK, simply put, you get 2 directions out of a mouse (or touch) X and Y. You need to convert that into something that is “relative” to the user. The generalization is that you would use a plane based off of the camera in some sort or fashion.
If you are specifically wanting a point on the “table” you change that to the table’s platform. So build a plane based on the top side fo the table:
var plane = new Plane(table.transform.up, table.transform.position + tableTopFromPosition);
This then gives you a plane where the table top is. You then cast a ray to that table and get the point where it hit. (remember that the raycast is like this: Plane.Raycast(ray, out float) )
Simply get the hit point as such:
ray.GetPoint(distanceOfHit)
So if you are then looking at it from a camera’s perspective then you would move the object back some to adjust for your object’s size.
either: position = ray.GetPoint(distanceOfHit - object.transform.localScale.magnitude * 0.5);
or: position = ray.GetPoint(distanceOfHit) + object.transform.localScale.y * 0.5;
Both methods give you a point to place your object and adjust it for the size of the object.
There are about a billion ways to do this stuff, so there is no exact answer.
I would agree with BigMisterB here. There is a ton of ways to accomplish this and what he is saying is what I was trying to put into code. I have a feeling that the reason the object is shooting away is because it has a rigidbody that is colliding with the table, causing physics to take over. You could set the object to “IsKinematic” (= false) in the Rigidbody to disable physics moving the object around (unless you want that to happen).
It’s hard to completely answer your question without seeing what your project is doing first hand.
@ BigMisterB,
I’m not fully digesting conceptually how the a plane will work in this case. The reason being is that the table
has a convex curvature to it and unless the plane is being constantly redrawn as the mouse moves over it, I
would think that the object would try to translate through the surface of the table to reach the transform of the
plane. I’m a little slow… am I missing something?
@ LordAelfric,
I have the table set to kinematic and the objects themselves are non-kinematic which makes sense in
this application. The stones move dynamically and the table is stationary. Unfortunately, I won’t be able
to share the actual application because it isn’t mine, but I can post my entire drag script and hope that it
helps:
using UnityEngine;
public class DragRigidbody1: MonoBehaviour {
private GameObject selectedObject; // The object that is selected with the mouse
private float fixedDistance; // The set distance of the ray cast
private Camera mainCamera; // Cache the main camera
private Vector3 mouseVector; // Vector to mouse position when over table
private Vector3 forceVector; // Vector to mouse position when not over table
private RaycastHit hit; // Check all collisions from the raycast
private Ray ray; // Stores the ray to test collision with the stones
private Ray ray2; // Stores the ray to test collision with the stones
private Vector3 target; // Target created by the raycast hit point
private int stoneTableMask; // Cache the layer mask for the stone table
public int stoneTable; // Stores the layer that the stone table is on
/// <summary>
/// Start this instance.
/// </summary>
void Start() {
// Cache the stone table layer mask
stoneTableMask = 1 << stoneTable;
// Find the main camera position
mainCamera = FindCamera();
}
/// <summary>
/// Update this instance.
/// </summary>
void LateUpdate () {
// Cast a ray from the camera to the mouse position
ray = mainCamera.ScreenPointToRay(Input.mousePosition);
// Enable rotation if the stone is no longer dragged
if (Input.GetMouseButtonUp(0)) {
if (selectedObject != null) {
selectedObject.rigidbody.freezeRotation = false;
}
}
// Check that the mouse button is down
if (Input.GetMouseButtonDown(0)) {
if (Physics.Raycast(ray, out hit, 100) (hit.rigidbody !hit.rigidbody.isKinematic)) {
// Select the object to be dragged
selectedObject = hit.rigidbody.gameObject;
// Turn off gravity for the selected object
selectedObject.rigidbody.useGravity = false;
// Change selected stone to ignore raycast layer
selectedObject.layer = 2;
// Set the fixed distance
fixedDistance = Vector3.Distance(mainCamera.transform.position, hit.transform.position);
}
else return;
}
// If the drag button is pressed and there is a selected object, drag the object
if (Input.GetButton("Drag Object")) {
if (selectedObject != null) {
// Drag the object
DragObject(selectedObject, fixedDistance);
}
}
else if (selectedObject != null) {
// Turn on gravity
selectedObject.rigidbody.useGravity = true;
// Switch back to stones layer
selectedObject.layer = 0;
// Clear the selected object
selectedObject = null;
}
}
// Drag selected rigid body with mouse
private void DragObject(GameObject go, float distance) {
// Freeze rotation of the selected object
selectedObject.rigidbody.freezeRotation = true;
// If ray collides with the stone table layer
// (This is how the stone moves when the ray hits the table)
if (Physics.Raycast(ray, out hit, 100, stoneTableMask)) {
// float x = Input.mousePosition.x;
// float y = Input.mousePosition.y;
// float zDepth = hit.point.z;
// Vector3 position = new Vector3(x, y, zDepth);
// Vector3 position = new Vector3(Input.mousePosition.x, Input.mousePosition.y, hit.point.z);
// Get vector to mouse position (change to world coordinates)
mouseVector = mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, hit.point.z));
// Translate the selected object to the new vector using velocity
go.rigidbody.velocity = mouseVector * Time.deltaTime * .5f;
// go.rigidbody.velocity = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, hit.point.z));
}
// If ray does NOT collide with the stone table layer
// (This is how the object moves when the ray is not hitting the table)
else if (Physics.Raycast(ray, out hit, 100)) {
// Get vector to mouse position
forceVector = (ray.GetPoint(fixedDistance) - go.transform.position);
// Translate object to the mouse position with velocity
go.rigidbody.velocity = forceVector * Time.deltaTime * 150;
}
Debug.Log(Input.mousePosition);
}
// Returns the main camera
private Camera FindCamera () {
return Camera.main;
}
}
Note that I left in some commented lines that show how I tried to solve the problem.
I think what I need to understand essentially is how to make an object move along the
surface of a stone table while it is being dragged and while the mouse is over the table.
I don’t know how to capture that point where the mouse ray is hitting the table, and also
tell the object to move to it.
Okay, I tested this out in a project using what I believe is the same setup you have. I created a sphere and flattened it, giving it a smoothed round rock look- very flat. It had a rigidbody and a mesh collider. The rigidbody was set to “IsKinematic = false”. Then I created a cube with a simple box collider and also a rigidbody.
// Drag selected rigid body with mouse
private void DragObject(GameObject go, float distance)
{
// Freeze rotation of the selected object
selectedObject.rigidbody.freezeRotation = true;
// If ray collides with the stone table layer
// (This is how the stone moves when the ray hits the table)
if (Physics.Raycast(ray, out hit, 100, stoneTableMask))
{
// Get the direction of the point hit on the table
forceVector = hit.point - go.transform.position;
// Translate the selected object to the new vector using velocity
go.rigidbody.velocity = forceVector * Time.deltaTime * 150.0f;
}
// If ray does NOT collide with the stone table layer
// (This is how the object moves when the ray is not hitting the table)
else if (Physics.Raycast(ray, out hit, 100))
{
// Get vector to mouse position
forceVector = (ray.GetPoint(fixedDistance) - go.transform.position);
// Translate object to the mouse position with velocity
go.rigidbody.velocity = forceVector * Time.deltaTime * 150;
}
//Debug.Log(Input.mousePosition);
}
That works exactly as I asked.
Thank you very much for taking your time to help me.
Thank you also for your time BigMisterB. You are both
very helpful and I appreciate it very much. I hope that I
can return the favors someday.