Nirion»Blog
Ben
The last month or so of Nirion's development has been mainly focused on fixing obvious bugs and improving core functionality. I'm currently still trying to get the first area of the game at a level I'm happy with for a demo, but hopefully most of this work will go towards improving the entire game going forward.

Spatial Partitioning

One of the first things I did this month was a major change to the game's spatial partitioning. This code is used for broad phase collision detection, culling entities for rendering, querying tile data, and organizing the world into loadable "chunks". The approach I was using before this change was a simple sparse grid of fixed size. Entries could be added and removed by hashing their cell location(simply by dividing their location by the cell size and rounding down), and then looking up their cell from a hash table. This worked great in a lot of ways, since adding and removing was very fast. Unfortunately, using this approach, the code performing a query has to account for the largest entity in the game, since an entity is only added to a cell based on it's position, not it's size. An alternative is that each entity must be added to every cell that they touch, requiring extra logic during queries to ensure a set of unique entities are returned.

Ultimately I decided to change my approach. I had avoided doing this for a long time, and instead preferred to just tweak numbers to account for larger and larger entities. Eventually I decided I wanted a solution that works well for any entity size primarily, and quickly found BVHs:

https://en.wikipedia.org/wiki/Bounding_volume_hierarchy

I replaced my fixed cell size grid solution with a BVH and this solved all functional problems I was having with entity size. I had a bit of trouble implementing this at first, I haven't worked too much with tree data structures directly before. I didn't really get the idea of balancing the tree or the importance of deciding where to insert a leaf. With enough reading on the subject, and messing around with my implementation, I got it working at a reasonable speed. At some point in the future I intend to go back and work on making it better. Inserting is taking much longer than I'd like at the moment, even with a reasonable amount of objects.

Merging Tilemap Collisions

With either of these methods, I wanted to greatly reduce the amount of entities that were in the game. By far(probably up to 90%) the most spawned type of entity was tilemap collisions. The reason for this was their small size. Basically any tile placed on a tilemap can have a small 8px*8px(1m*1m) collision tile, usually a square or a triangle. Altogether this amounted to about 20,000 entities across the entire map, and the game's world isn't even close to being completed yet. My plan was to compose these into minimal convex pieces since I'm only using GJK for collision detection with them anyway, and I'm not relying on their current shape in any way.

First I grouped tiles based on whether they had connecting edges or not. This reduces the problem into only groups of tiles that are touching. Any tiles that aren't connected will never end up being part of the same convex shape.


Collision after composing tiles into concave pieces

This basically leaves us with some amount of separate groups of tile, or just a giant group of points. I wanted to create one giant concave pieces and reduce it from there, but I struggled to do this at first. I started by implementing this:

https://www.researchgate.net/publ...egion_occupied_by_a_set_of_points

I found the results to not be precise enough. With k too low, it didn't include all points, and with k too high the shape became too smooth and not precise enough. I guess this isn't surprising since it's a general algorithm for any group of points in space, and by using it, I was basically just throwing away all the information I had about how these tiles connected together.

I gave up on the concave hull solution and managed to come up with something that worked on my own. The idea was to loop over all tiles and record which edges are shared. If an edge is shared by 2 or more tiles, it gets removed. This leaves only the points on the outside of the shape.


Shared edges are in red. Removing shared edges leaves us with just the points on the outside of the shape

Now that we've removed points inside the shape, we need to connect the points together in some way. To do this I used the fact that there are still duplicate points in the data where two edges connect. Since we've removed all inside edges, we can work out how edges should connect. If we have edge A, and edge B, and A's end point overlaps B's end point, we know that A is connected to B in that order. To connect the entire shape, we start with one of our edges from the last step and look for a connection based on what other edge overlaps it's end point. Once we've found that edge, we then find the edge which overlaps it's end point etc. Once we're back to the edge that we started on, we're done and the entire concave shape is connected, with colinear points. I removed colinear points by checking if the point is on the line created between it's previous and next points. This leaves us with a concave shape.

From here I tried a few things to create the convex pieces, but eventually just settled on ear clipping triangulation to reduce the shape to a set of triangles. Right now I still need to merge triangles into larger convex pieces, but overall this process works pretty well.


The final collision triangles

