Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Sokoban Style Crate Movement ? Push Problem.

Discussion in 'Scripting' started by Deleted User, Oct 21, 2017.

  1. Deleted User

    Deleted User

    Guest

    Hi,

    I'm putting together a small prototype for a sokoban style inspired game, but I'm having a lot of trouble with the 'crate' movement. So, basically I have my player ( a cube at this point ) working and colliding fine, however, the crate movement upon collision isn't quite working as expected.

    Player has a rigidbody, as has the crate.

    I want to move the 'crate' 1 unit in any direction I push it from
    , I'm currently getting really unstable results, as my crate jumps around the level, or won't move in x axis, etc, clearly what I thought was going to be fairly simply turns out to be much more complicated to pull off than I can currently get my head round, any help would be appreciated !

    Code Snippet from my script dealing with 'push' movement
    Code (CSharp):
    1.     public void OnCollisionEnter (Collision col) {
    2.         if (col.gameObject.tag == "crate") {
    3.  
    4.             Vector3 direction = transform.position - col.gameObject.transform.position; // Calculate direction to push
    5.  
    6.             direction.y = 0.5f;
    7.  
    8.             if (Mathf.Abs(direction.x) > Mathf.Abs(direction.z)) {
    9.                 direction.z = 0f;
    10.             }
    11.             else {
    12.                 direction.x = 0f;
    13.             }
    14.  
    15.             direction.Normalize();
    16.  
    17.             changePosition(col.transform.position + direction * 1f); // Push
    18.         }
    19.     }
    20.  
    21.     public void changePosition(Vector3 to) {
    22.         newPosition = to;
    23.     }
     
  2. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    If it needs to move in only the cardinal directions, it's more complicated than this. You need to take the player position and the box position and sort of cut the difference into quadrants to determine a straight <= => direction and use that- if it's exactly to the => right, that's easy, but if it's NE then you need to know if it's slightly more N or slightly more E, to know which direction to use. The player's position should not be used directly in the calculations this way, but only to determine which of the 4 preset directions to apply to the box.

    A quick and easy way to do this might be to just take the relative player/box position difference to create a direction from that, compare it to the four options (left, right, forward, and back), and take the smallest absolute difference and use that. So something like:
    Code (csharp):
    1. private enum directions
    2. {
    3.     left = 0,
    4.     right,
    5.     forward,
    6.     back
    7. };
    8.  
    9. private Dictionary<directions, Vector3> lookupTable = new Dictionary<directions, Vector3>();
    10.  
    11.  
    12. private void Awake()
    13. {
    14.     UpdateDirectionLookup();
    15. }
    16.  
    17. private void UpdateDirectionLookup()
    18. {
    19.     Transform trans = GetComponent<Transform>();
    20.     lookupTable.Clear();
    21.     lookupTable.Add(directions.left, -trans.right);
    22.     lookupTable.Add(directions.right, trans.right);
    23.     lookupTable.Add(directions.forward, trans.forward);
    24.     lookupTable.Add(directions.back, -trans.forward);
    25. }
    26.  
    27. private void OnCollisionEnter(Collision col)
    28. {
    29.     if (col.gameObject.tag == "player")
    30.     {
    31.         directions pushDirection = DeterminePushDirection(col.transform.position - transform.position);
    32.         PushInDirection(pushDirection);
    33.     }
    34. }
    35.  
    36. private directions DeterminePushDirection(Vector3 vectorDirection)
    37. {
    38.     float smallestAngle = float.PositiveInfinity;
    39.     directions selectedDirection = directions.left;
    40.  
    41.     float tempAngle = 0;
    42.     for (int i = 0; i < lookupTable.Count; i++)
    43.     {
    44.         tempAngle = Vector3.Angle(vectorDirection, lookupTable[(directions)i]);
    45.         if (tempAngle < smallestAngle)
    46.         {
    47.             smallestAngle = tempAngle;
    48.             selectedDirection = (directions)i;
    49.         }
    50.     }
    51.  
    52.     return selectedDirection;
    53. }
    54.  
    55. private void PushInDirection(directions pushDirection)
    56. {
    57.     transform.position += lookupTable[pushDirection];
    58. }
    ... and put that on the crate, not the player. I probably missed something stupid in there- I typed it into the response area so it may not quite compile as-is, but should give you a rough idea. I made calculating the direction and actually pushing in the direction two different functions, this way you can replace the latter with a coroutine pretty easily, and disable it from re-occurring while being moved, etc...

    A slightly different approach is considering the crate as a real-world object in which the player has to be able to animate pushing on the sides. In that case, you'll have 4 planes to push against (one on each side), and each plane needs to be a sort of independent surface so you aren't constant running calculations every single frame about what you're pushing against. In that case, I would actually use 4 different little trigger areas, and the math of setting up which trigger is responsible for which direction should be done once, in Start, and never again. Then, when you push on the box while in a specific trigger area, the box controller says "oh, that's the Right side trigger" and moves left- the player isn't used in the calculation at all except to trigger the push function.

    That feels far cleaner to me.
     
  3. Deleted User

    Deleted User

    Guest

    Thank you for the super informative answer, got me looking at new techniques and ways of thinking about this.

    I tried the code, but it's still super glitchy in my scene test, I like the idea of the 4 planes to push against theory, I may end up trying this method instead. I've uploaded a small test scene with your help implemented to see what I mean about it being glitchy, my Player cube 'tumbles', but I also have a standard movement Player cube in the scene also, which is disabled should you wish to alternate.
     

    Attached Files: