Gamasutra: The Art & Business of Making Gamesspacer
View All     RSS
September 24, 2017
arrowPress Releases






If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 

Unity3D: How to use the collision pipeline to your advantage

by zion siton on 06/13/17 11:37:00 am

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

 

How to use the Unity3D execution order of events to your advantage


 

The Reason

Whilst working on Swing King and the Temple of Bling, a 2D grid-based action-puzzler for mobile devices, I came across what seemed like a complicated problem but using the Unity3D pipeline I was able to solve it in a reliable and elegant way.

 

The Problem

Okay, imagine you have a 2D grid-based game where the player runs around in straight lines (like in Swing King) and bumps into walls and you need to make some levels for the player to run around in.

You decide to make a "Block" prefab using a Sprite with a BoxCollider2D and attach a little "Block" script that bounces the player in the opposite direction when he bumps into it:

public class Block : MonoBehaviour
{
   void OnCollisionEnter2D(Collision2D coll)
   {
      if (coll.gameObject.tag == "Player")
      {
         Player player = coll.gameObject.GetComponent <PLayer> ();

         if (player) player.direction = Opposite(direction);
      }
   }
}

Now you can place several of these "Blocks" around the scene and it works perfectly.

That's not enough though, you want to speed things up a bit so you include a 2D tile-map editing tool from the Asset Store in your project. The tool enables you to draw tiles that are automatically aligned to the grid directly in your scene and you can even work on several layers at a time. Great! Now making the levels are a breeze and you can entrust the task of designing levels to the level-designer while you stuff your face with a tea-soaked biscuits.

Eventually the level-designer says "Hey! We need a Spikey-Block that kills the player when he bumps into it so I can make more interesting levels". That's not a problem for you, so you knock up a tiny "SpikeyBlock" script that simply kills the player when he bumps into it. Just switch the Sprite out for something a little spikier and it's back to filling your face. This process carries and before long you have "StickyBlocks", "ButtonBlocks" and so on.

Things are really going well until the unthinkable happens... the level designer needs a block that is spikey on one side and sticky on another. In other words, blocks are supposed to have edges now! Edges!! And the level-designer wants to be able to add a multitude of edges in different ways. So what do you do?

At first you consider adding extra variables and collision logic to the Block class but soon realise this approach will make the Block class unecessarily complicated and difficult to extend. It will also make things considerably more difficult for the level-designer who will now have to go round setting values in the Inspector window. Not only is this a slow process, but it also renders the lovely little tile-map editor tool pointless.

Then you come up with another idea to make an "Edge" class that encapsulates the different behaviours and can be added to a Block. Better. This solves the problem of keeping the code clean, but it doesn't make things any easier for the level designer who still needs to add Edge components to the Blocks in the inspector window.

 

The Solution

This brings us to the crux of the article, the solution that takes advantage of the Unity3D pipeline.

Firstly, you break the idea of an Edge out into a separate object that can be placed anywhere on the tilemap using different tile-map layers. With a little careful prefab construction, Edges can be placed on the tile next to a block and look just like a part of it. This solves the workflow issue for the level-designer.

Secondly we construct the Edges in a slightly differently way to the Blocks by marking their colliders as trigger areas in the inspector. This gives us the opportunity to handle the collision with the edges before the collision with the blocks because, wait for it, trigger colisions are handled before solid collisions in the pipeline, just check Unity's "execution order of events page" website if you don't believe me. So we just move the code from the OnCollision function to the OnTrigger function.

But wait a minute, what if the player is brushing past one of the edges and touches one of the trigger areas? We only want to handle the collision if the player is bumping into the edges, not heading past it. And what if the player is in a corner with two different edges? Which edge should we hit? Now we have to do a bunch of calculations to check that we are actually hitting the edges. This can't be right, Unity is a game engine! We shouldn't need to do these checks, we just want to know when a collision has taken place and do something cool at that point. Well we can still take further advantage of the pipeline...

Let's say the player has an array of Edges; everytime the player enters an Edge's trigger area it gets added to the array, when the player leaves the area it gets removed. That's it, that's all we do, we don't need to do any checks to find out when we've actually hit the Block's edge. Instead we let Unity tell us when we hit the block because we will receive an OnCollision event due to the Block's solid collider. All we then do is ask the player to check any edges in their array (max 3 edges) and if any edges are facing the opposite direction (I used an enum for this) the player collides with that Edge, otherwise we handle a collision with the Block. Remember, OnTrigger is handled before OnCollision, so we are assured that the player will already have the Edge in their array when they hit the block.

The player's collision check is then trivial:

// in the Player script
OnHitBlock(Block block)
{
   // check the edges
   foreach (Edge edge in edges)
   {
      if (edge.direction == Opposite(player.direction))
      {
         Hit(edge); // handle edge collision

         return;
      }
   }

   Hit(block); // otherwise handle the block collision
}

 

 

Conclusion

So there you go, it may have taken a while to get to the point but I got there in the end and hopefully you've seen how using the order of executuion in the pipeline can help solve complicated problems relatively easily.

By the way, if you are interested in playing the game used in this example, it will be available on iOS devices throught the AppStore on the 29th June 2017 and is called Swing King and the Temple of Bling. It was a collaboration between Objective-Z (that's me) and Shed-Works (Greg & Dan) and we're really excited about the impending launch.

In the meantime, Shed-Works are working on another great looking and Objective-Z is working on a new collaboration (top secret at the moment but watch this space).

Anyway, I tried to keep this article pretty light (even though the subject matter is some what technical) and hope that it can help in some way. Feel free to post any questions or comments and I will respond as best I can.

 

Nuff said!


Related Jobs

Naughty Dog
Naughty Dog — Santa Monica, California, United States
[09.22.17]

Graphics Programmer (Game Team)
Insomniac Games
Insomniac Games — Burbank, California, United States
[09.22.17]

Mid to Sr Gameplay Programmer
Insomniac Games
Insomniac Games — Durham, North Carolina, United States
[09.22.17]

Mid to Sr Gameplay Programmer
Supergiant Games
Supergiant Games — San Francisco, California, United States
[09.22.17]

Platforms Engineer at Supergiant Games





Loading Comments

loader image