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

Collision isn't exact and that is a problem for me.

Discussion in '2D' started by AAcat, Aug 3, 2019.

  1. AAcat

    AAcat

    Joined:
    Apr 11, 2015
    Posts:
    19
    side tangent: I tried seeing if I could get this problem resolved in the Answers page, but it seems that the question is too complex and requires moderator approval or something, Idk, but it has been almost a week since I asked and it hasn't gotten approved, and I can't sit on this for too long as it is a core mechanic of the game.

    I've been working on this game, that is pixel perfect. Each tile is 8x8 for the tilemap, and the characters are 16x16 (with 8x16 box colliders, but not really and I'll get to that). So I created this code below to dictate how the character moves, they move 1 unit up, down, left, or right. However, to make sure it doesn't seem snappy, I have the character move 0.125 units in that direction for 8 updates until it reaches a whole number. (1/8 = 0.125 and each tile is 8x8 pixels so the character moves 1 pixel per update, making a smooth transition.) And if there is a tile that contains a collider, the character can't move there.
    I have this working with the following code, but before you see it, understand why it is the way it is. First off, I originally intended the box colliders to be exactly 1 unit (8 pixels) big (no decimal places what so ever), so it perfectly wraps around specific objects and can act as a wall. However, if the player is also that exact, being 1x2 units (8x16 pixels) and the player is standing next to a wall (doesn't even have to touch) the character will be pushed off their aligning, and since the players position has been moved a fraction of a fraction (so small that the player wouldn't even notice they were moved) this would create a soft lock, where the player can't move because they are off the grid.
    Why did they get moved slightly off the grid? The unity engine has colliders not actually touch each other, there will always be a small gap between colliders no matter what, so even if they are next to each other perfectly without any force pushing them together, there will always be a gap, and because of this gap it means something has to move, in this case, the player, who will be moved slightly off the grid.
    Bellow is code that works fine, as long as the player is smaller than 1x2 units, ( such as 0.9x1.9 ) because it doesn't move the character.
    Code (CSharp):
    1.  
    2. public class Movement : MonoBehaviour {
    3.     private Rigidbody2D con;
    4.     public bool up;
    5.     public bool down;
    6.     public bool left;
    7.     public bool right;
    8.  
    9.     public bool inputer;
    10.  
    11.     private Vector2 rict;
    12.     public bool upc;
    13.     public bool downc;
    14.     public bool leftc;
    15.     public bool rightc;
    16.    
    17.     void Start () {
    18.         con = GetComponent<Rigidbody2D> ();
    19.         con.MovePosition(new Vector2(Mathf.Round(con.position.x), Mathf.Round(con.position.y)));
    20.     }
    21.    
    22.     void FixedUpdate () {
    23.         if(right || left || up || down) {inputer = false;}else{inputer = true;} //check if a input can be made
    24.         if(inputer && Input.GetAxisRaw ("Horizontal") > 0 && !rightc){leftc = false;downc= false;upc= false; right = true;rict = new Vector2 (0.125f, 0);con.MovePosition(con.position + rict);}//these 4 else if moves the character
    25.         else if(inputer && Input.GetAxisRaw ("Horizontal") < 0 && !leftc){rightc = false;downc= false;upc= false; left = true;rict = new Vector2 (-0.125f, 0);con.MovePosition(con.position + rict);}
    26.         else if(inputer && Input.GetAxisRaw ("Vertical") > 0 && !upc){downc= false;leftc = false;rightc = false; up = true;rict = new Vector2 (0, 0.125f);con.MovePosition(con.position + rict);}
    27.         else if(inputer && Input.GetAxisRaw ("Vertical") < 0 && !downc){upc= false;leftc = false;rightc = false; down = true;rict = new Vector2 (0, -0.125f);con.MovePosition(con.position + rict);}
    28.         else if (right && con.position.x < Mathf.Ceil(con.position.x)){con.MovePosition(con.position + rict);}//these next 4 are the locked direction the player must go, until they reach a whole number.
    29.         else if (left && con.position.x > Mathf.Floor(con.position.x)){con.MovePosition(con.position + rict);}
    30.         else if (up && con.position.y < Mathf.Ceil(con.position.y)){con.MovePosition(con.position + rict);}
    31.         else if (down && con.position.y > Mathf.Floor(con.position.y)){con.MovePosition(con.position + rict);}
    32.         else if (right) {right = false;}//these next 4 are to re-allow the player to select a direction to move
    33.         else if (left) {left = false;}
    34.         else if (up) {up = false;}
    35.         else if (down) {down = false;}
    36.         else {rict = new Vector2();}//idle
    37.  
    38.         if(con.position.x % 0.125 != 0){con.MovePosition(new Vector2(Mathf.Round(con.position.x), con.position.y)); if (right) {right = false; rightc = true;} else if (left) {left = false; leftc = true;} else if (up) {up = false; upc = true;} else if (down) {down = false; downc = true;}} //the directions with a c after it stands for cancel, if the player is pushed back to the previous tile for whatever reason (which is what the mathf.round is doing), the last known direction is marked as canceled and the player cant move in that direction again. This prevent the player from jolting back and forth, but still has 1 jolt.
    39.         if(con.position.y % 0.125 != 0){con.MovePosition(new Vector2(con.position.x, Mathf.Round(con.position.y)));if (right) {right = false; rightc = true;} else if (left) {left = false; leftc = true;} else if (up) {up = false; upc = true;} else if (down) {down = false; downc = true;}}
    40.     }
    41. }
    42. }
    My temporary solution to the collision is the last two "if" statements. They are responsible for moving the character back to the grid if they had been pushed by another collider box. As a result the direction that was last detected is canceled (thus the c after each input in the naming of the variables) as this will prevent the player from spamming into a wall and causing the character to shake like crazy. This (however) still requires the player to push against the wall and jolt back once, and that jolting action is something that I don't want. Not just because it is asthetically unpleasing, but causes the camera to shake in a way that is not satisfactory.
    Here are some videos showing my evolution to solving this problem, the first video showing the soft lock I mentioned if the player is the exact size.

    The second video shows the small gap that is causing the problem.

    and the third video shows the jolting I mentioned before.


    Now the only problem left for me is to figure out how to stop it from doing the initial jolt, or if there is a bigger solution to all of this.
    Here is what it looks like working right now:


    Any solution would be nice, thx :v
     
  2. TRRBusiness

    TRRBusiness

    Joined:
    Aug 7, 2019
    Posts:
    6
    From what I understand, PhysX/ physics systems need a minimum contact offset to work properly. If you look in the project settings you'll see the default is 0.01f. You can scale everything up but it'll still be there.

    Unfortunately, if your dead set on an actual rigidbody you'll have to adjust something. Raycasting would be much more precise if you absolutely need perfect edges but that has its own set of drawbacks.

    Far as jolting etc. I would separate your Sprite from the body and sync it after your done resolving movement.

    E