This has greatly reduced the amount of collision entities in the game. It took a long time to implement this entire process. The results are rather slow, but only needs to be done when saving a tilemap(takes about 2-3 seconds to save every tilemap in the game right now), and I'm perfectly happy with this. There is still tones of room for improvement with this implementation. One edge case worth noting is when tiles create a "ring". The process for connecting edges for the concave shape won't work if the shape has "holes" in it. This is avoidable by detecting that the tiles create a ring and treating at least one of them as a different group.

OpenGL optimisation and bug fixing

I spent a pretty large amount of time fixing bugs in and optimsing my OpenGL code. There were a few instances were I was relying on undefined behavior that worked on my Nvidia GPU, but not my Intel GPU. Also, the game was very very slow on my laptop due to rendering.

As I mention before in another update, lighting in Nirion was done while rendering objects. A uniform buffer was used to upload all light data, and then in an object's fragment shader it would loop over all lights and determine the final light colour. Since objects are sorted from back to front in Nirion, this means a pixel might be lit by several lights, several times and not even end up having any lighting effect on the final pixel. I decided to change lighting to be one post processing step at the end of the "layer", just blending with the final screen pixel. This has a few issues when mixed with alpha blending sprites, mainly due to normal mapping. After testing out a few things, I decided that normal maps weren't making a significant enough difference to the visual quality of the game and removed them entirely. Making this change, along with batching more things in general, rendering is now much, much faster on my laptop. I still have a long way to go with performance, but for now it's good enough.

I also performed some preprocessing on tilemaps to remove hidden and unnecessary tiles. I did this in the most naive way possible, looped over each cell and found the tile with the highest layer that did not have any transparent pixels or did not meet any special requirements(needed for collision or other metadata).




As you can see, unnecessary tiles below the grass and rocks have been removed. This would normally be too time consuming to remove by hand. This improved performance a bit, and reduced the amount of tiles on some tilemaps by about 25%.

Tilemap height generation

In Nirion the player can jump down walls and climb up stairs:


The sense of height is just an illusion created by the art, and the engine doesn't really understand that the player just jumped down a level. He just kept falling until he hit a tile where there wasn't a wall. This works pretty well besides a couple of scenarios. Bridges and firing over gaps.

My solution to this before was to have projectiles record whether or not they've crossed over a ledge(just the same collision entity that tells the player that they can jump off) and if they have, don't interact with anything until you cross back over a ledge pointing the other way. This was pretty messy and didn't work a lot of the time. What if the projectile passes over more than two ledges of the same direction in a row? What if you shoot a ledge at a 90 degree angle?

Bridges were handled by painting whether or not a tile was a bridge into tilemaps, and changing a flag on an entity depending on which type they were on. This had a lot of problems around where tiles changed from "bridge" to "not bridge". Overall it was not a good solution, and it was pretty time consuming to make bridges.

My solution to both of these issues was to precompute tile "heights" at build time and use this to determine which height an entity should be on.


Computed height visualisation

An entity can now just check which tile they're on, and determine from that which height they should be at. If they do this every frame however, bridges won't work since they will just select the height that is below the bridge(since a tile query only works in 2D and will ignore bridges). But, if an entity performs this check only when certain conditions are met, bridges can be supported as well.

The player will reevaluate his height only when doing any of these things:
- Jumping
- Falling down a "layer"
- Going up stairs

This means that the height the entity was at when entering a bridge will be maintained until one of these things happen. This is exactly what we want for a bridge, if we were above it, stay above it, and if we were below it, stay below it. Unfortunately, if an entity jumps down onto a bridge, they'll won't get the correct height. Right now I'm just trying to avoid this situation and it hasn't been a problem yet.

To solve the firing a projectile over a gap problem, I just ignore any interactions between entities that have a different height. Overall this approach works really well and allows me to build pretty complex environments using various heights.





Game and level design

For the past week or so I've been focusing on some game and level design. I've added some variety to combat in order to make it more interesting. Before, the player only have one attack per weapon, but now each weapon has a more power alternate attack that can be used on a cooldown:


Drill alt attack


Cannon alt attack

Hopefully these abilities will help make combat a little more interesting. The game isn't solely focused on combat though, so I don't think it needs to get too complex.

