3D - Move cube forever until collision then normalize position

So, I’m absolutely tearing my hair out trying to make a super simplistic ‘sokoban’ style game, no matter what method I use, it seems to cause major headaches with the physics / collision setup, movePosition, velocity, Translate, transform.postion, you name it, nothing works as I’d like it to. Who would’ve though getting a cube to push another cube around a grid based level would be so bloody difficult.

So, I’ve taken a small step back, and I’m trying to make an even simpler concept mockup,

I have a cube under keyboard control, movement is left, right, forwards and backwards, after pressing the appropriate GetKeyDown directional arrow, I want to ‘MOVE’ my cube at a constant speed until it hits a wall ( I won’t be able to move in any other direction until it comes to a stop ), once it hits said wall, STOP moving and then round / normalize it’s transform values back to ‘good’ numbers, i.e. it moves from 0,0,0, and then because of the maddening physics interaction it may hit a wall and end up 0,0,3.000025, which then breaks the movement for other directions, I simply want this normalized to 0,0,3, when it stops. Numerous tests later and I still can’t even manage this seemingly simplistic movement.

Any pointers or help would be greatly appreciated ?

I’m aiming for something like this, but in 3d not 2d.

What’s your code like?

Also I think you should just create a grid and use basic vector maths rather than the built-in physics for a typical sokoban game.

Ok, for the secondary concept, not the sokoban style, the most basic functionality I have is :

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

public class playerMove : MonoBehaviour {

    public Rigidbody playerRigidbody;

    public int playerSpeed = 10;

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

    void FixedUpdate () {

        if (Input.GetKeyDown (KeyCode.LeftArrow)){
            playerRigidbody.velocity = new Vector3(-playerSpeed, 0, 0);
        }
        if (Input.GetKeyDown (KeyCode.RightArrow)){
            playerRigidbody.velocity = new Vector3(playerSpeed, 0, 0);
        }
        if (Input.GetKeyDown (KeyCode.UpArrow)){
            playerRigidbody.velocity = new Vector3(0, 0, playerSpeed);
        }
        if (Input.GetKeyDown (KeyCode.DownArrow)){
            playerRigidbody.velocity = new Vector3(0, 0, -playerSpeed);
        }
    }
}

this uses velocity as the ‘move’ method, it kind of works ( I can increase the playerSpeed for ‘bigger’ levels ), but I don’t know how to round up / normalize the transform values when it comes to a stop.

I have also tried variations on playerRigidbody.MovePosition, playerRigidbody.AddForce, transform.Translate, transform.position and still major mishaps in the collision and player / cube movement.

The only thing other that kind of works is if I use GetAxis horizontal and vertical for movement, but this means I move ‘anywhere’, rather than this being ‘grid based’ in movement, not ideal, so I can’t even use that either… :frowning:

Also, what do you mean by just create a grid and use basic Vector maths, how would this be used to ‘push’ a cube also ? Btw, I’m primarily an artist with some programming knowledge, but if it’s going to get super advanced, it may be quite difficult for me to implement.

It sounds scarier than it is! Personally I think you should take a step back and reprogram your game to be grid based. I can try to give you a general idea.

In the video you posted, you can see the tiles in the background, as well as the boxes/cherries/whatever. These are all occupying a position in a 2D grid.

In the first part of the video, let’s say the cat starts at (5, 0). The user presses up. What goes on is that theres a routine that will check what exactly is 1 unit above the cat. If the cat is allowed to move, it moves the cat up one unit using a lerp. Once it reaches the point, it runs that check again. If the cat can still move, it moves the cat up one unit and then repeat until it can’t (or until it blows up a box or in a cherry in that case).

If you have interest in rebuilding your game in such a manner, I can help you out, just let me know. If not, then you can just use a method like this to round your position vectors when the velocity == 0.

Vector3 GetRoundedVector3 (Vector3 vector)
{
    vector.x = Mathf.RoundToInt (vector.x);
    vector.y = Mathf.RoundToInt (vector.y);
    vector.z = Mathf.RoundToInt (vector.z);
    return vector;
}
1 Like

Hi, thank you for your help, I would deeply appreciate the help in learning the grid system you mentioned ?

And thanks also for the code sample, although I’m not sure how to call this in script : -

for instance, within the FixedUpdated, would I add something like

        if (playerRigidbody.velocity == Vector3.zero) {

        }

and then within that, how would I call the GetRoundedVector3 code ?

so there is an OnCollisionEnter event. You can override that method in your player/cube script.

