Thursday, February 3, 2022

BlipJoy Newsletter: Issue 1

BlipJoy officially opened for business late last year as an independent game studio. This year I am launching a monthly newsletter to go over what's been happening behind the scenes and generally keeping my audience up to date. I'll try to keep the format consistent, starting with a short introduction and closing with a "postmortem" style list of things that went well in the last month, what went poorly, and what to look forward to.

About BlipJoy

It wouldn't be right to just jump right into what I've been working on without first quickly describing who I am and what my goals are. Let's get some personal details out of the way! My name is Jay, I've been coding and hacking on video games for about 25 years. I grew up wanting to become a game developer. I have been making little games and working on game engine tech for roughly a decade under the pseudonym BlipJoy. You can see some of my game dev work in the GitHub Org profile (links are on the right side of the blog).

In September 2021, I formed a Limited Liability Company to ramp up production on my first real game project. Sadly, that project came to an abrupt end in December, and I'll talk about it in more detail in the next section. Starting this month, I have been refocusing my efforts on a completely new tech stack (new to me) and I have been busy off and on trying to chase down minor bugs.

I'm not ready to announce the game I am working on, but I do want to provide at least a high-level overview of the business plan. And of course, most of this newsletter will be dedicated to providing information about various aspects of the development of the project. Let's start with the unambiguous basics:

  • The initial target platforms are Windows, Linux, and macOS. I am especially interested in Linux support because I've already preordered the Steam Deck (expected order availability is "Q2 2022", not bad!) and I shouldn't have any trouble porting the game.
  • The obvious storefront is Steam, but I am also interested in checking out some alternatives like Epic and GOG. They all seem to offer similar API/SDK capabilities for things like multiplayer, cloud saves, achievements, and the like.
  • Console ports might happen, but they are not a priority and if I'm honest, I am kind of concerned about how closed these platforms are. That makes it significantly more difficult and riskier for an independent developer to get their games onto any of the consoles. I'll save console ports for a "to be determined" item to deal with later.
  • This game is expected to take quite a while to make. I have about 2 years of runway right now, but I have some ideas to help offset costs and extend that runway to increase my chances of success. (More about that in a future newsletter!) I suspect overall development time will have to stretch beyond 2 years.
  • My tech stack is Godot and Rust. I started with Godot 3.4, since it is the current stable branch. The future looks bright for Godot 4.0, but I cannot commit to the engine in its current state. One downside with this choice is that porting to 4.0 later will be much more difficult than just starting with 4.0 in the first place. But it's the right choice because I can't justify dealing with the bugs and trying to port godot-rust to GDExtension at the same time. I want to make a game, not work on game engines!

The short-term roadmap for this game currently looks something like this:

  1. Minimum viable product in Q1 2022.
  2. Alpha in Q3 2022.
  3. Beta and Early Access in Q4 2022 or Q1 2023.

MVP is a basic prototype. The reason I'm giving myself up to 3 months for this one is because I am learning how to use the game engine from scratch, and because the scope is a little bigger than what might fit into a game jam. There's plenty of time to build the alpha, up to 9 months! This stage will be all prerelease, when the content and mechanics are still malleable. I've combined Beta and Early Access into a single phase for simplicity. When I can qualify the game as a beta, then it will also be ready to take on public beta testing in the form of Early Access. I'll consider my options for private betas in the months leading up to EA.

That's about it for the early projections! This is all likely to change over time. And one of the goals of this newsletter will be to inform everyone of any updates or announcements. Watch this space!

What Went Well

Starting a New Project

While I wasn't working on the legal stuff for my business, I was making a game. I had the start of a level made in Crocotile 3D, I had a unique art style, I had a powerful and highly customizable game engine at my disposal, bevy, and my entire project was 100% written in Rust.

