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.
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:
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 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.
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.
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
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
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.
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!