I've recently been trying to come up with more interesting level designs and puzzles. The Mines area mainly focuses on pushing around mine carts to solve puzzles. I've been playing around with what will hopefully be some interesting things to do with this idea.



That's all

That's most of what I've been doing for the past month or so. Right now I'm planning on doing a lot of level building to complete the first area of the game. Building these areas and making them look nice takes me hours, so I'm a bit worried about how much the entire game will add up to. I am hoping to get a demo released within the next couple of months still.

Let me know if you have any questions or just want me to go more in depth on anything. Thanks!

Also, since I haven't posted it in a blog post yet, here's the gameplay video from about a month ago:
Ben
Over the last few weeks I've been working on a variety of different aspects of Nirion. Probably the one that I spent the most time on recently was music and sound. So far I had been neglecting sound and music to an extent, having only gotten basic sine waves playing way back when I first started the project. It's a relief to have made some progress in this area, and I think I have a lot more confidence going forward. I've also updated the art, added a 3D effect for upgrades, and finished off a first pass of the first major boss. Here's an overview of some things I've done since the last update.

3D Upgrades

A while ago I decided that I wanted a few select things in the game to have a 3D effect, influenced mostly by Sonic 3 and Sonic Mania, where the player can come across giant 3D rings that transports them to special stages. To me this makes the things that are 3D feel more special, and they stick out more.

I first tried exporting and loading PLY files from blender. This worked fine, except I don't have any experience whatsoever in 3D modeling. I don't think learning enough blender to make the simple models I needed would have taken that long, but I'd prefer to not have to spend the time to learn it right now.

What I settled on was generating a 3D mesh from pixel art. The process is basically to just draw the 2D sprite in Aseprite, create the 3d model asset:
1
2
3
4
5
Model
{
	bitmap = Common.png;
	source = (32, 240, 16, 16);
}


And then tell the game to render that 3D model. During asset preprocessing, the code will load the given region of the bitmap, loop over every pixel, and for each non-zero alpha pixel, it will create a cube. The cube's colour is the pixel's original colour, and the cubes depth is some constant multiplied by the pixel's alpha. I'm basically just extruding the pixel art, but manipulating the alpha to change the depth makes the model look more interesting.

The result:


The source art:


I think this works pretty well and suits my needs. Using alpha to control depth is a bit difficult, it's mostly just guessing, and using alpha this way makes it a bit harder to visual in Aseprite.


First Major Boss

The "first pass" of the first major boss in the game is done. There's a few balance/visual issues I'll want to fix eventually, but the basic functionality is there.



The boss will cycle through different attacks which require the player to dodge and block/reflect. Successfully blocking certain things will stun the boss, allowing the player to do much more damage for a short amount of time. I'm aiming to design bosses similar to the more difficult boss fights found in the Kingdom Hearts series. The focus is mainly on learning the enemy's patterns and blocking/dodging correctly to get openings.

The boss is animated entirely in code allowing me to dynamically position his different limbs based on stuff like IK and the player's current location. I think this does make some animations more cumbersome to create, but overall I think it's the right direction for this game.



Updated Art

Over the course of about a week, I updated a lot of the environment art. I redrew tiles, objects and added some more interesting designs and variety to the map.





I think it's looking a lot better overall, and feels more like an actual mine now! I'm still in the process of updating some tilemaps. Another thing I finally got around to fixing was the scaling of tilemaps. Before, tilemaps were just using nearest neighbor filtering, which was fine for layers that the player was currently on. However, in some rooms, layers below the player use a parallax effect and are scaled down to make them seem far away. This caused shimmering and cracks in the tiles depending on the camera's sub pixel location.

I solved this using the method described here by Ryan Fleury. I've always used this for rendering the player and other objects, but struggled to get it to work with tiles. Ryan explained that you can get this to work on tiles by first compositing the tiles onto a separate texture at an integral offset, and then using that texture to render the tilemap using the algorithm.

So every frame for each visible tilemap, I first render it to a texture(tilemaps are 512*512 always) and then draw that texture in the world. This works great.



Unfortunately, I'm using entities(not part of the tilemap) placed on top of tilemaps for stuff like doors. This produces visible seams that I've yet to work out. Besides this though, I've very happy with the results. The camera also occasionally zooms out for boss battles, so this helps with that as well.

Music and Sound

