Challenges with 2D angled graphics

In this course, we’re working on an isometric game, which comes with its set of challenges.

In 2D platformers or 100% top-down perspectives, layering is simple. You know full well what is in front of what and can position nodes in the scene tree accordingly.

Game: Darkwood.

But as the camera tips up or down to simulate the feeling of 3D, the story changes. The player can appear and disappear behind objects in the world depending on their position relative to the virtual camera.

Game: Z (1996).

In this project, we decided to use an isometric perspective to show you how to solve this view’s specific challenges.

Isometric graphics

The isometric view is a 2D graphical style where the camera is above the horizon, looking down at the scene diagonally, without perspective. Objects that are far away are the same size as objects close to the camera on screen, and the diagonal lines have a constant angle depending on the projection type.

The two most popular projections are isometric and dimetric.

In isometric graphics, the angle between the diagonal lines and the horizontal axis is about 30 degrees. This gives the effect of a camera that is at a 45-degree angle above the action.

Game: Super Mario RPG on Super Nintendo.

In dimetric, the angle is closer to 15 degrees and results in a camera that floats closer to the horizon, flattening the tops of everything.

Games: Head Over Heels, Landstalker, and Equinox. Source: Significant Bits.

The angle does not need to be exact: so long as you are consistent and use similar proportions, your graphics will look isometric.

To create the graphics for the demo, I did not use angles. Instead, I used a vertical to horizontal ratio of 1:2. For every pixel traveled vertically, a diagonal line travels two units horizontally.

The resulting angle is about 26.57 degrees. It’s not pure isometric, but it makes programming, animating, and positioning items in Godot a lot easier: move twice as much horizontally as you do vertically.

Layering and sorting

With this view, sorting and arranging your layers can be tricky.

When the player goes behind a machine, you want it to obscure them without having them pop through and break the illusion.

In Godot, we do this with the YSort node and the TileMap node’s Y Sort property.

The YSort functions look at the position of its children and updates their rendering order accordingly, regardless of their index or order in the scene tree.

Here are two examples of a setup using the YSort node where first, the player moves above the machine.

And here’s the view updated with the player moving right below it.

Notice that the YSort node works only with the nodes’ position. In the images above, the player’s position indicator is at their feet, and the furnace’s origin is in the center of its floor.

Being consistent about where the origin is helps YSort prevent situations like below where the player is in front of the machine but appears behind it, as illustrated below.

You want to be mindful of your nodes’ origin and consistent across scenes so they render in the correct order. In this project, we’ll design scenes so that each entity’s origin is centered at its bottom.

To maintain the illusion of solidity, we also make sure that the player cannot get too close to the tile where he risks his position going below the furnace’s.

We do that by lightly inflating the back of the furnace’s collision polygon beyond its square base. You can see the collision shape in red in the images above.

Nesting YSort nodes

We can layer YSort nodes. A YSort (A) inside another YSort (B) will sort A, then sort it as a full object with B, allowing small sorting hierarchies.

In the final demo, we use the following sorting hierarchy for the entities:

- YSort for flat entities like wires or rocks that the player always walks over
- YSort for all other entities
  - TileMap for the floor, with Y Sort set to true
    - All solid entities
  - Player

This kind of complex layering does require doing some compromises and tricks.

For example, we’ll have wires on the floor to connect machines, and the player will be able to walk over them. We also drew plugs for the wires inside the machines’ sprites. You can see below how the flat wire disappears under the machine without a plug, but the plug on the opposite side makes it look like they are connecting instead.

This is one of the cases where a specialized engine for isometric games could be useful as you could add an exception rule to sort the player that, at the moment, does not exist in an engine like Godot.

Splitting entities for animation

As noted above, sorted entities are also layered by being the child of a different Node2D; a StaticBody2D in our case. Godot sorts the graphics for the scene before putting it into the YSort node’s queue or sorting TileMap.

This allows you to create conventional sprite trees, though the isometric perspective does require some clever splitting to animate them.

Below is an example of a piston engine.

To draw it and animate it in-game, we use four sprites.

As we create this engine as part of a scene, even though it has four sprites, once we instantiate it as a child of a YSort node, it is treated as a single entity.

This covers some general aspects of working with the isometric perspective in Godot. In the next lesson, we’ll set up the project and write our first lines of code.