Nokta Sorxo Retrospective
"Nokta Sorxo" is a personal jam game, I created as a gift for my friend Valarie. It is free to play, and takes about 5 minutes to complete.
Download for Linux or Windows or Play now from your browser.
I have a few goals that I wanted to accomplish with this project, and I had hit every one:
- Create a cute human character from scratch
- Learn Rust & try out a different game engine
- Compose a game soundtrack
- Create some custom shaders
- Use procedural noise and wind as aspects in the gameplay
- Release by Halloween
This project took me about a month and a half to 2 months depending on how you look at it. The first couple weeks I built the project "Stela Nubo", this was a quick and dirty asteroids game to get the hang of bevy. I forked that project into "Nokta Sorxo" after completing a simple gameplay loop, and beginning on a character design.
The witch character and her cat are a couple of my first scratch built characters. Most of my modeling experience has been hard surfaces, and boolean operations. It took me a week to create the Witch model, and about two days for the Cat who was completed at the end of the project. My inspirations for the characters were from "Kiki's Delivery Service", "Little Witch Academia", and a painting my wife has of her and her cat riding on a broom. The witch and her cat do bare resemblance to Valarie and our cat Noodle. I had a lot of fun animating that cat.
I decided to try out the Bevy game engine as I was interested in learning the Rust programming language, and I like the ECS design. It was easy to get started, the examples were enough to give me a good idea of how to use Rust and the game engine. Building is fast compared to Unity, which made testing and iterating pretty easy. The ECS system is succinct, and helps guide you to a cleaner architecture. It was nice working around a small open source engine. Bevy has a nice structure, which helped guide my own decisions when structuring my project. Diving into their code isn't overwhelming, I feel like I could easily process and understand the way the features work.
I enjoy making music, but I don't do it nearly enough. So part of the challenge for me was to compose a song that would work well for the game. The track was completed in a day with LMMS, it's been stuck in my head for the past week. I was pleasantly surprised that the game and soundtrack are almost in a natural lockstep with timing.
I approached this project as someone who dreaded shaders, they were difficult to debug with an odd structure. For the project I used WGSL, as it's the most well documented within the Bevy project. I really enjoyed my experience, and found a new love creating the shaders. The fast build times made it really easy to iterate upon, and the errors output from bevy was very helpful. I feel a lot more confident in writing shaders, and I look forward to creating more.
I knew I wanted to make a game around wind, because I was quickly getting obsessed with sailing and wanted to play around with using sailing mechanics. Ended up with the idea of a witch on her broom, chasing down her spell notes that are being blown away. I knew I wanted to make something to celebrate our upcoming anniversary, which aligns with Halloween.
My original idea was to use noise to generate the terrain, but changed my mind as I was interested more in the process of generating the terrain in Blender and transferring it to the game engine. Instead, I made use of procedural math in other components. Directly affecting the gameplay, is the wind system. The wind is affected by a few layers, the vector is a mix of the opposing direction from a point on a 2D plane, that value's tangent, modulated by the distance minus radius. Up, down, and force are determined by a worley noise. Worley noise was also used in the wind & spell effects.
Things that worked well
Rust
I came into this project with zero experience in Rust or Bevy. Previously my tools of choice were Unity and C#. Overall Rust was not difficult to understand, most concepts port over from other languages. The parts that tripped my up the most were probably the lifetime identifiers, and trying to make generic functions. There is still a lot more for me to learn with rust, I feel like the next place I would like to dive more into is generics, interfaces, and abstractions. There was a fair bit of my project that's fairly repetitive that I believe with some generics it could be cleaned up. Cargo and the compiler toolchains surrounding Rust are really nice. Laying out my project, and making builds took me a lot less time and headaches than what I was expecting when I first came into this. I have been converted, I love rust this is now my favorite language.
Bevy
I am not new to ECS (Entity-Component-System), I have experience using DOTS (Data Oriented Technology Stack) within Unity. My experience working in Bevy is significantly better than what I was experiencing within Unity. ECS is unnecessary for many projects, "Nokta Sorxo" included. However, I feel like the framework that Bevy supplies is a good structure for project design in general. Adding new features a simple and clean process that scales nicely, and following a plugin structure make it simple to manage the systems and toggle their use. Developing systems was easy, I didn't have much of any issue using debuggers and bevy comes with profiler configurations.
Designing in Blender
As there isn't an editor for Bevy, I instead used blender as a way to layout the scene and test shaders. My previous workflows had me laying out the scene from within the Unity editor, where I spent minimal time using blender besides modeling individual objects. Being forced to make more use of blender's features really helped improve my overall understanding of blender and I feel like I am a much better 3D artist because of it. When putting together the scene within blender, I was able to spot problems well before I go through the effort to transfer the objects into the game. It was also helpful to have a representation of my scene in blender, as it allowed me to make measurements when designing the gameplay and programming in some animations. The shader nodes within blender are fun to use, and provided realtime visual feedback. I couldn't export shaders directly, I was able to reconstruct it within the shader language.
Development Focus
With this project, I wanted to deliver a vertical slice of a game that was cute and fun. Too often my personal projects would fail because I'd follow a rabbit hole, loose sight of the project and eventually get bored with the idea altogether. For most of this project, I kept a pretty good focus and limited the rabbit holes. I have Valarie to thank for some of that, she was able to detect when I was getting lost in the details. Another factor that I feel like lead to improved focus, was the way that I planned development and tracked my progress. My Game Design Document and Progress tracker was a single document, and it was kept alongside the source code allowing me to reference it without leaving my editor. Progress tracking is a simple markdown list with checkboxes inside the GDD.
I started the project by listing my goals.
With my goals in mind, I brainstormed gameplay ideas and fitting stories.
Wrote out a summary then expanded it into a description.
From that I listed the core features
With the features, I listed out required assets and dependencies.
I ordered the tasks by importance for a completing the project.
Within the same ranking of importance, I would attempt to complete the "Unknown Difficulty" tasks first, followed by the most
difficult tasks. As I was new to the game engine, I had a lot of unknowns.
If they had dependencies, I was to create a minimum viable solution for that dependency first.
I would return to this list regularly during development, and made modifications as I went along.
When I began new features, I'd describe them as a new section within my game design document. Defining the components,
and any assets that may be required.
I feel like this process reduced friction caused by using tools like Trello or Spreadsheets when working on a personal project like this one. Being a simple text file that was just another tab in my editor made it easy to reference and update.
Things that could improve
Development Focus
I did fall into a rabbit hole during production of this game. I was creating a detailed landscape, I was about to take on a week-long journey to add in dynamic foliage when I didn't even have a working prototype. At that point in production, I really should have made something quick and simple instead I spent 3 days modeling and optimizing a small town. Thankfully my wife was there to catch this incident. One downside to making updates to my plan as I went along, is that it gives me plenty of openings for feature creep. It's important that I say mindful and vigilant, and it's helpful to discuss your plan regularly with others within the space as they can help keep you grounded.
Exporting from Blender to Gameplay
A major rough spot for me during development was trying to export from blender. Much of it was through my own inexperience, although the newness of the Bevy engine was a contributing factor. My primary issue were down to joint limits on skinned meshes. As these were a couple of my first from-scratch animated characters, I didn't have much experience with skinned meshes. I was stumped by cryptic errors when trying to load my model, I tested every export setting from blender and wasn't having any luck. After searching the error output, I came across a GitHub issue of someone who had a similar problem, and it was already fixed, but it wasn't merged into the current release while "main" wasn't working with other 3rd party plugins that I wanted to use. So I went onto the discord and another community member guided me to forking the release, cherry-picking the PR's that fix the issue, and using the patch feature in Cargo.
Once that fix was in place, I was able to load the game without a crash but my model was still not loading. Instead, I was given a console error informing me that I had too many joints. The number corresponded to the total bones in my character. I fixed the issue by reducing the complexity of her flowing dress. That in the end was an overall improvement for my animation process.
The error resurfaced with the cat, as besides her ears I didn't have any bones besides a "simple" rig. When I have begun researching how to simplify the exported bone structure, I found a lot of misleading comments suggesting that I just not use Rigify. I didn't buy that though. Rigify is immensely helpful when animating, and it would have meant I'd either need to redo the animations in a more difficult process or figure out how to copy the information onto a new rig. I knew the problem was that I just didn't know enough, and that I should see how others do it outside GLTF and Bevy specifically. A tutorial for exporting animated characters as FBX for Unity brought attention to the options "Export Deformation Bones Only", and "Bake all objects animations". When paired with "Flatten Bone Hierarchy" the output armature was greatly simplified and the animations worked flawlessly.
Player Input
A mistake I made early on was to not test my game on various frame-rates and performance levels. With ECS it is important that systems allow for variable update rates. Bevy provides the tools needed to measure the time since your last update, and I largely kept it in mind that I needed to use this when developing. I had noticed fairly early on that when recording with OBS Studio, the game would stutter and cause the camera to jump around. This should have been a red flag for me, and I should have addressed this early as I continued to tweak the input system throughout the process and I did land on something that was really responsive and enjoyable on my system.
I first started tackling the input trouble while trying to debug colliders, and finding the game unplayable when using the physics debugging tool. I was getting the same issues I had when recording, but it was now constant and turned up to 11. I reviewed my code, and simplified it greatly to reduce as many variables as possible. It was there that I realized that my problem was caused by the input system and the way I assumed it was working. I was using the mouse motion to move the camera around, and this value was a "distance traveled since last update". The updates were less frequent than my game logic, and the in between steps would read as "0". I also believe this value would vary depending on the device. My resolution was to add joystick support, which was a simple normalized vector and design around that. Then I emulated joystick values from mouse input by clamping the value and tracking the time since the last non-zero number. If I were to improve this further, I would keep track of the time since the last non-zero number and use that to average out the mouse movement distance and normalize the value.
I was also experiencing an issue with rubber-banding, an issue where when one object tries to follow another object too closely it could vibrate or blur as it's relative position changes wildly frame to frame. The problem is especially noticeable when it is a camera and there's a lot of movement. I had brought over a camera rig that I liked from a previous Unity project, when it was working well player's speed and changes in velocity were expressive. The camera was its own object that followed a transform of a target point attached to the player, when the camera's position can be recalculated directly after the player's physics were calculated this works well. I had placed the system to run after the physics schedule. Problems with this system didn't arise until a day before release, after I had created a WebGL build and saw it being played on someone else's computer. I don't know exactly why, but in Chrome the camera system continued to update, but it was no longer aligned with the computed value from the physics calculation causing the rubber-banding to return. The fix was as simple as making the camera a child of the camera input rig that it was following, but it lacks the expressive motion of the previous system. Because I didn't spot this issue until the last minute, I didn't have any time left to improve the way movement feels.
Joystick support in the game wasn't an intended feature, but was more of a product of convenience while I was debugging other issues in the input system. After adding it in however, I do wish there was a simpler way to navigate the game menu without using a mouse. It's a feature that was automatic within unity that I kind of miss as it's missing in Bevy. This would not be difficult system to implement, and I did see there were existing plugins that provide the generic functionality. This just wasn't a feature that I was concerned about, as my target was only Desktop.
Development Tooling
Bevy existing without an editor wasn't much of a hardship for this project, but it does place more of the burden of development tooling on the individual developers. Next game, I would take more time to setup a better system for quickly iterating on individual features. As my game was short, I got into the habit of just running the game, with maybe a couple values modified or features toggled. It allowed me to try out a lot of the gameplay, but if the game was any bit longer or more difficult it could quickly become unsustainable. I didn't however go completely without tooling. I had a few UI components that allowed me to quickly set various app states while in the game. This came in handy to ensure there was a repeatable experience.
Next time around however, I would organize my game as a library and include it inside individual crates. This would allow me to trim my game to just the features that I am working on, and keep this testing setup around but separated from the game itself.
I would also want to dive deeper into the Blender to Bevy pipeline, so that I can define more of the scenes and animations within blender and export them into bevy while minimizing labor and the duplication of data and code. The process that I took was fine for this project, however it would not scale well if the game required multiple scenes or more flexibility within the scene. Many object's positions were baked within their GLTF's, and for the player I had a system that searched for specific strings in the names of objects in order to add the right component.
Final Thoughts
I had a lot of fun with this project, and I learned that I really like sculpting and modeling. I am also a lot less afraid to make my own shaders. I really enjoy using Bevy and Rust, I was able to accomplish what I wanted and I didn't feel like it was ever too difficult to understand. It helped a lot by having the engine's source code directly available, as it provided a good reference developing some features and allowed me to work around any limitations or issues that I encountered.
I would be happy to continue using Bevy, I don't think the issues or limitations are going to negatively affect the type of games that I want to work on in the future. For that reason, I decidedly would pick Bevy over Unity for any personal projects going forward. Although I still want to make something with godot to give that a fair judgement before I pick a game engine that I would focus on for larger projects.
- Vi
Download for Linux or Windows or Play now from your browser.