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
  4. Dismiss Notice

Bug Collision detection between Player and Environment

Discussion in 'Physics' started by Karrzun, Dec 9, 2022.

  1. Karrzun

    Karrzun

    Joined:
    Oct 26, 2017
    Posts:
    123
    Hi everyone!

    I'm working on a clone of Nintendo's "Tanks!" game. For reference, here's a video:



    Unfortunately, I'm having issues with the movement regarding collision detection with the environment and I simply don't get why. Currently, I have 4 outer walls that function as a level boundary and 3 inner walls, scattered around the level. All of these walls have the exact same components with the exact same settings (except for their transform scales). All of the outer walls and one inner work as intended and block the player when he drives into them. The other two inner walls result in weird behaviour such as:

    - The player ignores the wall and keeps on driving through it.
    - The player drives into the wall and is then catapulted onto the other side.
    - The player bounces back when colliding with a wall.

    These results are always the same when hitting the walls at the same locations so at least it's persistent.
    What irritates me the most is the fact that the inner wall that's working as intended and one of the non-working inner walls are duplicates of another, meaning they even have the same size. They are just a few steps apart, yet lead to these different results.

    Here's a short video of what's happening:

    https://i.imgur.com/Cjq4fqb.mp4

    Anyone knows why?


    Kind regards
     
  2. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,428
    How are you moving the tanks? Proper collision detection and handling requires moving the rigidbodies with AddForce/AddTorque (and their variants) from FixedUpdate. Any other method of moving rigidbodies (i.e. modifying the Transform, applying physics from Update, etc) can cause collision issues like those in the video.
     
    kallais likes this.
  3. Karrzun

    Karrzun

    Joined:
    Oct 26, 2017
    Posts:
    123
    I've tried calling rigidbody.MovePosition and setting the velocity directly via rigidbody.velocity both from within the FixedUpdate function. Both approaches yield similar results.
     
  4. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,428
    That's the problem. Calling MovePosition and/or setting velocity are wrong methods in this scenario:
    • Calling Rigidbody.MovePosition forcefully moves the tank to the new position, in direct conflict with any collision resolution from the physics engine.
    • Modifying Rigidbody.velocity overrides the velocity calculations from the physics solver, again in conflict with any possible velocity computed internally as a resolution of a collision.
    Use AddForce/AddTorque (and/or their variants, like AddForceAtPosition) exclusively and always from FixedUpdate. These methods allow an optional parameter ForceMode that allows to modifying the velocity instead of applying a force/torque.

    For example, you may impose a specific velocity vector to the rigidbody like this:
    Code (CSharp):
    1. rigidbody.AddForce(expectedVelocity - rigidbody.velocity, ForceMode.VelocityChange);
    This will make the rigidbody to move at expectedVelocity, but also it will collide with the obstacles properly.
     
    Last edited: Dec 11, 2022
  5. Karrzun

    Karrzun

    Joined:
    Oct 26, 2017
    Posts:
    123
    Thank you for your input. I changed the code to use the AddForce method as you suggested. Unfortunately, the results are still the same.

    What baffles me the most about all of this, is the extremely different behaviours achieved when interacting with identical obstacles. As described in my initial post, there are 2 absolutely identical inner walls that persistently lead to very different results. I don't know whether there's a hint about my mistake in there but at least that's peaking my interest the most.

    //Edit: For better understanding, I set up my movement code as follows:

    Code (CSharp):
    1. private void FixedUpdate()
    2.     {
    3.         //rigidbody.MovePosition(transform.position + direction * speed * Time.fixedDeltaTime);
    4.         Vector3 expectedVelocity = direction * speed * Time.fixedDeltaTime;
    5.         rigidbody.AddForce(expectedVelocity - rigidbody.velocity, ForceMode.VelocityChange);
    6.     }
    with direction being a normalized Vector3 and speed being a float
     
    Last edited: Dec 11, 2022
  6. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,428
    Not related with the issue, but using Time.fixedDeltaTime in the calculation of the velocity is incorrect. Simply remove it, and you can specify the speed as m/s directly (or the speed units you use). Otherwise, if you leave it as is now, the velocity will be dependent of the physics time step used. For example, increasing the physics rate to 100 Hz (default is 50 Hz) will reduce the speed to the half.

    As for diagnosing the actual issue, there's something wrong in your scene or the scripts. I'd create a minimal scene from scratch with a plane as floor, a cube as tank, and some other cubes as obstacles. Add a script with the motion code exclusively, and nothing else. That must work. Then add new elements progressively from the other scene, testing the result after each change, and it should fail at some point. Then it should be simple to find the exact cause and solve it in the original scene.
     
    Last edited: Dec 11, 2022
  7. Karrzun

    Karrzun

    Joined:
    Oct 26, 2017
    Posts:
    123
    First things first:
    I created a new project and only added the ground plane, one player cube and three walls to the scene as well as the movement scripts. Once again, the results are the same. The interaction with one of the walls is as expected, collisions with the other two walls lead to the player driving into the walls or bouncing around.

    (At this point I'd like to add, that all these interactions always seem to push the player to the right, i.e. he can only drive through a wall from left to right and only bounces back when driving into a wall from the right side.)

    For movement I'm using two kind of scripts:
    1. MovementBehaviours: these implement different intentions regarding movement. One agent can theoretically have multiple MovementBehaviours, e.g. one for following the player, one for dodging land mines, one for dodging bullets, etc. The player itself only has a single MovementBehaviour, though, that reads the input from WASD and translates that into a direction.
    2. A general Moving script that knows all MovementBehaviours that are attached to this GameObject. Then on Update, it checks each MovementBehaviour's desired direction to move, weighs them against each other and, consequently, decides in which direction to move.

    Maybe there's a problem somewhere in there that I miss so here are the complete scripts. Also, I'm attaching the sample project (only consisting of the movement parts) to this post.

    Code (CSharp):
    1. public abstract class MovementBehaviour : MonoBehaviour
    2. {
    3.     public Vector3 WeightedDirection => CalculateWeightedDirection();
    4.    
    5.     protected Moving moving;
    6.  
    7.  
    8.     protected virtual void Awake()
    9.     {
    10.         moving = gameObject.GetComponent<Moving>();
    11.         enabled = (moving != null);
    12.     }
    13.  
    14.     protected void OnEnable() => moving.Behaviours.Add(this);
    15.     protected void OnDisable() => moving.Behaviours.Remove(this);
    16.  
    17.  
    18.     protected abstract Vector3 CalculateWeightedDirection();
    19.  
    20. }
    Code (CSharp):
    1. public class PlayerMove : MovementBehaviour
    2. {
    3.     [SerializeField] private KeyCode upKey = KeyCode.W;
    4.     [SerializeField] private KeyCode downKey = KeyCode.S;
    5.     [SerializeField] private KeyCode leftKey = KeyCode.A;
    6.     [SerializeField] private KeyCode rightKey = KeyCode.D;
    7.  
    8.     private Vector3 direction;
    9.  
    10.  
    11.     protected override Vector3 CalculateWeightedDirection()
    12.     {
    13.         direction = Vector3.zero;
    14.  
    15.         if (Input.GetKey(upKey))
    16.             direction.z += 1;
    17.  
    18.         if (Input.GetKey(downKey))
    19.             direction.z -= 1;
    20.  
    21.         if (Input.GetKey(leftKey))
    22.             direction.x -= 1;
    23.  
    24.         if (Input.GetKey(rightKey))
    25.             direction.x += 1;
    26.  
    27.         return direction;
    28.     }
    29.  
    30. }
    Code (CSharp):
    1. public class Moving : MonoBehaviour
    2. {
    3.     [SerializeField] private MovementSpeed movementSpeed = MovementSpeed.Normal;
    4.     private float speed;
    5.  
    6.     private Vector3 direction = Vector3.zero;
    7.     private new Rigidbody rigidbody;
    8.  
    9.     public readonly List<MovementBehaviour> Behaviours = new List<MovementBehaviour>();
    10.  
    11.  
    12.     private void Awake()
    13.     {
    14.         speed = (int)movementSpeed * 5f;
    15.         rigidbody = gameObject.GetComponent<Rigidbody>();
    16.     }
    17.  
    18.     private void Update() => direction = GetDirection();
    19.  
    20.     private void FixedUpdate()
    21.     {
    22.         if (direction.magnitude == 0f)
    23.             return;
    24.  
    25.         Quaternion orientation1 = Quaternion.LookRotation(direction);
    26.         Quaternion orientation2 = Quaternion.LookRotation(-direction);
    27.  
    28.         Quaternion targetOrientation = (Quaternion.Angle(transform.rotation, orientation1) < Quaternion.Angle(transform.rotation, orientation2))
    29.             ? orientation1
    30.             : orientation2;
    31.  
    32.         Quaternion rotation = Quaternion.Lerp(transform.rotation, targetOrientation, 0.2f);
    33.         rigidbody.MoveRotation(rotation);
    34.         Vector3 expectedVelocity = direction * speed * Time.fixedDeltaTime;
    35.         rigidbody.AddForce(expectedVelocity - rigidbody.velocity, ForceMode.VelocityChange);
    36.     }
    37.  
    38.     private Vector3 GetDirection()
    39.     {
    40.         Vector3 result = Vector3.zero;
    41.         foreach (MovementBehaviour behaviour in Behaviours)
    42.         {
    43.             result += behaviour.WeightedDirection;
    44.         }
    45.         return result.normalized;
    46.     }
    47.  
    48. }
    49.  
    50.  
    51. public enum MovementSpeed
    52. {
    53.     Stationary = 0,
    54.     Slow = 15,
    55.     Normal = 25,
    56.     Fast = 35
    57. }
    I'd be very grateful if you could take a closer look at it.


    On a side note, I'd be interested to know, why one shouldn't use Time.fixedDeltaTime. As far as my understanding goes, it prevents jerky movement if my game happens to have frame drops. I know FixedUpdate is supposed to run at a set timestep but what if my CPU can't keep up with that rate? I thought, fixedDeltaTime helps handle that. Also, it prevents changes in movement if I happen to change the desired timestep for physics calculations.

    Reference:
    https://answers.unity.com/questions/1391729/should-i-be-using-timefixeddeltatime-here.html
     

    Attached Files:

  8. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,428
    This part incorrect:
    Code (CSharp):
    1. rigidbody.MoveRotation(rotation);
    2. Vector3 expectedVelocity = direction * speed * Time.fixedDeltaTime;
    • MoveRotation still overrides the calculations of the Physics solver. Use AddTorque instead. ForceMode.VelocityChange may also be specified in AddTorque.
    • Using Time.fixedDeltaTime for calculating the speed makes your code dependent of the physics time step. Simply remove it and adjust the units of speed. Otherwise, different physics rates will change the behavior of the character, which is not the expected behavior.
     
    Alessanchuk likes this.
  9. Karrzun

    Karrzun

    Joined:
    Oct 26, 2017
    Posts:
    123
    I did as you suggested and reworked the code to use AddTorque and removed Time.fixedDeltaTime. It didn't change the behaviour though. The player still glitches through walls, bounces off of them, etc.

    I started the project in Unity 2020.3.26f1. Are there any known bugs in this Unity version that I missed?
     
  10. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,428
    No, that's a misunderstanding. In this case what you're doing is just multiplying the velocity by an arbitrary number, 0.02 by default. If you change the fixed timestep to 0.01, then the velocity will be the half of the original velocity and your object will be moving at half of the expected velocity.

    deltaTime is used when you're calculating a position using a velocity, or a velocity using an acceleration. Calculating a position using a velocity is "pos1 = pos0 + velocity * deltaTime". For calculating a velocity using an acceleration, it's "velocity1 = velocity0 + acceleration * deltaTime".

    In the case of rigidbody velocity, you feed the rigidbody with the desired velocity in m/s and the physics engine internally calculates the new position as "pos1 = pos0 + velocity * deltaTime".
     
  11. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,428
    Nope, it's working fine:


    This is the controller used in the video:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class CharacterMovement : MonoBehaviour
    4.     {
    5.     public float speed = 10.0f;
    6.     public float rotation = 5.0f;
    7.  
    8.     Rigidbody m_rb;
    9.  
    10.     void OnEnable ()
    11.         {
    12.         m_rb = GetComponent<Rigidbody>();
    13.         }
    14.  
    15.     void FixedUpdate()
    16.         {
    17.         Vector3 velocity = transform.forward * speed * Input.GetAxis("Vertical");
    18.         m_rb.AddForce(velocity - m_rb.velocity, ForceMode.VelocityChange);
    19.  
    20.         Vector3 angularVelocity = transform.up * rotation * Input.GetAxis("Horizontal");
    21.         m_rb.AddTorque(angularVelocity - m_rb.angularVelocity, ForceMode.VelocityChange);
    22.         }
    23.     }
    24.  
     
    Last edited: Dec 21, 2022
  12. Alessanchuk

    Alessanchuk

    Joined:
    Jul 3, 2023
    Posts:
    1
    New
    Bro, you are realy cool, you helped me a lot not only to fix my project but i also improved a lot, thanks!
     
  13. Karrzun

    Karrzun

    Joined:
    Oct 26, 2017
    Posts:
    123
    Very late response on my side I'd like to add if someone stumbles upon this problem again: even with the controller you posted, I had the very same issues.
    I had been using RigidBody components on the tank as well as the walls and obstacles because I had been testing several other ideas regarding the latter. After disregarding them, I removed the RB component because I no longer wanted to move those GameObjects. Removing the RB component from the wall/obstacles solved the issue.
    The tank obviously still uses a RB for moving, though.