Things started going downhill when I started looking into how to add animations to my character model. Bevy does not support mesh skinning or skeletal animations. "No problem!" I thought, bevy is open source, and it isn't a ton of work to add animations. I can just do that and the whole community will benefit, too! There's already an open PR for mesh skinning. It's only compatible with bevy 0.5. I did some work to get it migrated to the pipelined renderer (this was at a time before bevy 0.6 was released). One of the problems I hit while doing this work is that the pipelined renderer hardcodes vertex attributes. It isn't even possible to write a plugin that can add new vertex attributes. I opened a PR to add dynamic vertex attribute support.

What I like about Bevy is, well, actually I like a whole lot of things about bevy! The design is nice, the community is great, and the plugin model makes creating extensions convenient. I was happy to contribute something that several (most?) bevy users need. But my experience with contributing to the project was very not good. This is the first time I've made my opinion on this public. In the interest of not creating any drama, and to keep the newsletter "speaking for BlipJoy", I only want to provide my perspective on what happened and why I had to stop working on bevy, and ultimately stop working on my game that uses it.

The mesh skinning PR has been open for around 6 months with no activity from the original author since. The bevy team announced their interest in the thread last month, but there is still no progress on it. I'm a little concerned, since animations are a core feature of a game engine and mesh skinning is core to animation.

Meanwhile, my dynamic vertex attribute PR has been open for two-and-a-half months and is similarly stalled with last interest from the bevy team shown a month ago. As much as I want to see bevy gain these capabilities (and I know that it will!) I also have to be pragmatic. I have a business to run, and I cannot afford to wait for bevy to figure out when it wants games to use skeletal animations. Bevy is evolving quickly, just not in the direction I need it to right now. So, I had to make the difficult decision to scrap my bevy-based game entirely. I didn't feel like I could build the kind of game I wanted with another engine where I have less control over the internals.

Aside: Maybe I should have paid more attention to the warnings. I wasn't worried about API breakage, but I didn't expect to be stuck in a holding pattern with no clear way out. No disrespect to bevy, this was clearly all my fault.

I regret having to leave bevy after only a few months, but reality set in pretty strong and I just have to make my business viable. That means I need to ship a game, and I cannot compromise on my vision. I really look forward to what bevy can do in the future, and I wish them all the best! Who knows, maybe in a few years I'll check it out again and make my second game with it?

Also out of pragmatism, I picked Godot over Unreal Engine or Unity. Open source is a big win, IMHO, and the license is compatible with my business. I'll still find some time to contribute back to the project, so it will work out similar to what I had hoped for bevy. I was intrigued by the Godot bindings for Rust and was surprised with how well it seemed to work.

I guess this one was a little of both "what went well" and "what went wrong". But I wanted to start the newsletter with this short retrospective. And it just so happens to be a good segue into what I started working on this month!

Godot

Starting in January, I picked up Godot for the first time and I was able to get a basic 3D scene working with an animated NPC using free assets in just a few minutes. I was also quite happy to have the editor, which I believe will accelerate development if I can take better advantage of it.

Physics

It took about a day to go from "camera showing animated model" to having a simple FPS controller with physics. I had a lot of success implementing the jumping physics described in Math for Game Programmers: Building a Better Jump. The presentation is a bit dense! But there's also a simplified version as a Godot tutorial in two parts: part 1 and part 2. This was an eye-opening experience since I had never considered parameterizing gravity over how high and how far I want the player to be able to jump. But it just makes so much sense. I'm now looking for ways to parameterize other aspects of the game over non-obvious attributes because of this.

Slide from Math for Game Programmers: Building a Better Jump

Rust

Rust has been awesome, as usual. Using godot-rust has been decent. I haven't hit any major issues with it at all. So far, I have only been compiling my NativeScripts with Rust in debug mode (without optimizations) and running the game has always performed good enough. I guess this could be compared to running game logic in GDScript or lua. You don't expect these parts of the game to run particularly fast, and in general they won't have to. So, it turns out Rust is actually an incredible scripting language! I am interested in seeing how well it goes when I get to running release builds with full optimizations.

Cargo Bot