As I mentioned before, I spent a lot of time on music and sound. I've never done any sort of music or sound design ever, I barely even knew what a note meant in music. So I found it pretty difficult to jump in, but over the course of a few days, I got the hang of the basics. I used Reaper, and found some free instruments and synths. I used these to make a couple of basic background tracks for the mines and was reasonably content with my third attempt.

I think I found sound design even harder. With music, I actually have an interest in composing and I would love to be competent at it one day, but not so much with sound design. I've ended up settling on using a mixture of synthesizing and buying sounds packs with some editing. I've made a few sounds now, and I'm reasonably happy with them.

I spent a day or two implementing sound playback in the engine. I've added basic stuff like being able to play a sound, loop sounds, stop sounds, change pitch and volume. One thing I haven't done yet is streaming long tracks. The background music I made is already a 40mb wav file, which is larger than the rest of the game combined so far, so I think streaming and compression might be necessary.

Save Points

I also added save points to the game. The player can enter them, an animation will play, and then the player will be healed and progress will be saved. If the player dies, they return to the last save point having lost all of their progress since last saving. I'm not sure if I'll keep this "punishment" for dying, since it might just be annoying to lose progress, but keeping the player's progress requires some extra consideration of where it's okay for the player to die and how they can return to where they were without issues.

The player "evaporating" in the save point. He will be reconstructed by the save point after saving


Lighting

In the comments, Oliver Marsh was interested in the lighting, so I'll explain it a bit here. Basically it's doing the most straightforward thing possible. During the game update "PushRenderLight" can be called. This will add a light to a list of lights to be used for rendering that frame. A light consists of a position, a radius, an intensity value and some other location related data like roomId and layer. At the beginning of rendering, a uniform buffer is created and all visible lights are stored in this buffer.

When a tile or sprite is rendered, during the fragment shader, it loops over all lights in this buffer and calculates it's contribution to that pixel. All of the light contributions are added together. The lighting calculation I'm using is the one described in this answer

1
att = clamp(1.0 - dist*dist/(radius*radius), 0.0, 1.0); att *= att


I'm mostly using this as a placeholder, and am not overly happy with the results. Finding a nicer attenuation calculation is definitely on my to do list.

Overall the lighting is dead simple. At one point I did implement deferred shading with depth peels, but Nirion uses transparency quite a bit in the art and layer effects, so limiting the amount of alpha blends was a bit tricky. This seems fast enough for now during normal gameplay, so I haven't found a need to replace it yet.



That's All

I think that's most of the major stuff I've been working on. Upon adding save points, the game felt a bit more complete. You can save, die, and respawn at the last save point without issues. Right now I'm working on updating tilemaps and polishing the mines area. I'm hoping to have a reasonably complete demo of the mines and the first minor and major boss that I can show off to people. I'll also put a gameplay video together sometime in the next couple of days so people can see the game in action!
Ben
Nirion is a 2D action adventure game drawing heavy inspiration from mostly The Legend of Zelda: A Link To The Past and Super Metroid. I've always wanted to make my own games similar to these and in 2017 I started working on this game from scratch in C++, inspired by Handmade Hero. Originally the game was going to be a pretty complete "shoot em up" game modeled after relatively simple games like "1942", however I quickly switched to what Nirion is now, a top down action adventure game focusing on more complex combat and exploration. I figured that this would be much more interesting to work on and would keep my motivation up.

Of course, this greatly increased the volume of work. I'm not a designer or artist, I've only ever done gameplay programming professionally and a small amount of engine level programming while porting a game from a custom engine to UE4. I'm definitely still learning a lot of these skills as I go on, but I'm relatively happy with how the art and design have been going so far. A huge reason for doing this project from scratch is to gain more engine level experience since I'm much more interested in more complex engine work than gameplay programming.

In these blog posts I aim to detail different aspects of Nirion's development. I think these will be mostly technical and will generally just describe how I've chosen to do things. If you start to have any questions or want to see a particular topic covered, I'd be happy to go over that. This blog post will just be a light introduction to different aspects of the game so you can get familiar with what the game is and how it's been built so far.

Story and Setting

