Sonic Battle (GBA) Renderer Series - 45-degree walls

Posted on September 22, 2019

Table of Contents

Welcome to the ninth article of the Sonic Battle (GBA) Renderer series.


There’s a map called Tail’s Lab that contains octogonal boxes:

Pre-computed tangents

Like the inverted walls situation this appears complicated at first glance.

But really all that’s new is that four new wall affine matrices need to be computed for the new diagonal wall directions.

To compute the matrices the new four directions’ tangent vectors are required:

The tangents can be pre-computed:

  • Top left: (-1, -1)
  • Bottom left: (1, -1)
  • Bottom right: (1, 1)
  • Top right: (-1, 1)

But that’s not the final form, they need to be normalized. The formula is to divide each component by the vector’s length so (1, 1) for example becomes (1/sqrt(2), 1/sqrt(2)).

The value of 1/sqrt(2) is stored in a constant used to initialize the diagonal tangent vectors at compile-time.


To compute diagonal wall visibility the regular wall method can be re-used, only the camera heading is twisted by -45 degrees prior. It has the effect of twisting the scene +45 degrees.

Loop unrolling method

There was a messier but more efficient method for wall rendering described in an earlier article.

We can adapt it for diagonal walls too. We only need to twist our basis (world axes) +45 degrees by maintaining a separate set of camera matrices rotated -45 degrees, exactly like we just did for camera heading and diagonal wall visibility.

We can then re-use the exact same code for visibility and affine matrix computation of axis-aligned walls for diagonal walls (provided we maintain a separate rotated set of camera matrices).

In fact I think the loop unrolling method is less messy (and much more efficient) than the other one if we consider how cleanly it handles diagonal walls 😉

Pixel grid alignment

Walls have virtually no thickness and they can only be placed on edges of the X-Y pixel grid.

Here’s the bottom half of a hex box visualized from above with intuitive wall placement:

  • Black: Top tilemap pixels
  • White: Walls

But this placement leads to gaps between the roof tilemap and the diagonal walls (the floor tilemap bleeds through):

Here’s another wall placement that doesn’t show any gaps:

But then the diagonal walls are a bit inset relative to the regular walls which causes some overdraw. The walls are sorted by midpoint camera-space depth so walls overlap a bit depending on the camera orientation. It’s an acceptable side-effect and it isn’t noticeable unless you look for it.

The diagonal walls are placed and have their width scaled with all this in mind.

So far we’ve mostly been talking about walls and tiles. The next article discusses the rendering of sprites (characters and objects like projectiles for example).