To simplify the build-run-observe cycle, I wrote a naive watcher (like cargo-watch, in fact I am using the same watchexec crate that it is built on) to rebuild my crate and put the DLL into the Godot project directory every time I save a file in the crate project. This has really streamlined my capability to make changes quickly. I made it into a cargo alias so I can run it as cargo bot in my shell.

What Went Wrong

There were some interesting (as in non-obvious) bugs in this last month! I'd like to share some of them, at least with descriptions. I'm not ready to share screenshots or videos, since they might reveal some details but also because they won't be representative of the final visuals in the game.

bindgen

Godot-rust gave me a minor issue with its build process (requires LLVM, which took 15 minutes to build on a 12-core 5900X). The most concerning issue I had with the build process was finding that it took 15 seconds for incremental builds of the godot-rust hello world example I started with. That seemed completely unreasonable. I tracked it down to bindgen rebuilding the Godot bindings on every minor change to my project (completely unrelated to the Godot bindings).

After more digging, I noticed its build script depends on the value of the LIBCLANG_PATH env var (which was part of the setup process I had to do). And that I had accidentally configured my Windows environment and my bash environment with a different value for this variable! I used the C:\path\to\libclang style in Windows, and the /c/path/to/libclang style in bash because I thought it would be appropriate. (The reason for the split environments is nuance, but that's because I'm using bash in Windows!)

What was happening is that Sublime Text (and rust-analyzer) where using the Windows env. Every time I would save a change in my project, rust-analyzer would have to rebuild bindgen with the Windows path. And every time I ran cargo in bash it would rebuild bindgen again with the bash path. The solution was using the Windows style path in both environments. It's a little odd because that's the only path in my bash env with that style. But at least my incremental builds now take less than 1 second to compile!

Physics

Physics was a real pain to get right. I'm reasonably competent with the math behind it, but it's just so easy to make silly mistakes. Honestly, I guess there were multiple issues. I had trouble with collisions causing the camera to "stutter" when walking against a wall, instead of smoothly sliding against it. This turned out to be caused by running the game at a high frame rate (180 fps) but only updating physics infrequently (60 fps). I bumped physics updates to 240 fps and that problem went away (higher physics update frequency will also give better integration results in the general case).

The more onerous physics bug was confusing results from parameterized gravity. After I thought I had it working, I setup a test environment with ramps and platforms to make sure the player could easily clear a 3-meter gap while sprinting. It didn't work right until I tweaked the values to about twice what they should have been. I left it like this for a few days but finally got annoyed not knowing why that was and I went over the math multiple times but couldn't find anything wrong with it.

Then it hit me to check the scale of the world. Visually everything looked great! My platforms were supposed to be 2 meters high, and the NPC was about 3/4 as tall as that height. The jump height that I had to set for physics was about 1.8m to get my FPS controller to jump on top of a 1-meter-high block (about half of the NPC height)! And on top of that, I had to set the distance to the peak of my jump to 3m to clear a 3-meter gap. This is absurd! The peak of the jump should be half of the distance of the full arc.

Things got even more confusing when I created a cube in Godot to verify that the 1-meter cube really was 1m high. And it was! Or at least I thought it was. I learned that day Godot's default CubeMesh size is 2. 😐

I was comparing a 2m cube to a cube in my scene that I thought was 1m, and they were the same size! Suddenly it all made sense and a strange realization came to me. By coincidence, the NPC model I used was twice as large as it should have been (I don't know why this is; it's a CC0 model from the Quaternius Ultimate Animated Character Pack) and the test level I created in Crocotile 3D also happened to be exactly scaled up to 2 units per meter. As for how that happened, well, I just took a guess at the "Base Pixel Unit" and "3D Tilescale" settings and it happened to align with the scale of the NPC model.

