Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Top-down 2D Collision

Discussion in '2D' started by Wispy, Jun 10, 2015.

  1. Wispy

    Wispy

    Joined:
    May 17, 2015
    Posts:
    2
    Hi all!

    This is my first time posting on the forums - I searched around a bit, but couldn't find an answer. I'm trying to build a top-down 2D action game (think Zelda or Bomberman) using the 2D features of Unity, but I'm having difficulty getting the collision detection with walls, etc to work properly.

    Currently, I'm doing a raycast and moving the player to a location on the grid if and only if there's no hit detected, which works great for grid movement. But I'd like the player to move more smoothly along both axes, so the raycast won't detect potential collisions with the player's BoxCollider2D, just if the location right in front of it is occupied.

    Here is my grid based movement script (works great):
    Code (CSharp):
    1. private void Move(int horizontal, int vertical)
    2.     {      
    3.         Vector2 direction = new Vector2(horizontal, vertical) * Constants.BlockSize;
    4.         Vector2 start = this.transform.position;
    5.         Vector2 end = start + direction;
    6.        
    7.         RaycastHit2D hit = Physics2D.Linecast (start,end, this.BlockingLayer);
    8.        
    9.         if (null == hit.transform)
    10.         {
    11.             this._IsMoving = true;
    12.             this.AnimateMovement(horizontal, vertical);
    13.             StartCoroutine(SmoothMovement(end));
    14.         }
    15.         else
    16.         {
    17.             this._IsMoving = false;
    18.         }
    19.     }
    20.    
    21.     private IEnumerator SmoothMovement(Vector3 end)
    22.     {
    23.         float sqrRemainingDistance = (transform.position - end).sqrMagnitude;
    24.        
    25.         while (sqrRemainingDistance > float.Epsilon)
    26.         {
    27.             Vector3 newPosition = Vector3.MoveTowards(this._Rigidbody.position, end, this._InverseMoveTime * Time.deltaTime);
    28.             this._Rigidbody.MovePosition(newPosition);
    29.             sqrRemainingDistance = (transform.position - end).sqrMagnitude;
    30.             yield return null;
    31.         }
    32.         this._IsMoving = false;
    33.         yield return null;
    34.     }
    And here is my attempt at "sliding" movement (doesn't work):
    Code (CSharp):
    1. private void Move(int horizontal, int vertical)
    2.     {      
    3.         this.AnimateMovement(horizontal, vertical);
    4.         this.Move(new Vector2(horizontal, vertical));
    5.     }
    6.    
    7.     private void Move(Vector2 direction)
    8.     {      
    9.         Vector2 start = this.transform.position;
    10.         Vector2 end = start + direction * this.Speed * Time.deltaTime;
    11.         RaycastHit2D hit = Physics2D.Linecast (start, end, BlockingLayer);
    12.        
    13.         if (this._BoxCollider.IsTouchingLayers(this.BlockingLayer) && null != hit)
    14.         {
    15.             return;
    16.         }
    17.        
    18.        
    19.         Vector3 newPosition = new Vector3(end.x, end.y, 0f);
    20.         this._Rigidbody.MovePosition (newPosition);
    21.     }
    I've also tried using the physics engine and adjusting the player's velocity, which ended up perfectly detecting collisions as I expected, but had some weird properties like not animating the player sprite correctly, or causing the player sprite to spin around if it collided with a corner of a box collider.

    Can anyone help me figure out how to get the collision working as expected for the "sliding" case? Thanks!
     
  2. Wispy

    Wispy

    Joined:
    May 17, 2015
    Posts:
    2
    So one bug with the code here that I found is that I should just be setting the transform.position instead of using RigidBody2D.MovePosition. However, once I make that change, there's another problem.

    Obviously, the raycast is originating from the center of the player sprite, instead of the box collider. Is there an equivalent method for checking if a collider will overlap the blocking layer if moved in a certain direction?
     
  3. Async0x42

    Async0x42

    Joined:
    Mar 3, 2015
    Posts:
    104
    Not sure if you've found the solution to this since then, but I ran into the same issue as you at some point and after some digging found BoxCast, here's a quick example that was working on my end, but not extensively tested. I think I got HitTestBoxPath2D from the forums here somewhere.

    Code (CSharp):
    1.  
    2.     private bool DirectionIsAvailable(Vector2 direction, float distanceToMove)
    3.     {
    4.         //TODO, cache this somehow
    5.         var collider = ParentGameObject.GetComponents<BoxCollider2D>().First(c => c.isTrigger == false);
    6.         collider.enabled = false;
    7.  
    8.         var from = collider.transform.position;
    9.         var to = (Vector2)from + (direction * distanceToMove);
    10.  
    11.         RaycastHit2D[] hits = new RaycastHit2D[10];
    12.  
    13.         var me = HitTestBoxPath2D(from, to, collider.size, hits, GameManager.Instance.CollisionLayers.value);
    14.  
    15.         collider.enabled = true;
    16.  
    17.         return me == 0;
    18.     }
    19.  
    20.     public static int HitTestBoxPath2D(Vector2 from, Vector2 to, Vector2 size, RaycastHit2D[] hits, int layerMask)
    21.     {
    22.         return Physics2D.BoxCastNonAlloc(from, size, 0, to - from, hits, Vector2.Distance(from, to), layerMask);
    23.     }