Today we do tech talk, exploring the marvelous world of the collisions! Collisions are mostly handled by the physics module in Unity, the software we use to make games. Unity is easy to use and financially accessible to anybody! If you want to learn how to make games, please use this powerful tool as it has the capacity to create pro level games and needs little knowledge to create rudimentary content. I recommend it to pro indies, hobbyists or simply people who just want a taste of what making a game feels like, but as with any editing tool, it doesn’t come with tools that will make your creation feel like a pro game, you have to create these! (or buy them in the assets store) The collisions are great for a physics engine, but we’re making a plaformer here, so the default behaviour is flawed for our needs and we’ll talk about how to overcome these flaws.

Collider for each task.

The character must have several collision zones, each has its use: one attack hit box, one weak hit box (to receive the attacks) and one physical collider. This gives the dev much needed control over how threats/collectibles/events/enhancements/enemy weak points interact with the main characters.

Usually, we like to make the main characters weak collision smaller than his physical collision, so there are more “holy fetuccini, that was close!” moments and less “But I wasn’t even touching it!” moments.

Inversely, we like to make the main characters attack collisions a little bigger, so if there is visual ambiguity, the game will cooperate with the will of the player.

Here is a toto at rest, let’s study its anatomy.

In Unity, there can be only one collider per entity, so we must put the other colliders inside the character, and reference the character to those colliders through a simple home-made mediator system.

void Awake () {//when the object initializes
_master = (ICollidable)master;//change the master in parameter to a usable ICollidable master
}
void OnTriggerEnter(Collider other) {//When there is a (not physicial) collision... if (receiveSignal) {//if the container is made to receive the signal...ColliderContainer otherCol = other.GetComponent<ColliderContainer>();//find the other containerif (otherCol == null) {//if there is no container...
_master.enterCollision(other.gameObject, LayerMask.LayerToName(other.gameObject.layer), other.bounds);//send the message of the collision to my master, referencing the gameObject that hit me
} else if (otherCol.master!= master) {//if there is a container and it has not the same master
_master.enterCollision(otherCol.master.gameObject, LayerMask.LayerToName(other.gameObject.layer), other.bounds);//send the message of the collision to my master, referencing the master of the gameObject that hit me
}
}
}

Collisions need polish.

According to my understanding of unity collision/physics engine, PhysX, my characters overlap with the level colliders for a frame before beign replaced at the correct place, this leads to a really cheap feeling and some errors in the interpretation of collisions at high speeds.

Interpenetration (overlap) is a plague we must act against!

This leads me to make a function called “PreventCheapInterpenetrating” which is executed after the movement of the character is done and before any physical calculation, that checks if the player will touch something over it’s course in the next frame, if yes, it reduces the character’s velocity so it just gently touches the surface instead of ramming into it, this way, physics calculations to determine the position of the character afterward are more accurate and the brief overlap is not visible anymore.

Slow down before your collision, little toto!

Now that we prepare for impact, we must properly analyse the impact; do I touch a ground, a wall, a ceiling, what angle is it? Sadly, the native collision interpretation can be strange sometimes, giving contact points that are out of both the receiving and the colliding collider, with surface informations that are impossible. For example, if I land on the ground, the collision points could be higher than the ground, way off at the right, and the angle of the collision would be in diagonal.

I cannot use the collision points, happily there is a function called ClosestPointOnBounds, that returns the bounds of a collision (extreme values forming a box around the collider, not the actual closest point in the collider itself). I need to know without a doubt the nature and angle of the surface I hit, how do I do it? I know that all my characters will have square physical colliders and won’t ever rotate, so I can use character.ClosestPointOnBounds(collider.center). Then cast a ray between that point and the center of the collider, with regular shapes it should always give the good surface you hit.

Here is the code needed to do so:

protectedVector3 GetNormalOfHit(Collider collider) {//get the angle of the collisionVector3 rayDestination = collider.bounds.center;//destination of ray (center of collider)Vector3 rayOrigin = col.ClosestPointOnBounds(rayDestination);//origin of ray (point on the character)Vector3 rayDiff = rayDestination - rayOrigin;//vector describing the difference between the 2 previous vectors
rayOrigin -= rayDiff.normalized * 5;//inset the origin according to the angle between origin and destination so if there is overlap, the ray will still hit.
rayDiff = rayDestination - rayOrigin;//rebuild the difference with new origin
ray.origin = rayOrigin;//set the origin of the ray (I‘m reusing rays this is why there is no instantiation.)
ray.direction = rayDiff.normalized;//set the direction of the rayRaycastHit hit;
if (rayDiff.magnitude > 0) {//make sure there are no errors due to exceptionnal occurences.
collider.Raycast(ray, out hit, rayDiff.magnitude + 2f);//cast the ray on the collider (only the collider)return hit.normal;//return the normal, it’ll determine we hit the top/sides or bottom of the object.
}
return new Vector3();
}

If you did not understand a word said in the previous paragraph, this picture should be of help.

Colliding opinions!

In Rogue Legacy, there are times when you get hit by projectiles that are under a platform when you land on it, because at some point in the code, your downward speed made you pass through the platform before beign placed back to the top of it.

Still in Rogue Legacy, there are relatively rare incidences of neighbooring collision issues, but all in all, it’s a game that kept me going for a long time despite the crudeness of it’s platforming!

In Conclusion

Whew! That was quite a chunk, hope I did not burn you up! In this post I presented you some ways to overcome some lacks in the collision engine. If you ever want some specific details like actual code, don’t be shy! Next time we’ll talk about the design decisions behind the original idea of Toto Temple, so stay tuned!

Join the discussion 9 Comments

Cool! I always struggle when doing this sort of thing to know if I should chalk bad behaviour up to me misusing the physics/collision engine or the limitations of the engine itself. It’s great to hear some thoughts and approaches about tight platforming controls/feedback from someone who’s been through the gauntlet a few times before.

Well I don’t consider myself a veteran in any way, but Thanks! Yeah, collisions are really tricky, of all the the technical aspects of a platformer it is the one I’ve spent most time on. Before getting to that result, I’ve tried several ways and even after writing this article (2 months ago) I rewrote the entire collision handling mechanic because I found a cleaner and leaner way to do things (the part with ClosestPointOnBound).

Hi! I’m trying to implement with with 2D raycasts, but I’ve been having trouble getting the proper normals. I was wondering if you had GetNormalOfHit() in a FixedUpdate() or only when there’s a collision detected between the player and the platform. Also, do you have multiple raycasts for multiple sides? For example, if you touch a corner (two sides) at the same time.

Thanks for this post by the way! I’m trying to learn Unity and the technique implemented here seems better than the drawing raycasts along the side of a GameObject.