Onions have layers...

Games using top-down oblique projection have layers.

The game

Right, since this is my first post relating to this game project I thought y’all might want a quick introduction to what the project actually is.

My team, Hastur, are currently working on a Shoot ‘Em’ Up based on a concept created by another group of students. The concept goes by the name of “You May Kiss The Bride”. Its set in a nightmare that Brad, the protagonist, has on the night before his wedding. In this nightmare Brad dreams about the wedding, but before saying “I do” he gets cold feet. When this happens the church, along with the bride and all the guests, transform into a hellish scene. Brad must now run down the isle to escape the monstrous and unkillable bride, while using a gun he found to deal with the guests that are in his way.

The game uses a top-down oblique projection view (thanks google :P) similar to early Pokémon or Zelda games. More commonly this is often, incorrectly, referred to as an isometric view. In the image (from RPG Playground) at the top of this post you can see a proper comparison between some popular graphical projection types.

The problem

One common issue when oblique projection (OP) is used in games is that people tend to forget that they are still trying to represent a three dimensional world. The player is not able to do things like moving “behind” a building, they are instead forced to move around the whole sprite. While I get that this might have been the way that it was needed to be done back in the days, due to technical limitations, I don’t think it makes a lot of sense. It makes it feel like everything is just lying on its side and sliding across the ground.

To solve this issue one needs to use sprite layering. Which is something that Unity comes with by default. Great! so everything is good then. All I need to do is put Brad on the layer behind everything that he should be able to move behind, right?

Wrong.

If one was to do this then Brad’s body would start disappearing behind any object placed on a layer in front of him as he approached it from below.
As seen in this gif that is not exactly the effect we are going for.

Great for clouds, not so good for benches and buildings :P

The solution

(Quick side note: I’m not sure that this is the best solution. Its pretty much just a dirty workaround due to the way that Unity handles sprite layering. This is simply a solution)

As this is a problem that applies to not only Brad, but all objects and NPCs that might interact what we need is a layering system that moves sprites between “layers” as they move up and down in the scene. By doing some quick searching on/ testing of how Unity handles which sprite is drawn on top we find out that this is based on three factors:

So, all we need to do now that we know how sprite layering works is to go through the system and see how we can relate it to the Y positions of objects in our scene.
Sorting layers are, similarly to Photoshop, manually created and named. And while one perhaps could create a sorting layer for every possible position on the Y axis this would we very cumbersome and probably not great for performance. Using these layers for things like the background, which we know is always “behind” everything, is good. But for now its off the table.

Order in layer (OIL) is determined by a integer value. Lower numbers are rendered first and subsequent numbers overlay those below. This we can work with.
We’ll start with tying the OIL to the inverted Y position (as we want the order number to get lower as we move up) of our object. To this we add an offset to make sure layering is based on where the object actually “touches” the ground and not just the center of our object.

public float _offset = 0;
private SpriteRenderer _sprite;

void Start () {
_sprite = GetComponentInChildren<SpriteRenderer>();
}
void Update () {
Vector3 target = transform.position;
Vector3 groundPos = new Vector3(target.x, target.y + _offset, target.z);
_sprite.sortingOrder = (int) groundPos.y * -1;
}
Now we’re getting somewhere. But, as you might have noticed if you tried implementing this code into your own project, it only kinda works. Sometimes it does and sometimes it doesn’t. This is because, as previously mentioned, OIL is an integer. Which means that we don’t get the float value precision that we need for this to work. So, in the words of Dom Cobb, “We Need To Go Deeper”.

The only thing left in our arsenal is the distance to the camera (DTC). Therefore, we might as well use it. Seeing as our game uses an orthographic camera, moving objects closer or further away doesn’t change their size. In theory this means that, we could just set our object’s Z position to always equal the Y position and hope that the object never moves towards the camera enough to end up behind it.
However, ignoring the fact that this is simply just bad code design, this would also mean that the object might move away from any potential light sources. As we knew that we’d want to take advantage of Unity’s lighting system this was a no-go.

What I ended up doing was taking the ground Y position value (a float) and adding the sorting order value (which was previously set to be the negated floor value of the ground Y position value). This effectively left me with just the decimal point value of the ground Y position value. For good measure I multiplied this by the arbitrary value of “0.0001” before applying it to the Z position of my object. This is to insure that the object moves as little as possible on the Z axis.

void Update () {
Vector3 target = transform.position;
Vector3 groundPos = new Vector3(target.x, target.y + _offset, target.z);
_sprite.sortingOrder = (int) groundPos.y * -1;
transform.position = new Vector3(target.x, target.y, (( groundPos.y + _sprite.sortingOrder) * 0.0001f));
}
And voilà!
There you have it. It might not be pretty, but it works.
I have of course added more functionality to this system to handle things like Brad’s gun and projectiles as well as making a version of the script that only runs the logic in the Start() function. This static version is what is used on things that will never move (i.e. benches and buildings). But these are all things that I’m pretty sure you can figure out on your own :)