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

Preventing dynamic, moving, Rigidbody2D-based entities from "pushing" each other

Discussion in '2D' started by AssembledVS, Jul 26, 2017.

  1. AssembledVS

    AssembledVS

    Joined:
    Feb 23, 2014
    Posts:
    248
    In my Zelda-like game, my movable entities (the player, enemies, etc.) have dynamic Rigidbody2D and non-trigger BoxCollider2D components. They move via forces by calling objRigidbody.AddForce(movementForce). All movable entities share code via the same Monobehaviour movement script or scripts derived from this movement script, so they all move pretty much in identical ways.

    This seems to work well but I wish to avoid the "pushing" that happens with this physics-based movement. I want my entities to move, but I want them to act like walls to one another; the player should be unable to push other entities out of the way and other entities should be unable to push the player as well as each other.

    Should I change my approach in order to avoid this physics "pushing"? Or is there something that I am overlooking?
     
  2. frasderp

    frasderp

    Joined:
    Oct 6, 2016
    Posts:
    19
    I have solved a similar problem in the past by changing the properties of the RigidBody on collision.

    You'll need to change the BoxCollider to trigger firstly, to detect the collision.

    On trigger -

    Code (CSharp):
    1.  
    2. player.GetComponent<RigidBody2D> ().velocity = Vector2.zero;
    3.  
    4. player.GetComponent<RigidBody2D> ().isKinematic = true;
    5.  
    This will stop the object from continuing to move. You might also have to set it to the parent and target.
     
  3. AssembledVS

    AssembledVS

    Joined:
    Feb 23, 2014
    Posts:
    248
    Thanks for the reply.

    The issue that I see with this is this: it will stop movement regardless of direction. For example, if I'm walking right, past an NPC who is up, and brush against this entity, it will stop movement, even though I am not "walking into" the NPC. This will also have the same effect on the NPC's movement if I brush against a moving NPC, since all of my entities - the player, the NPC, and enemies - function very similarly.

    Is there something that I am not getting in this? Do I need to scrap physics-based movement if I wish to achieve this? I like the ability to slide around solid objects (moving along a diagonal wall, etc.), but not pushing them around with physics.
     
  4. AssembledVS

    AssembledVS

    Joined:
    Feb 23, 2014
    Posts:
    248
    I figured out the simple solution to this last night. I have not seen this solution mentioned in any of the threads or Unity Answers questions that I've come across, so here it is:
    • All characters have dynamic Rigidbody2D components with the 2D collider of your choice.
    • They also have child game objects with a kinematic Rigidbody2D component and its own 2D collider of your choice.
    • Either the parent dynamic rigidbody's or the child kinematic Rigidbody2D's collision detection mode should be set to "continuous" instead of "discrete" or your object will still be pushed., although strangely and slowly.
    • Some code in Start or Awake that tells the parent collider and child collider to ignore each other, like:
      Physics2D.IgnoreCollision(colliderToIgnoreA, colliderToIgnoreB);
    This will allow you to have "normal" Unity entities with standard dynamic rigidbodies that do not push around other entities upon collision. A few notes:

    1. The reason you want a parent dynamic rigidbody and child kinematic rigidbody is because dynamic colliding with dynamic pushes objects around, while kinematic/kinematic causes objects to pass through one another. But dynamic/kinematic causes the kinematic rigidbody (with a collider) to simply stop upon collision. This way, dynamic collides with both dynamic and kinematic at the same time and you get the advantages of both for both static level stuff like trees and walls as well as dynamic objects like characters.

    2. You may want the child object's collider (with the kinematic rigidbody) to be slightly larger than the parent's collider. This will make sure that the dynamic to kinematic collision will be picked up first, meaning that there is no chance of slight nudges.

    3. Without the code to ignore collision, you will have a parent dynamic RB with a collider on top of a child kinematic RB with a collider. Because a dynamic/kinematic collision will cause the object with the dynamic RB to stop, the two RBs with colliders on top of one another will pretty much make them "fight" for space, the effect of which is the game object jetting off across the screen as if moving at a very high velocity.

    I never liked the idea of trying to circumvent Unity's physics and stopping movement in the direction of normals or via raycasting, etc. It all seemed like a hack. This uses Unity's own components in a much more direct way and seems to work very well.
     
    Last edited: Aug 8, 2017
    camillazi and shikekaka like this.
  5. Robster95

    Robster95

    Joined:
    Jul 3, 2019
    Posts:
    152
    I am having a similar problem with my code and glad to see someone was able to figure it out! Havent been able to find anything anywhere!

    I am a little confused though. So i'm trying to have "enemies" follow and eventually attack my player. But when they're multiple on screen and they're set to kinematic they tend to overlap each other. When I put them on dynamic they get close to the player and start jumping back and forth a little because of the radius' I made to check for the player. They can also be pushed around which is something I do not want.

    If I apply a child empty gameobject to them and put a rigidbody2d as well as a collider, set it to kinematic and have the parent(main enemy) be set to dynamic do you think this will help fix my problem?

    I'm still fairly new to this and hoping to learn the most I can.

    I'll drop the movement code i have implemented on the enemy below so you can see how I was working with this.

    Code (csharp):
    1.  
    2. public class EnemyMovement : MonoBehaviour
    3. {
    4.     private Rigidbody2D rb;
    5.  
    6.     [SerializeField]
    7.     private Transform playerPos;
    8.  
    9.     //chase radius that checks for player
    10.     [SerializeField]
    11.     private float chaseRadius;
    12.     [SerializeField]
    13.     private LayerMask playerLayer;
    14.     [SerializeField]
    15.     private Vector2 movement;
    16.     [SerializeField]
    17.     private float moveSpeed;
    18.  
    19.     private bool isInRange;
    20.  
    21.     //inner radius check
    22.     [SerializeField]
    23.     private float innerRadius;
    24.  
    25.     //if the player shoots the enemy while outside the radius chase for a set time
    26.     [SerializeField]
    27.     private bool chaseOnShot;
    28.     [SerializeField]
    29.     private float chaseTimerMax;
    30.     [SerializeField]
    31.     private float chaseTimerMin;
    32.  
    33.  
    34.     //commented out just incase needed later
    35.     /*private Collider2D outterRadiusCheck;
    36.     private Collider2D innerRadiusCheck;*/
    37.  
    38.     private void Start()
    39.     {
    40.         rb = GetComponent<Rigidbody2D>();
    41.     }
    42.  
    43.  
    44.     private void Update()
    45.     {
    46.         //if enemies are on kinematic setting they work and dont have problems but can overlap each other
    47.         //dynamic enemies constantly push and jump towards player when in chase range but dont overlap
    48.  
    49.         //possibly just install A* for enemy movement mesh in 2D to fix problems and not have enemies push each other while in movement range
    50.         if(Vector2.Distance(transform.position, playerPos.position) <= chaseRadius && Vector2.Distance(transform.position, playerPos.position) > innerRadius)
    51.         {
    52.             isInRange = true;
    53.         }
    54.  
    55.         else
    56.         {
    57.             isInRange = false;
    58.         }
    59.  
    60.         Vector2 direction = (playerPos.position - transform.position).normalized;
    61.  
    62.         movement = direction;
    63.     }
    64.  
    65.     private void FixedUpdate()
    66.     {
    67.         if(isInRange)
    68.         {
    69.             Debug.Log("yer");
    70.  
    71.             ChasePlayer(movement);
    72.  
    73.             chaseOnShot = false;
    74.         }
    75.  
    76.  
    77.         if(chaseOnShot)
    78.         {
    79.             ChasePlayer(movement);
    80.  
    81.             ChaseTimer();
    82.         }
    83.     }
    84.  
    85.     private void ChasePlayer(Vector2 direction)
    86.     {
    87.         if(Vector2.Distance(transform.position, playerPos.position) > innerRadius)
    88.             {
    89.                 rb.MovePosition((Vector2)transform.position + (direction * moveSpeed * Time.deltaTime));
    90.             }
    91.     }
    92.  
    93.     private void ChaseTimer()
    94.     {
    95.         if (chaseTimerMin > 0)
    96.         {
    97.             chaseTimerMin -= Time.deltaTime * 1;
    98.  
    99.             if (chaseTimerMin <= 0)
    100.             {
    101.                 chaseOnShot = false;
    102.             }
    103.         }
    104.     }
    105.  
    106.  
    107.     private void OnTriggerEnter2D(Collider2D collision)
    108.     {
    109.         if(collision.gameObject.CompareTag("Bullet"))
    110.         {
    111.             chaseOnShot = true;
    112.  
    113.             chaseTimerMin = chaseTimerMax;
    114.  
    115.             chaseTimerMin -= Time.deltaTime * 1;
    116.         }
    117.     }
    118.