Friday 24 April 2015

How to detect Collisions in Unity. Like a BOSS.


Any game developer beginner has asked himself at some point... how do I know if my character collided with the enemy? or reached the end of the level? or a little bit more specific, what part of the body of the character was hit with the enemy bullet? The answer to all this questions is in the Unity Physics Engine. Thanks to it you will have the most ridiculously easy way to integrate physics into your game (like a line of code? :D)

As I said in my previous post about Delegates and Lerp, this is the next in the family of "Like a BOSS" tutorials. They are simple and straight to the point. I focus on the best utilities I find for each of the components and my best practises with them. In the post you will find an example about Collisions in 2D and at the end you can enjoy the full git project :)

Collisions are of the first things you probably learnt while doing the unity official tutorials. Using collisions is probably one of the simplest things in Unity. Then why am I writing a post about it? Surprisingly, many people find problems while working with them, silly errors due to how they work most of the time. I will try to show you the way of efficiently working with them, avoid this errors and give a little bit more indepth utility like detecting the exact point of the collisions.

(Almost) All kind of colliders in  Unity. Some of them I didn't even knew xD

Adding colliders to your gameObjects and moving them around (probably with a rigidbody) will allow you to automatically receive unity messages in your monobehaviour scripts. We can say it happens "magically" the same way than with "Start" or "Update" methods.

After unity 4.3 that added the 2D components to the engine things got a little bit messy, but still clear enough if you keep this in mind:
  • Physics3D and Physics2D are totatlly independent systems that doesn't interact with each other.
  • Collisions between 2 Colliders produce messages of the kind "OnCollisionEnter(Collision collision)"
  • Collisions between 2 Colliders when at least one of them is marked as a "Trigger" will produce messages of the kind "OnTriggerEnter(Collider collider)"
  • Collisions are possible even without rigidbodies, but rigidbodies will make the gameObjects behave like physical objects (gravity, forces...). In general if the object moves it should always have a rigidbody, because Unity will make internal optimizations. Additionally, if none of the gameObjects has a rigidbody attached you will not receive any event!

What are the most basic callbacks for collisions?

// Basic Collisions in 3D with another collider
void OnCollisionEnter(Collision collision) {
  Debug.Log("OnCollisionEnter with GO: " + collision.gameObject.name, this);
}
	
void OnCollisionStay(Collision collision) {
  Debug.Log("OnCollisionStay with GO: " + collision.gameObject.name, this);
}

void OnCollisionExit(Collision collision) {
  Debug.Log("OnCollisionExit with GO: " + collision.gameObject.name, this);
}

// Basic Collisions in 3D with a Trigger collider
void OnTriggerEnter(Collider collider) {
  Debug.Log("OnTriggerEnter with GO: " + collider.name, this);
}

void OnTriggerStay(Collider collider) {
  Debug.Log("OnTriggerStay with GO: " + collider.name, this);
}

void OnTriggerExit(Collider collider) {
  Debug.Log("OnTriggerExit with GO: " + collider.name, this);
}


In the project you can find at the end of the post I added all the possible variables and callbacks related to Collisions in a template file you can use to play around. It's called "AllCollisionsTemplate". Feel free to download it :)

It's time to show you the example I prepared for the occasion. It's my very nice looking and crazy version a Solar System where planets bounce around forever inside the asteroid field. :D



Bouncy physics are achieved though PhysicMaterials

Credits for the art: Meowx.

In this example, whenever two planets collide they create an small particle explosion. When a planet collides with the asteroid field, a collision is registered and the top counter updated :)

This is the code related to the planet collision detection

// Class that reacts to the collisions of the planet
public class PlanetController : MonoBehaviour {
	
// prefab variables
public GameObject collisionPrefab;

  // The collision starts NOW
  void OnCollisionEnter2D(Collision2D collision) {
    if (collision.gameObject.GetComponent< planetcontroller>() != null){
      // We are colliding with another planet
      Instantiate(collisionPrefab, GetMiddlePoint (collision.contacts), Quaternion.identity);
    }
  }

  // Helper method to find the middle point of the contacts
  // In this example this should not be neccesary as contacts.Lenght should be equal 1 always
  public Vector2 GetMiddlePoint (ContactPoint2D[] contacts){
    // We follow the centroid method: http://en.wikipedia.org/wiki/Centroid
    Vector2 centroid = Vector2.zero;
    foreach (ContactPoint2D contact in contacts) {
      centroid += contact.point;
    }
    centroid = (centroid / contacts.Length);
  
    return centroid;
  }
}


The asteroids are actually a big polygon collider and not a set of circle colliders. Try to reduce the amount  of colliders in your game whenever possible as it greatly affects efficiency (specially when animating them!).

// Class that reacts to the collisions of the asteroids
public class AsteroidsController : MonoBehaviour {

  // Keep track of the amount of collisions
  public int collisionCount = 0;

  // Notify collisions to UI
  public delegate void CollisionEntered();
  public event CollisionEntered OnCollisionEntered;

  // The collision starts NOW
  void OnCollisionEnter2D(Collision2D collision) {
    if (collision.gameObject.GetComponent< planetcontroller>() != null){
      // We are colliding with a planet
      IncreaseCollisionCount();
    }
  }

  void IncreaseCollisionCount ()
  {
    // Normal increment
    collisionCount ++;

    // We notify the listeners
    if (OnCollisionEntered != null)
      OnCollisionEntered();
  }
}


If you don't understand how the uGUI Text get's notified of the changes in the collisionCount variable, please check my previous post about Delegates. I though it was a great opportiny to put it into practice :)


That's all for today, it may even look short! :D That's because very few lines of code are neccesary to effectivelly work with Collisions. However, it is possible to take the control over the Physics System and check for any kind of Raycast or Collision manually. This is only recommended as a last resort since it will be always less efficient.

Like I was mentioning before, because of the unity messages "black magic", you have to be extra careful and follow this steps whenever you don't get the expected result:
  • Make sure you are writing the header of the method properly. If you miss a single letter it will not work and what is worse, it will not complain.
  • Make sure you attached a collider to the gameObject.
  • Make sure the colliders are properly set as "Trigger" or not depending what you want.
  • Try paying around with the "Kinematic" field of the rigidbodies. Enable it if you are moving it manually and not by forces or gravity.
  • Make sure the rigidbody is not sleeping. In 2D you can set in the inspector to never sleep but in 3D you will have to write an small custom script for it.

    The ultimate example of Physics and collisions... the Large Hadron Collider :)

    Here is the link to the complete example posted on github.

    Here are the links to the official documentation: collisions, colliders3D, colliders2D, Physics.

    Thanks Adam for the corrections to the post! :)

    1 comment: