- Home /
Calculating a movement direction that is a tangent to a slope surface
What I need is for my character to be aware of the direction he needs to walk in order to climb up/down any slope, without relying on the gravity to pull him down or push up as he keeps trying to walk into the slope horizontally (I need that actual direction for other things). I've made this code which can only move me on the world's X-Z plane, now I need to change the "forward" and "right" variables so that they're adjusted to the surface. I know it has something to do with angles, normals, vectors and quaternions, and it's something VERY simple, but I just can't figure it out :(
I've already added a temporary part of the code that gets the normal of the colliding surface, because I'm sure it's needed for the formula somehow. The code's pretty well commented:
var cam: Transform; var shadow: Transform; var speed = 6.0; var jumpSpeed = 8.0; var gravity = 20.0;
//the direction vector for the next move private var moveDirection = Vector3.zero;
//when hitting something, store the surface's normal private var hitting: boolean; private var hitnormal: Vector3.up; function OnControllerColliderHit (hit : ControllerColliderHit) : void { hitting=true; hitnormal=hit.normal; }
//just adjust the blob shadow to be above you function Update() { shadow.position=transform.position; shadow.position.y+=3; }
function FixedUpdate() { //forward vector relative to camera var forward=cam.TransformDirection(Vector3.forward); //reset y to walk horizontally, so that camera looking up doesnt matter forward.y=0; //normalize to apply speed later forward=forward.normalized; //right vector relative to the camera, to strafe var right=Vector3(forward.z, 0, -forward.x); //make the player always look forward relatively to the camera transform.rotation=Quaternion.LookRotation(forward); //get the player's move controller component var controller : CharacterController = GetComponent(CharacterController); //controls when grounded / not flying if (controller.isGrounded) { //move along the forward/right plane according to input, with current speed, also avoiding strafe speed by normalizing vector moveDirection=((Input.GetAxisRaw("Horizontal")*right+Input.GetAxisRaw("Vertical")*forward).normalized)*speed; //temporary jumping input if (Input.GetButton ("Jump")) { hitting=false; hitnormal=Vector3.up; //add the jump speed to the vertical part of the move direction moveDirection.y = jumpSpeed; } } //apply gravity by reducing the vertical part of the move direction by the gravity value with time moveDirection.y-=gravity*Time.deltaTime; //apply the actual movement, with time, by using the controller component controller.Move(moveDirection*Time.deltaTime); }
Answer by Brian-Kehrer · Jan 19, 2010 at 04:41 AM
The cross product of two vectors gives you the vector orthogonal to both.
Vector3 surfaceNormal; // Get the normal value on collision; Vector3 myDirection; // player direction in X-Z space;
void FixedUpdate(){ Vector3 temp = Vector3.Cross(surfaceNormal, myDirection); Vector3 myDirection = Vector3.Cross(temp, surfaceNormal); }
So take the cross product of the surface normal (which you can detect via the collision) and the X-Z direction of motion as a temporary vector TEMP.
Then take the cross product of the same surface normal and TEMP. That will give you the vector along the surface.
You may need to normalize the result vector and scale it by the magnitude of the original myDirection vector.
$$anonymous$$, I edited your answer and switched the parameters in the second cross product; otherwise the new myDirection would be flipped. :)
Vector3 surfaceNormal; // Get the normal value on collision;
I have a problem with this method because ContactPoint.normal doesn't seem to give the actual surface normal, but rather some kind of impact normal. In other words, depending on the angles of impact, the normal returned can change. (I found a strage issue when testing where I'd get a consistent surface normal result with a collider until I disabled and re-enabled the collider, after which the impact returned different values.)
The only way I've found to reliably get the surface normal is with a raycast. Am I missing something here? It sure would be a lot simpler if ContactPoint.normal just returned the surface normal of the polygon it collided with.
I have also had problems with CapsuleCast hitInfo.normal returning different normal values depending on the angle of impact. This page is what got me to realize why my normal alignment would be off when on a certain angle.
http://answers.unity3d.com/questions/50825/raycasthitnormal-what-does-it-really-return.html
To combat this, I just have a Raycast shoot from my CapsuleCast start position towards my CapsuleCast hitInfo.point and get the normal with the Raycast hitInfo ins$$anonymous$$d of the CapsuleCast. This comment is for anyone who been having trouble with inaccurate normal values as I wasted hours for such a silly thing.
I also notice that if you are using a mesh collider and you change the scale, the hitInfo.point will go to weird places on the collider and can give bad results. For example, I had a mesh collider scaled to be very tall, but when colliding with it, the hitInfo.point from my CapsuleCast point to the bottom of the collider, which was through the floor, so the raycast I shot out hit the floor and gave me the floors normal causing wrong results.
Thank you so much! This is exactly what I have been looking for. So simple!
Answer by Matt-Downey · Sep 29, 2012 at 04:36 AM
I really dislike both of the methods because they get really whacky at larger angles, which prompted me to invent a new method a while back.
Since then I've made two methods that should be better or faster or both...
//This first equation only works for gravity being downward (so it's perfect for first person shooter)
//it is both faster and it is infinitely accurate according to my interpretation of how movement on slopes should work in first person shooter:
//planar equation: ax + by + cz + d = 0
//(a,b,c) = slope of the plane or the magnitude of normal vector's components
//"d" is zero because of our coordinate system.
//(x,y,z) is partially known, we know x is vect.x and z is vect.z
y = (-normal.x*vect.x - normal.z*vect.z)/normal.y; //find "y" by y = (-ax - cz)/b
direction = Vector3(vect.x,y,vect.z);
direction.Normalize();
//The second works easily on floors, walls, and ceilings (although with the normalization it should be more expensive than two cross products), so gravity could theoretically be in any direction
//This method is slower and is less accurate at steep angles compared to the previous code, but it took me three months to notice it was off its mark and if its any consolation and it's infinitely better than the aforementioned (google + other) methods on steep slopes:
//By dot product/negative inverse, this is orthogonal to the normal
var right : Vector3 = Vector3(normal.y,-normal.x,0); //in world space
var forward : Vector3 = Vector3(0,-normal.z,normal.y); //in world space
right.Normalize();
forward.Normalize();
//the direction the player will move (tangential), which is a combination of any two non-parallel vectors on the correct plane
var direction : Vector3;
direction = right*vect.x + forward*vect.z;
direction.Normalize();
Answer by Micha Lewtak · Jan 19, 2010 at 08:17 PM
I'd like to thank Brian Kehren for his answer and Rune Johansen's edit, but while it was answered, I also found the answer on Google, and I was given a different formula, which appears to do exactly the same thing. Here's both (vector is the original vector, normal is the collision normal):
(vector-(Vector3.Dot(vector,normal))*normal).normalized
(Vector3.Cross(Vector3.Cross(normal,vector),normal)).normalized
The one below is the one from the Kehren answer. So can anybody tell me if there's any actual difference between these two expressions or will they yield the exact same result every time? If so, which one's the most simplified/processor-friendly?
I believe the first one looks similar the equation for reflection. The first one is a bit cheaper of a computation but will give different results.
Answer by Horsman · Jan 20, 2010 at 01:32 PM
The answer found on Google is a faster method of doing the calculation (pretty clever actually), and the one Brian gave is the standard way of doing it. There aren't the same (the intermediate calculations are quite different, but should yield the same results. They will both give you garbage if the normal is the same as the forward direction (not sure why that would ever happen, but its a thing to know).
Answer by Haversack · Sep 17, 2010 at 07:42 PM
Could you post the code to the final solution? I have read what the answer was several times but It isn't clicking.
Your answer
![](https://koobas.hobune.stream/wayback/20220612000641im_/https://answers.unity.com/themes/thub/images/avi.jpg)