I won't go into the story in too much detail right now, but I'm aiming for a simple story that mostly doesn't get in the way of the gameplay. The basic premise is that the player takes control of a "house robot". These robots are basically just simple robot that are designed to do chores around the house, but are also built to be extended with different(usually just house utilities) upgrades. A family will own one of these robots like a pet in this world and are generally treated as part of the family. During some events at the start of the game, the robot and his family wind up stranded separately on a mountain named "Nirion", and this is where the game takes place. At this point the story takes a back seat and more details will be given through optional cutscenes until the player gets closer to the ending.


Concept art of the Melee Wasp enemy by my partner. I convert this into pixel art.

The Nirion mountain is broken up into several areas which the player explores. There are many (mostly bug inspired) creatures who inhabit each area of the mountain, as well as environmental dangers such as lava and plasma. Although most of the world is organic, there are hints of technology around, usually deeper inside the mountain. The player begins in the "Mountain Side" area and is quickly directed into the Mines.


Concept art of the Spike Crawler enemy by my partner. I convert this into pixel art.

General Gameplay

As mentioned above, the gameplay is heavily inspired by Super Metroid and A Link to the Past. The game world is one huge interconnected area with zero screen transitions. The player should generally only be blocked by required upgrades and not by story progression or arbitrary boundaries. This kind of world design is mostly inspired by Super Metroid, but the perspective is obviously closer to A Link to The Past.

Moving and combat are done with a "twin stick shooter" approach, where the player controls the robot's movements with the left stick and aims weapons/items with the right stick. The shoulder buttons are used to activate items and other abilities. Throughout the game, the player will pick up upgrades which will change different aspects of the controls and also add to combat and exploration options. Controlling the player character in exploration and combat should feel fundamentally different at the start of the game versus the end of the game.

Combat is focused on three different player abilities which are obtained fairly close to the start of the game. These are:
- Attacking. This can be melee and ranged.
- Dodging. This allows the player to quickly and safely move out of the way of enemy attacks. It's easier to do, but is generally less rewarding.
- Blocking. This allows the player to block melee attacks, potentially stunning the enemy, or reflect projectiles back at the enemy. It's riskier, but is more rewarding when executed correctly.


The player dodging an attack


The player blocking an attack. This weak enemy is destroyed immediately as a result.

These three concepts are the focus of combat. Most normal enemies found throughout the game are more like obstacles than difficult combat challenges, but boss fights will use this extensively. Some player combat attributes like max health and attack/defense are increased by finding optional upgrades. The more the player explores and finds secrets, the easier combat will get.


The player attacking an enemy

Dependencies

The game is written from "scratch" using C++. The game only supports windows right now and uses only OpenGL to render. I've tried to separate platform and rendering APIs in a reasonable way, very similar to how it's done on Handmade Hero. The game includes an asset preprocessor which is used to process all the files in the "data" directory and output them as a more optimised format in a single file. In this process, I currently rely on stb_TrueType.h to render bitmap fonts, but I do eventually plan on doing this myself one day. The game also relies on a few CRT functions such as sin, memcpy and qsort.

High Level Structures and Editor