After rescaling everything, I had to redo the physics parameters. But this time, entering specific numbers had the expected outcome for my player movement! It was absolutely perfect. I could now jump exactly 1.25m high. No more and no less. Easily jump on top of 1m-high blocks and easily cross a 3-meter gap with a jump peak distance of 1.65m while sprinting. For an added bonus, I could just barely jump on top of the NPC's head from a running start without adjusting any settings. This worked out because both the NPC and player use a capsule collision shape and they are both about 1.5m high. The rounded ends are just enough to give it that extra 250cm of height with the sprinting speed.

Cargo Bot

I'm not entirely happy with the implementation of the watcher/rebuilder that I wrote. It has to shell out to cargo so it can echo status updates and use cp to copy files. This is a limitation of watchexec. If I ever get the itch, I will be replacing watchexec and inline shell scripts with something more Rusty. But for quick-and-dirty, it's fine.

Losing Focus

One of the greatest challenges I face is staying focused and always working on the most important thing. Right now, that's terrain generation so players have a unique environment to explore. But also I need to know when I can take some time to shift focus to outreach and building a community. That's why we have the newsletter!

I ended up wasting a lot of time chasing squirrels that I don't need to be concerned with yet. Things like performance optimizations and improving visuals. I kind of want real time lighting and shadow rendering in this game but getting it to look good enough is really hard in Godot. They have made big improvements in the 4.0 alpha as far as quality goes. But it isn't perfect, and the performance still leaves some things to desire. I opened an issue for discussion, and there is already some nice feedback and conversation taking place. That said, I should not be spending my time working on shadows.

What to Keep Doing

Terrain

There are still a few months left for the MVP. That isn't a whole lot of time, but I need to take this project to the next step beyond "decent player controller" and into the realm of a playable game. There are some mechanics implemented that I didn't discuss above, but they will have to be redone anyway. Part of the problem is that I don't have a "world" for the player to walk around yet. This is my priority going into February, and I expect to have half-decent procedural terrain and texture generation within a week or two. I have done some procedural content stuff in the past and it was a lot of fun. The results were also better than expected: It's a Lovely Day for a Drive.

The terrain I'm working on will be a little better than the mountains in the background of this screenshot. I will also be going for a very different style. But it is good to see how well that game holds up after all these years. (Not to mention the whole thing is only 9 KB. Gotta love procedural content!)

The new terrain system will be parameterized (just like I did with physics) to allow effortless tweaks in the Godot editor and that should allow me to come up with some initial values that produce convincing and interesting landscapes. I could even go a bit meta and parameterize the parameters, so they all have some fluctuation to avoid the issue with randomly generated landscapes all feeling "same-y".

If you create a new map in vanilla Minecraft, you know exactly what to expect; you're spawned near a forest, there are trees in the forest and some rolling hills and small mountains. All Minecraft forests look alike. (That is the point of fractals, after all.) But in the real world, forests all over the world are quite varied and unique. You can look at a photo of any forest and it will probably be difficult to pick out exactly which forest it is (yeah, there's a whole game based on this premise!) But if you look at a screenshot of a forest biome from Minecraft, you know it's a forest biome from Minecraft. And it doesn't matter what the location of the forest is, or even what seed generated it. Because you can just use any seed and walk over to any forest biome in the game, and it will feel like the same forest from the screenshot.

That's what I mean by "same-y". I would like to find a way to break out of that pattern with procedural terrain generation. And my hypothesis is that meta parameterization might help make almost imperceptible corrections that produces enough variety to claim, "this forest is truly unique". But this is all experimental and I have no idea what I'm talking about. 😄

If I have any time left over in the month after the basics of terrain generation are in place, the next thing I need to work on are the game mechanics. I have already written a comprehensive section on mechanics in my design doc. There are still some gaps to fill in the plan, but I think I have enough to work on until March.

Community Focus

I'll also put a little time into community focused things like these newsletters, and finally launching a website!

Sorry I'm still keeping things vague. I don't want to make any promises I can't keep, but I'll be making an official announcement as I get closer to alpha. If you are interested, though, I encourage you to follow the newsletter. You might be able to piece together what I'm making just based on the details I do provide. And if you become a patron or sponsor, I'll be sure to send these out directly to your email every month!