void OnCollisionEnter(Collision collision)
{
cube.position = vector3.zero //or whatever you want
}

Thanks, @iamvideep_1

But that would just reset my cube to world origin, 0,0,0.

So what position do you want it to be at?

Anywhere it stops, if I move forward at a constant speed 10 units and then it hits a wall, stop there ( and round up x,y,z values ), or if I press left, and it travels at a constant speed and hits a wall 5 units away, stop there, and then round up x,y,z values.

Basically, press directional arrow, cube moves, hits wall, wherever it stops, normalize that transform position so I don’t have small positional variants, i.e. if i move forward and hit a wall 10 unit away, I want the transform to show, 0,0,10, not 0,0,10.00002, or 0,0,9.9999989 etc.

Then on the OnColliderEnter event you can use Mathf.floor or Mathf.Ciel to normalize the values. I till roundoff the values. Usually you would like to use the mathf.floor to get to the nearest base value. then you can wait for the other key command and check to move or not.

1 Like

Thanks, I’m still trying to work out the best way to attack this.

Let me go back to my original ‘SOKOBAN’ style, to explain some of the problems I’ve been having, apologies, this is going to get long ( ANYBODY else fee free to chime in ! ) :stuck_out_tongue:

basic synopsis, sokoban style in 3d, not 2d, I have a player that I want to move around in a grid based movement, player can then push crates to locations to perform tasks and also utilise said crates to create bridges, etc.

Now, CRATES first :

my CRATES have 5 collisions boxes
the main cube box collider, set at a size of 0.9, 1, 0.9
then I have 4 smaller box colliders, one for each side, left, right, front and back, these are small in size, and set slightly within the crates outer shell size, this is so I can move around the box with my player cube without pushing it, you have to physically enter the grid position the crate is occupying for the crate to be pushed away. The crates can be moved 1 unit at a time, using Transform.Translate in the appropriate direction. You can drop crates to form bridges, when you do this, all 4 directional colliders are set to false, but I leave the main box collider on, so I can walk across, and I also freeze all transform constraints so that you cannot push it again, should you fall into a pit, etc. This is all working PERFECTLY.

Now, the PLAYER movement

Ideally, the PLAYER should also move in a GRID Like style, 1 unit at a time

Script 1 - snippet

    void FixedUpdate () {
        transform.Translate(Input.GetAxisRaw("Horizontal")*Time.deltaTime, 0f, Input.GetAxisRaw("Vertical")*Time.deltaTime);
    }

Pros : Works, I can push crates as required
Cons : Not grid based movement so Transform positions not ideal, not what I want

Script 2 - snippet

    void Update () {
        if (Input.GetKeyDown(KeyCode.LeftArrow)) {
            transform.position += Vector3.left;
        }
        if (Input.GetKeyDown(KeyCode.RightArrow)) {
            transform.position += Vector3.right;
        }
        if (Input.GetKeyDown(KeyCode.UpArrow)) {
            transform.position += Vector3.forward;
        }
        if (Input.GetKeyDown(KeyCode.DownArrow)) {
            transform.position += Vector3.back;
        }
    }

Pros : Works, I have grid based movement 1 unit at a time
Cons : Physics turns to shit as soon as I try to push cube, I get pushed back, pushing me off nice grid and then messing up any further movement

Script 3 - snippet

    void FixedUpdate () {
        var x = Input.GetAxis("Horizontal") * Time.deltaTime * 5;
        var z = Input.GetAxis("Vertical") * Time.deltaTime * 5;

        Vector3 direction = Quaternion.AngleAxis(0,Vector3.up) * new Vector3(x, 0, z);

        transform.Translate(direction);
    }

Pros : Works, I can move around and push cubes, also at whatever speed I define
Cons : Not grid based movement, so useless to me

Script 4 - full source

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

public class tumbleCube : MonoBehaviour {

    public float tumblingDuration = 0.25f;

    void Update () {
        var dir = Vector3.zero;

        if (Input.GetKey(KeyCode.UpArrow))
            dir = Vector3.forward;
        if (Input.GetKey(KeyCode.DownArrow))
            dir = Vector3.back;
        if (Input.GetKey(KeyCode.LeftArrow))
            dir = Vector3.left;
        if (Input.GetKey(KeyCode.RightArrow))
            dir = Vector3.right;

        if (dir != Vector3.zero && !isTumbling) {
            StartCoroutine(Tumble(dir));
        }  
    }

    bool isTumbling = false;

