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);
}

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.

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();

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?

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).

Could you post the code to the final solution? I have read what the answer was several times but It isn't clicking.

Thank you to Brian-Kehrer .
I solved with this code :


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class cubemovement1 : MonoBehaviour
{        
    public float speed = 10;
    public float rotSpeed = 10f;
    
    Vector3 surfaceNormal; 
    Vector3 myDirection; 
    Vector3 destinazione;     
    Vector3 targetPosition;
    Quaternion destRot;
    bool move = false;


    void Update()
    {
        if (Input.GetMouseButtonUp(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 1000))
            {
                targetPosition = hit.point;
            }
            move = true;
        }

        if (move) {
                    transform.position = transform.position + (myDirection.normalized * speed * Time.deltaTime);
                    transform.rotation = Quaternion.Slerp(transform.rotation, destRot , rotSpeed * Time.deltaTime);
        }

        if (Vector3.Distance(transform.position, targetPosition) <= 1.2f) move = false;
    }

    private void FixedUpdate()
    {
        RaycastHit hit;
        if (Physics.Raycast(transform.position,-transform.up, out hit, 10))
        {
            surfaceNormal = hit.normal;
        }
        destinazione = (targetPosition - transform.position);
        destRot = Quaternion.LookRotation(targetPosition - transform.position);
        Vector3 temp = Vector3.Cross(surfaceNormal, destinazione);
        myDirection = Vector3.Cross(temp, surfaceNormal);
    }

    private void OnDrawGizmos()
    {
        if (move)
        {
            Gizmos.color = Color.yellow;
            Gizmos.DrawSphere(targetPosition, 1);
        }
    }
}

My cube use a rigidbody with gravity and is locked on X and Z axis

If you have any suggestion to optimize the code or perfomance (i need many Gameobject in game) please answer this post

Thanks to all!!!