Walking around a cube

Hey,

i am really stuck with a problem here. I want to be able to walk around a cube, so my character should rotate around the edge (Around the P1 vector along the edge) when he leaves the surface and continue to walk on the next surface.

My approaches were the following:

  • Making the cube rotate as soon as the player reaches the edge, worked semigood because I would have to rotate it around P1 and it could just move forever. Could also lead to further position problems in the later state of the game I guess.
  • Changing the gravity as soon as the player leaves the surface. Had trouble with the player facing the right direction.
  • Using LookAt at P1, but it takes the blue axis and not the down axis and rotates the rest as well.

Now the best options I would see is that I use LookAt plus change the gravity to P1 while he is in the trigger zone. If I use LookAt I would need to have my player rotated around 90 degrees all the time though because the LookAt just works with the players forward vector.

Anyone got any better approach? I have to make this possible for all sides, so also for the corners where three sides come together btw.

Off the top of my head…

Shoot a ray from the player’s position to the center of the cube. The point it hits is where on the surface of the cube they should be. Get the surface normal there (the raycasthit info should contain this normal) and orient the player with its up in that direction.

Always move relative to that up.

2 Likes

But the problem is in the trigger zone? Even if I use a raycast there the surface normal would either go straight up or straight to the side and that is not how the player should move or be oriented.

If you saved raycast result from previous frame, you can calculate edge distance and nicely lerp your charater around it.

Btw, what if you going to turn not around edge, but around a vertex? I think you need three rays around that character

I would use the event/animation sequence method- when facing the edge and moving into the trigger area fast enough, trigger an event in which the character is animated moving from one side of the cube to the next automatically, like a tiny cutscene. Once the event is triggered, you’ll know what side they’re moving to, and can flip the gravity or cube or whatever you like.

I’ve had to deal with this problem for one of my old game concepts, and i ended up with the following

Hopefully you can see the red lines shooting from the lower half of the character. They are debug rays of physics raycasts, and how I detect the surface normal in front of the character. Most important is for the ray to face the movement direction since you are only concerned with where they intend to move.

The first ray shoots downward at an angle to slightly below the character, it detects the surface directly infront of it and also detects concave surfaces.

If the first ray hits nothing, the second ray shoots from the maximum endpoint of the first ray straight and inward to detect convex surfaces.

Based on the surface detected depending on the ray casts above, the character’s upward facing direction is changed. It seems to work on any surface orientation from what I’ve tried so far. The only caveat i suppose is, i haven’t tried this on very detailed environments, so hard to tell if it will work when faced with a desk for example. probably will have to approximate or ignore raycasting certain objects

To allow the character to stick to the surface it is walking on, uncheck gravity on the rigidbody. Instead, apply your own force as gravity. Apply the opposite of the surface normal as your gravity. If you want your character to fall in a consistent down direction, simply reset the gravity to your down direction when the above raycasts nolonger detect anything(The raycasts double as ground detection)

Sadly this method is still jaggy when transitioning from one surface to another. If that can be solved, bravo i guess.

EDIT:
almost forgot, Vector.Cross is your friend. You’ll have to use it to composite your input direction and detected up value.

2 Likes

Yeah, that would work I guess but it sounds like a lot of work for my small brain.

Thought about an animation first as well, but being able to jump and walk over the edges would definitely be more fun for the player.

That actually looks like it would work, as you say jaggy but at least working. I did not quite understand how it works. Basically you check the rotation in front of you and depending on that you rotate the character?
What happens if you would jump over the edge?

Approximate normals from surfaces you’re standing on isn’t hard. Actually this is easiest way to get smooth movement around multiple edges not only for cube. It will work for vertex too.

But the LookAt method would seem like the easiest way if you could change the used forward direction into the down direction I suppose? The gravity while being on the surface could then be calculated by using the players position minus the edge position (P1). Or am I wrong?

You shoot the rays in the direction of movement. This matters since you don’t want the ray to shoot forward while strafing sideways, but this also depends on your game. Mine happens to be in First Person view, so strafing needs to be accounted for.

By my design, the character will fall in the last direction of gravity it detected. Sadly I have not figured out how to make the character fall like its around a planetoid. It can only detect the surface immediately under it. At most you could reset the gravity to a consistent down direction when the character leaves a surface altogether, be it from the edge or not.

Yes. But how do you calculate edge position from only one raycast? Assuming there’s box collider it is possible, but it won’t work with arbitrary mesh collider. You need two points on two triangles and their normals to find the enge. And there’s a case where you need 3 because player may walk over vertex.