    IEnumerator Tumble(Vector3 direction) {
        isTumbling = true;

        var rotAxis = Vector3.Cross(Vector3.up, direction);
        var pivot = (transform.position + Vector3.down*0.5f) + direction * 0.5f;

        var startRotation = transform.rotation;
        var endRotation = Quaternion.AngleAxis(90.0f, rotAxis) * startRotation;

        var startPosition = transform.position;
        var endPosition = transform.position + direction;

        var rotSpeed = 90.0f / tumblingDuration;
        var t = 0.0f;

        while (t < tumblingDuration) {
            t += Time.deltaTime;
            transform.RotateAround(pivot, rotAxis, rotSpeed * Time.deltaTime);
            yield return null;
        }

        transform.rotation = endRotation;
        transform.position = endPosition;

        isTumbling = false;
    }
}

Pros : Tumble movement, actually grid based movement, and ideally my first choice of movement for my game mockup, I can push crates around level no problem
Cons : If I drop a crate off a ledge to form a bridge, then ‘tumble’ onto it, the physics interaction jumps my player cube out of whack with the grid movement, so positional transform values are all over the place, I can still tumble, but I am no longer on a nice grid, I’m offset. Also it keeps trying to ‘climb / tumble’ other objects within the level, again offsetting my grid position, so currently unreliable

Script 5 - snippet

    void Update () {
        if (Input.GetKey(KeyCode.RightArrow) && playerTransform.position == playerPosition) {
            playerPosition += Vector3.right;
        }
        if (Input.GetKey(KeyCode.LeftArrow) && playerTransform.position == playerPosition) {
            playerPosition += Vector3.left;
        }
        if (Input.GetKey(KeyCode.UpArrow) && playerTransform.position == playerPosition) {
            playerPosition += Vector3.forward;
        }
        if (Input.GetKey(KeyCode.DownArrow) && playerTransform.position == playerPosition) {
            playerPosition += Vector3.back;
        }
        transform.position = Vector3.MoveTowards(transform.position, playerPosition, Time.deltaTime * speed);
    }

Pros : Nice sliding movement, Grid Based also
Cons : If I hit object in level, player just keeps trying to push through ‘forever’, breaking all movement code, also, if I push crate of ledge to create a bridge, then walk on it, I then get stuck on top, can’t move off it to cross to other side or fall down.

ALTERNATIVELY, I have attached a project file with some of my tests within it, easier to look at, just disable / enable scripts if looking at the different versions ! :face_with_spiral_eyes:

3268066–252312–UNew.zip (1.86 MB)

Ok, some minor progress here :

Pros : Grid based movement, Slide To Position at whatever speed, Able to push Crates, And Collision with Walls.

Cons : If I fall off ledge, my player just continues to jump up and down ( not sure why ? - maybe something to do with my scripts Y position, so need to figure that out ) ( EDIT : FIXED this, now have two conditionals in OnCollisionEnter, one that checks player at 0.5 and one for -0.5).

There is a ‘wait’ before I fall when I do go off a ledge ( not sure why ? but looking into it ) ( EDIT : Implementing isGrounded to check whether on floor or not on floor - hopefully resolve this problem ).

but hey, at least I’m making progress… :roll_eyes:

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

public class Movement : MonoBehaviour {

    public float playerSpeed = 5.0f;

    private Vector3 playerPosition;
    private Transform playerTransform;

    void Start()
    {
        playerPosition = transform.position;
        playerTransform = transform;
    }

    void FixedUpdate()
    {
        if (Input.GetKey(KeyCode.UpArrow) && playerTransform.position == playerPosition)
        {
            playerPosition += Vector3.forward;
        }
        else if (Input.GetKey(KeyCode.RightArrow) && playerTransform.position == playerPosition)
        {
            playerPosition += Vector3.right;
        }
        else if (Input.GetKey(KeyCode.DownArrow) && playerTransform.position == playerPosition)
        {
            playerPosition += Vector3.back;
        }
        else if (Input.GetKey(KeyCode.LeftArrow) && playerTransform.position == playerPosition)
        {
            playerPosition += Vector3.left;
        }

        transform.position = Vector3.MoveTowards(transform.position, playerPosition, Time.deltaTime * playerSpeed);
    }

    void OnCollisionEnter (Collision collision)
    {
        if (collision.collider.tag == "Wall") {
            float x = transform.position.x;
            float y = transform.position.y;
            float z = transform.position.z;

            x = (int)x + -0.5f;
            z = (int)z + 0.5f;

            y = 0.5f;

            Vector3 away = new Vector3(x, y, z);
            playerPosition = away;
        }
    }
}