Most of the gameplay code is related to the Entity data structure. All things in the world are an entity in some way. The player is an entity, all the items the player can use is an entity, and tilemaps are an entity(although each individual tile isn't). Using code generation, the entity structure can be extended and new behaviors can be added.

A typical entity definition in code would look like the following:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
ENTITY_DATA
(
 struct animatorEntity
 {
     u32 animation;
     i32 loopCount;
     
     EntityId attach;
 };
 );

ENTITY_DEFAULT_FUNCTION(EntityDefault_Animator)
{
    return CreateEntityDefault(entity, true);
}

DEFINE_ENTITY(hash="Animator", type=ET_Animator, func_Default=EntityDefault_Animator, func_Constructor=EntityConstructor_Animator, 
              func_Update=EntityUpdate_Animator);


This piece of code defines extra data the entity needs, how it should be initialized by default, and some information about the entity such as it's type and what functions to call for common behaviors. Default functions are called only once at start up to create a default version of each type of entity. Any entity instance loaded from map assets is saved as a per property difference from this default entity. What I mean by that exactly is that when an entity is saved to the map asset in the editor, only it's properties that are different from this default are saved. When an entity is spawned, you can pass in a data structure containing all changed properties to spawn a specific instance.

Entity functions are also defined here, allowing you to implement different logic for different entities. The code generator will look at this and generate a switch statement for each type of entity function, switching on the entity type and calling the given function. Personally I feel like separating entity code based on types is usually easier for me to work with. I found having large, manual switch statements for this was very difficult to work with as the code grew larger.

Entities also define zero or more "EntityVisual" and "Geometry" instances. EntityVisuals just allow entites to define a visual component. This can be a static bitmap, an animation, a particle system, or even an entire tilemap. Geometry instances are components for use by the physics engine. An entity can be made up of multiple of these, but the physics engine will just treat them all as one rigid body. Geometry is ultimately used by the GJK algorithm, so they can be arbitrary polygons and are defined as an asset in a text file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
ENTITY_DEFAULT_FUNCTION(EntityDefault_UpgradePickup)
{
    EntityVisual* container = PushEntityVisual(system, entity, BID_Common, BID_NormalMapFlat, Rect2(V2(0.0f), V2(16.0f) * ReferenceMetersPerPixel), Rect2(0.0f, 240.0f, 16.0f, 16.0f), 0.0f, RenderSort_Base);
    container->params.bitmap.isSubPixel = true;
    
    entity->physicsEntity.isInUse = true;
    entity->physicsEntity.type = PT_Entity;
    PushPhysicsGeometry(&entity->physicsEntity, GID_Square, CreateTransform({}, 0.0f, V2(0.8f)), &pickupResponseTable);
    
    ENTITY_EDITOR_NAME("Upgrade Pickup");
    return CreateEntityDefault(entity, true);
}


The upgrade pickup entity defined in the code above. It can be picked up by the player thanks to it's physics geometry.

Entities can only be defined in code. Considering I'm the only one working on this project, I just don't think defining entities anywhere else is that useful. In general the editor should not be used for making large or complex differences in entities from their default, since there is no support for saving an entity type and copying it etc. Any significant change to an entity should probably be a new type defined in the code.

The built in editor focuses mostly on entities. Using the editor you can place entities and change certain properties about them. This is mostly for visually placing the entities and changing some simple parameters. WorldArea entities are place here too, which correspond to the world's loading regions.


The entity editor. The bottom left is the entity palette for spawning new entity instances. The top right is the properties panel. The top left panel is just some debug view/buttons.

The editor includes a built in tilemap editor. This is probably the most important part of the editor for building the world. Using this, you can place tiles, animated tiles, tile properties(such as room ID, jump/fall properties and other stuff), and auto tiles. The tilemap system makes use of multiple layers so different tiles can be composited together. Once the tilemap is complete, it can be saved and displayed as an EntityVisual.


The tilemap editor. The bottom left is the palette panel where you can select tile regions to draw. Above is the layer panel for manipulating and selecting layers. To the top right is the property panel for defining which bitmap assets the tilemap will use.

When I want to test the game, I press start a test game instance(play button, F5, or F6 to start at the mouse position) and a new instance of the game will be started. This is completely separate from the editor game data and any changes here will not be reflected in the editor.

Some Other Technical Details

The code makes heavy use of code generation. Structs and enums can be annotated using macros to define meta data. For saving editor data, the game uses a general serialisation method using this system. The editor also uses this for displaying lists of enums, and drawing the property panel.

The physics system uses GJK for overlap tests and the method described in this paper for continuous blocking collisions.


Different layers

The game uses different layers for drawing separate "levels" in the game. This scales down lower levels and makes it dimmer. The player can fall down in real time, but this still only uses 2D collision detection. In terms of collision, objects on different layers just ignore each other.

Conclusion

Hopefully this gives you some idea of what the game is like and what it's like developing it. A lot of the tools/code is pretty janky at the moment. For example, the code generation tool does literally the bare minimum to output what it needs to and often breaks for random reasons. There are some big problems like the entity data structure not doing any compression at all. Right now entities are around 5kb each at all times, which is way too much.

At the moment I'm developing the first major boss in the game using just procedural animation(just animating rigid sprites). Most of the first area is complete and a few enemies have been designed and implemented. As I said above, I'm completely new to art and design, so it probably takes much longer than usual.

Thanks for reading! If you want me to go into more depth about anything mention in this post, or about the game in general, let me know. Otherwise, I'll probably just post about whatever I want or just what I'm working on in that moment.