Table of Contents
- Coordinate system
Welcome to the fourth article of the Sonic Battle (GBA) Renderer series.
I’ll be using the OpenGL coordinate system:
- -Z: forward
- X: right
- Y: up
In this system the default camera view direction is straight down, towards the terrain:
The camera will usually be tilted for a lateral view:
Objects represent characters, projectiles, and visual effect particles.
The term is used to distinguish them from walls, since walls and objects are both GBA hardware-accelerated sprites (discussed in article 2) under the hood.
As seen so far, Sonic Battle can display a floor and a “ceiling”/”second floor” which are aligned to the XY plane.
It can also display walls which are aligned to either the ZX or the ZY plane.
These elements are often combined to display “box” structures:
Maps are full of these boxes:
There’s a map with inverted walls. It’s like looking at the inside of a box:
As will be discussed in a future article, this variety comes at no performance cost.
There’s also walls which are twisted 45 degrees around the Z axis:
Walls and objects culling
Walls and objects which are offscreen are removed from the display list to save resources.
Backface wall culling
Only “closed” volumes are being rendered, so walls which are facing away from the camera aren’t rendered because they would be invisible anyway:
The dotted outlines show the hidden walls
When characters jump, fly, fall, or otherwise move along the Z axis it can become hard to determine their position.
The game uses a round shadow under each characters’ feet to solve the issue.
The shadow’s position indicates the character’s position on the XY plane.
The shadow’s size hints at his position on the Z axis relative to the object under his feet (not necessarily the “floor”/terrain).
The hardware-accelerated rendering of the GBA allows to set the draw order of tilemaps and sprites, but it can’t be more granular.
In other words the renderer is limited to the painter’s algorithm, where the draw items are sorted by distance to camera and drawn in “far to close” order. This works well overall but has some edge cases which are discussed below.
The camera rotation is evaluated in the following order:
- Z axis rotation: Heading
- X axis rotation: Pitch
Note that Y axis rotation (Roll) is never used in Sonic Battle.
Also, the camera pitch is always between 0 and 90 degrees.
In the context of the game it makes no sense to break these rules. They also enable many assumptions and performance optimizations.
The projection is orthographic. I think that perspective projection would have been possible with some more work. But it wouldn’t have brought any gameplay value, and it would have hurt the clarity of the visuals.
Object-wall clipping issue
The renderer seems to depth-sort by horizontal middle, vertical bottom points. We can observe the side effects it causes, for example:
Indicated are the sorting reference points for the character, the facing wall, and the side wall:
The character’s sorting reference point is in front of the others, so he’s drawn last:
The character’s point is between the side wall’s and the facing wall’s. He’s drawn on top of the side wall but is clipped by the facing wall (in the area near his hand):
The character’s point is behind the two others. He’s clipped by both walls:
Wall-wall clipping issue
There’s a specific map with unusually positioned walls which don’t depth-sort correctly:
Illustrated wall bounds and sorting reference points
Object-ceiling clipping issue
The “ceiling” faces of the “boxes” are all part of a tilemap and can’t be depth-sorted individually.
"First floor" tilemap
"Ceiling"/"second floor" tilemap
Our draw order is roughly this:
- First floor tilemap
- Walls and objects
- Second floor tilemap
But what happens when objects move up the Z axis, and eventually reach the second floor?
The draw order becomes as follows to accommodate it:
- First floor tilemap
- Walls and objects which are on the first floor
- Second floor tilemap
- Objects which are on the second floor
A sorting issue happens when a object is in between the first and second floors:
Jumping up from first to second floor
Jumping down from second to first floor
It’s not obvious why the issue also appears when jumping down. It will be discussed in a later article, fear not.
Wall offset issue
The walls and tilemaps don’t exactly match positions. This creates cracks at the seams of the “closed” volumes they’re supposed to represent:
Cracks at the left seams of the boxes
I don’t know if it’s caused by fixed point precision issues or by an “off by one” bug.
Wall jitter issue
I think this is caused by fixed-point math precision issues.
The further the player is from the center of the map (the bigger the magnitude of the numbers involved), the worse the jitter.
This GIF was recorded at the edge of the one of the widest maps where it’s definitely worse than usual:
These rendering quirks might seem like a big deal, but they’re honestly not.
I’ve played through the game twice and I never noticed even one of them. I only discovered the issues after careful inspection once I noticed them in my own implementation.
Also note that all the images and gifs that I included were upscaled by a factor of x2, so everything is less distinguishable at native resolution, and even less on the console’s small LCD screen.
The next article is a high-level summary of my implementation.