Just in case anyone stumbles across this thread with the same problem. I got it working with this piece of code:

    [SerializeField]
    private float walkingSpeed;

    [SerializeField]
    private float mouseSensitivity;

    private bool onCorner = false;
    private bool onEdge = false;

    private Transform edgeTransform;
    private Transform cornerTransform;

    private bool isGrounded = false;

    [SerializeField]
    private float jumpForce = 5f;

    private Rigidbody rigid;

    private void Start()
    {
        rigid = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
        Vector3 velocity;
        velocity = transform.forward * Input.GetAxis("Vertical") * walkingSpeed + transform.right * Input.GetAxis("Horizontal") * walkingSpeed;

        Physics.gravity = -transform.up * 9.8f;

        //velocity += currentGravity;


        transform.position += velocity * Time.deltaTime;

        if(Input.GetButtonDown("Jump")) rigid.AddForce(transform.up * jumpForce, ForceMode.Impulse);


        if (onEdge)
        {
            WhileOnEdge();
        }
        else if (onCorner)
        {
            WhileOnCorner();
        }

        transform.Rotate(new Vector3(0, Input.GetAxis("Mouse X") * Time.deltaTime * mouseSensitivity, 0), Space.Self);

    }

    private void WhileOnEdge()
    {
        Vector3 player2Edge = edgeTransform.position - transform.position;

        Vector3 playerUpDirection;

        Vector3 playerLeftDirection;
        Vector3 playerForwardDirection;


        if (edgeTransform.position.x == 0)
        {
            playerUpDirection = (
                Vector3.Dot(player2Edge, edgeTransform.right) * edgeTransform.right
                - player2Edge).normalized;

            playerLeftDirection = Vector3.Cross(transform.forward, playerUpDirection);
            playerForwardDirection = Vector3.Cross(playerUpDirection, playerLeftDirection);

            transform.rotation = Quaternion.LookRotation(playerForwardDirection, playerUpDirection);

        }
        else if (edgeTransform.position.y == 0)
        {
            playerUpDirection = (
                Vector3.Dot(player2Edge, edgeTransform.up) * edgeTransform.up

                - player2Edge).normalized;

            playerLeftDirection = Vector3.Cross(transform.forward, playerUpDirection);
            playerForwardDirection = Vector3.Cross(playerUpDirection, playerLeftDirection);

            transform.rotation = Quaternion.LookRotation(playerForwardDirection, playerUpDirection);

        }
        else if (edgeTransform.position.z == 0)
        {
            playerUpDirection = (
                Vector3.Dot(player2Edge, edgeTransform.forward) * edgeTransform.forward
                - player2Edge).normalized;

            playerLeftDirection = Vector3.Cross(transform.forward, playerUpDirection);
            playerForwardDirection = Vector3.Cross(playerUpDirection, playerLeftDirection);

            transform.rotation = Quaternion.LookRotation(playerForwardDirection, playerUpDirection);

        }
    }

    private void WhileOnCorner()
    {
        Vector3 playerUpDirection = (transform.position - cornerTransform.position).normalized;

        Vector3 playerRightDirection = Vector3.Cross(playerUpDirection, transform.forward);

        if (playerRightDirection == Vector3.zero) playerRightDirection = transform.right;

        Vector3 playerForwardDirection = Vector3.Cross(playerRightDirection, playerUpDirection);

        transform.rotation = Quaternion.LookRotation(playerForwardDirection, playerUpDirection);
    }

    private void OnTriggerEnter(Collider other)
    {

        print("Entered " + other.name);
        if (other.CompareTag("Edge"))
        {
            onEdge = true;
            edgeTransform = other.transform;
        }

        if (other.CompareTag("Corner"))
        {
            onCorner = true;
            cornerTransform = other.transform;
        }

        if (other.CompareTag("Surface"))
        {
            onEdge = false;
            onCorner = false;

            Vector3 playerForward;

            switch (other.name)
            {
                case ("Surface1"):
                    playerForward = Vector3.Cross(Vector3.up, -transform.right);
                    transform.rotation = Quaternion.LookRotation(playerForward, Vector3.up);
                    break;

                case ("Surface2"):
                    playerForward = Vector3.Cross(Vector3.forward, -transform.right);
                    transform.rotation = Quaternion.LookRotation(playerForward, Vector3.forward);
                    break;

                case ("Surface3"):
                    playerForward = Vector3.Cross(Vector3.right, -transform.right);
                    transform.rotation = Quaternion.LookRotation(playerForward, Vector3.right);
                    break;

                case ("Surface4"):
                    playerForward = Vector3.Cross(-Vector3.forward, -transform.right);
                    transform.rotation = Quaternion.LookRotation(playerForward, -Vector3.forward);
                    break;

                case ("Surface5"):
                    playerForward = Vector3.Cross(-Vector3.right, -transform.right);
                    transform.rotation = Quaternion.LookRotation(playerForward, -Vector3.right);
                    break;

                case ("Surface6"):
                    playerForward = Vector3.Cross(-Vector3.up, -transform.right);
                    transform.rotation = Quaternion.LookRotation(playerForward, -Vector3.up);
                    break;
            }
        }
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.CompareTag("Edge"))
        {
            onEdge = false;
        }

        if (other.CompareTag("Corner"))
        {
            onCorner = false;
        }
    }

On the edges and corners I change the rotation for anything but the up-rotation to face towards the edge/corner. The gravity is simply the players down-vector multiplied by 9.8. The rigidbody is just used for the gravity because otherwise a collision with the ground won’t be detected.

There is room for improvement in the code, that is out of the question, but it’s basically what’s working for me.

1 Like