Sparrow WebGL Devlog 3: 2D Particles
Sparrow's first game, a new 2D particle system, and an interactive demo09.05.2023 - 21:37
It's been a good week for my Sparrow WebGL engine. Last weekend, I used it for the first time to develop a game for Ludum Dare and this week, I worked on a new 2D particle system. In this devlog, I'm going to talk about some of the implementation details of the particle system and include an interactive demo of my particle editor.
Particles are a very important aspect of game engines and rendering frameworks. Fire, smoke, rain, snow, falling leaves, spell effects, and more are all done with particles. They are a great way to add ambiance and movement to an otherwise fairly static scene. Additionally, they are a lot of fun to implement because you can go crazy with the parameters to test the system.
Trying to find the limit of my poor Laptop's GPU:
There are many different methods of implementing particles. Unfortunately, WebGL doesn't support geometry shaders, which limits the options a bit. I decided to go with instanced rendering because it's a good middle ground: Flexible and reasonably fast while still being not too complicated to implement. If you aren't familiar with instanced rendering, it's a method to render many objects in a single draw call where the geometry of the object stays the same and only some parameters change for every instance. For example, you can use instanced rendering to draw hundreds or thousands of trees at different positions in a single draw call.
For 2D particles, the geometry is very simple (a single quad) but it always stays the same, while the position and age of each particle are instanced. Besides draw calls, you always want to minimize the amount of data that is sent to the GPU every frame. Instead of updating the 4 vertices of the quad and the particle age for a total of 9 floats, we only have to update the position and the age for a total of 3 floats, which means even for this extremely simple geometry we only have to send one-third of the data.
The particles are organized by emitter objects, which have a lot of options to control the particles. The particles themselves have a position, a velocity, an acceleration, and an age. When a new particle is spawned its values are randomized based on the parameters of the emitter. Then every frame, the particle position is updated according to its velocity, and its velocity according to its acceleration, until the particle reaches its max-age and despawns.
One thing worth noting is that even though this implementation is for 2D particles, the same system also works in 3D with only a few small changes and I'm going to use this implementation as a starting point when I add 3D particles to the engine.
While working on the particle system implementation, I also worked on a particle editor in parallel. This made testing the particle options a lot easier because rather than slowly changing the values in code, I could quickly change them in the interface and see the results immediately.
The particle editor with its interface:
I have also uploaded the particle editor in its current stage so you can play around with it if you want:
As mentioned in the intro, I used Sparrow to develop its first game last weekend during the Ludum Dare game jam. It's still in its infancy and not really ready for game development yet, but I treated the event as a test run to find potential problems as early as possible. It already worked quite well, but I encountered a few issues while creating the game. I was able to fix a few smaller ones, like missing functions and bugs, but I also found an engine-breaking bug in Firefox. For some reason, Firefox does not support the fontBoundingBoxAscent and fontBoundingBoxDescent attributes of the TextMetrics object, which I was using for text positioning. I temporarily fixed the problem with the actualBoundingBoxAscent and actualBoundingBoxDescent options, but they behave differently and cause other problems. I don't understand why Firefox doesn't support this (only via an option flag, but I cannot assume that users have it enabled), and there doesn't seem to be a good solution. I can only come up with bad workarounds like taking a bunch of generally low and tall characters and precalculating the font's bounding box, but I haven't decided yet whether I want to go down this road.
Another problem I found while creating the line trails behind the wheels of the vehicle in the game is that WebGL does not support drawing lines wider than 1 pixel. I wanted to have wide tire tracks, but the effect still looked okay with a thin line. However, I probably have to look into different solutions for drawing wider lines in WebGL.
Build System Improvements
I also improved the build system for Sparrow. Last week, I created scripts to automatically merge and minimize the files with UglifyJS, which works great. However, I wanted to have better version control. The Ludum Dare game is the first version of Sparrow used in a project and I don't want to break the game with future changes, so I manually added version 0.0 to the Sparrow build files. However, manually adding version numbers after every build was very annoying so I altered the scripts to do it automatically by reading a special version file. I even improved it further and the script now creates a folder for every version and automatically places the files in it, so I have a full history of every Sparrow version. At the moment, I increase the version number every time I use Sparrow for a public project, so Ludum Dare uses version 0.0, and this week's particle demo 0.1.
I'm pretty happy with how the particle system turned out. I'm sure there are missing features, but they should be easy to implement and I'm going to do it when I discover that they are missing. This is always the case: You only find the problems when you use the engine for an actual game, which is why I used it for Ludum Dare last weekend. I'm really enjoying working on Sparrow at the moment and I cannot wait to do more.
by Christian - 09.05.2023 - 21:37