- Home /
Move Object Along Ground Using Raycast
Hi, all!
I have a character in a sidescroller/platformer. So the Z axis is not a problem here. When I press A or D, the character moves horizontally. This is great, until I come upon a ramp or a curve. He just walks into it and stops.
I want to be able to move my character along the slope of the point immediately in front of him without just setting his transform.up to the hit.normal. This would not look very realistic in most cases.
So I decided I would draw a ray immediately in front of him, get the normal of the current face, and take the negative reciprocal of the slope of the normal in order to get the slope of the line tangent to the face.
Then I take a deltaX value and set the Y equivalent to the deltaX*m + transform.position.y
This does not work at all.
After doing some debugging, I decided to draw a Debug.Rays of the normal and of the calculated tangent. The normal is yellow and comes from the player's feet and the tangent is red, stretching from the player's feet (As an Origin) to the point along the line one unit away. On a flat surface, it works great. However, on a curved surface it doesn't work. What's up? I will offer some sample code and a few pictures.
Thanks! - YA
This is when he is on a flat surface. The red is the calculated tangent, the normal is yellow, and the ray test is cyan (It is a bit in front of the player so I know what is coming up before the player is over it)
This is when the player steps onto a gently-sloping ramp
Totally off...
Here is the code running the Debug and the rays.
ray = new Ray( new Vector2( p.x + c.x + s.x * Mathf.Sign(deltaX), p.y + c.y + s.y/(3)), new Vector2( 0, -1 ) );
Debug.DrawRay( ray.origin, ray.direction, Color.cyan );
if( Physics.Raycast( ray, hit, s.y, collisionMask )) {
var slopeVec : Vector3 = hit.normal;
var normSlope : float = ( slopeVec.y - transform.position.y) / (slopeVec.x - transform.position.x );
slope = Mathf.Pow( (slopeVec.y/slopeVec.x), -1 )*-1; //Tangent line is negative reciprocal of normal
// Debug.Log( slope + "˚");
Debug.DrawRay( transform.position, hit.normal, Color.yellow, 0.0, false);
Debug.DrawLine( Vector3( transform.position.x, transform.position.y, 0 ), Vector3( transform.position.x+1, (transform.position.x+1)*slope + transform.position.y, 0 ), Color.red, 0.0, false );
if( Mathf.Abs( slope ) > (0.05) ) onRamp = true;
else onRamp = false;
}
Apart from your specific issues, I'm having trouble visualizing what you are trying to accomplish. You say:
without just setting his transform.up to the hit.normal. This would not look very realistic in most cases.
So how do you want the character to behave? Do you want him to orient to the normal over time rather than in a single frame? Or are you looking for some other orientation with respect to the surface?
I understand your confusion. I do not want to change the players orientation. I just want to change his movement direction to the tangent of the surface he is standing on.
Answer by robertbu · Dec 14, 2013 at 04:47 AM
If all you need is a movement vector, then all you need to do is calculate a vector from your current position to the position of the raycast. How you calculate that will depend on the pivot point of the character. If the pivot point is at the feet of the character (which has been recommend in a couple of writeup I've read on game development), then the movement vector will be:
var moveDirection = (hit.point - transform.position).normalized;
If your pivot is further up, say 0.5 units up, from the feet then the calculation is a bit more complicated:
var moveDirection = ((hit.point + hit.normal * 0.5) - transform.position;
As an alternate, you can raycast directly down from the character and use the cross product to find the direction.
var moveDirection = Vector3.Cross(hit.normal, transform.right);
I may have the parameters backwards in the Vector3.Cross().
I was able to achieve a suitable result with both. Thanks! Now that I have the moveDirection as a vector 3, do I just translate the character by moveDir?
This is what is going on now for calculation and moving the character ($$anonymous$$oving is bottom portion). I can go up and down ramps with a thin slope, but ramps that are even mildly steep will cause the player to fall through the floor and do other whacky things. (deltaPlatPos.x is just for left/right moving platforms)
var moveDir = Vector3.Cross(transform.right, hit.normal);
Debug.DrawRay( transform.position, moveDir, Color.red, 0.0, false );
Debug.DrawRay( transform.position, hit.normal, Color.yellow, 0.0, false);
deltaRampPos = (deltaX+padding)*moveDir.y;
}
var finalTransform : Vector2 = new Vector2( deltaX + deltaPlatPos.x, deltaY + deltaRampPos );
transform.Translate( finalTransform, Space.World );
Should I post a video? And honestly, the ultimate goal is to just get him to successfully move along ramps and curved surfaces, so if you have another suggestion I will take it!
Anytime you move a character by directly changing the transform (even a short distance using Time.deltaTime), you teleport that character. And if that teleport happens to go through the mesh and you are using that mesh for your ground, you fall through. If you are using a Rigidbody, you might have better luck with Rigidbody.$$anonymous$$ovePosition(). Or if you are using a character controller, using Simple$$anonymous$$ove() or $$anonymous$$ove() will help.
But the typical way I've seen this problem handled is that the character is moved forward to the new position, then a raycast is made in some downward direction for some height above the character, then the position of the character is adjusted based on the raycast so that the character is guaranteed to sits on the mesh. From what I've seen here, you can use Vector3.down for your raycast. Sometimes the '-transform.up' of the character is a better solution. And occasionally raycasting to an interior point is best (say for a planet).