GameDev Weekly: 13th April 2020

It's been a while since my last update, so this one's kind of long.

Not Targeting the Web

I decided against my previous decision to target the Web. I tried, but there's too much friction. The Web is a "live" environment that requires a lot of maintenance. Servers, dependencies, web browsers, etc. I'm already dealing with that through FrontierNav so I don't want to duplicate my effort. I'm getting burnt out from it.

For more context, I took roughly a one month break from gamedev and almost all of the dependencies went out of date with security warnings and deprecations. All of which I went through for FrontierNav, so repeating it again is just... a headache.

To reduce repition, I could merge the two projects into one, but that will likely cause them to trip over each other since they have very different requirements and programming styles. So I think there's benefit in having a clear boundary between the two.

Using Rust

I decided to take the leap and learn Rust. Why Rust? I've always wanted to use a lower-level language. Game development is one of the few moments when I hit performance issues -- mainly due to garbage collection -- and I find it counterintuitive to take the usual steps to solve them like using object pools to hide from the garbage collector.

I could pick up learning C++ again but Rust seems more suitable for someone moving from garbage-collected ecosystems like JavaScript/TypeScript and Java since there's less focus on manual memory allocation.

My main reason for rejecting Rust initially was the lack of proper WASM support. But since I've moved away from targeting the Web, that's not an issue. When WASM support does arrive, it'll be a nice bonus.

I also previously mentioned that Rust isn't mature yet, and that is still true. While Rust itself is production-ready, the standard library is intentionally very small so that developers use crates (packages) instead. The problem is a lot of crates are still on initial development versions (i.e. v0.x) and some are either abandoned or not in active development. Relying on hobbyists to maintain your dependencies in their free time isn't great (e.g. bus factor), but most ecosystems have this issue. That's just how it is.

I'd argue it's impossible to avoid dependencies without writing everything yourself. Even an extensive standard library will need maintenance, and the larger it gets the more fragmented that maintenance becomes. The best solution I've learnt from dealing with JavaScript's dependency hell is to be aware of your dependencies and make sure you design around them so that they can be migrated easily.

Picking a Framework

Most games require graphics, sound and input handling. I looked into individual crates that can handle this and got a bit overwhelemed. There's a lot of crates, most at 0.x, so it's hard to predict which will be reliable.

So instead, I decided to delegate those decisions to a framework. There's three main ones I've found: Piston, ggez and Amethyst.

Piston is not in active development, the last change was 7 months ago, but it has everything I need. ggez is in a similar situation, though it's a bit more active. The graphics API for Piston is more lower-level which I'd like to avoid while I get to grips with Rust, so I prefer ggez over it. ggez is also a bit more thorough with examples and documentation.

Amethyst has a lot more activity and generally seems more reliable in the long run. However, reading through the documentation, it's a lot more heavy. Being new to Rust, I can't fully understand how it works so it's probably best to avoid it until I do. Also, since it's so big, I'd rather wait until it hits 1.x. I'm familiar with ECS and Amethyst strongly encourages it so chances are I'll end up migrating to it or at least learn from it.

So, I've picked ggez for now. I like that it's a combination of other libraries and delegates to them using a thin API. Even if it dies in the future, those APIs I've learnt should still be relevant.

Development Environment

Next... development environment. IntelliJ and Visual Studio Code are the most popular. I'm familiar with both and I prefer IntelliJ. However, I don't have a license anymore and since I'm just starting out, I'll stick with VSCode.

VSCode needs extensions to support Rust. The main ones I'm using are:

I don't know much about LLDB, but it's some standard protocol for debugging compiled code and it works so that's great.

rls isn't great however. It works, but it's not very helpful with autocompletions. I found rust-analyzer to be a lot more featureful, however I tripped on a few bugs that kept telling me there were errors that didn't exist and so I went back. I'd rather use something that works reliably.

Game of Life

Alright, so next step. Game of Life. No input, no sound, just simple logic and graphics. I've made the repository public. Not really proud of it, but I learnt a lot about Rust and it helped get my environment and workflow ready.

Game of Life Preview

I was planning to write a separate post about this before but I didn't get around to it. Now I've forgotten the specific scenarios and problems I faced, but the knowledge carried over. I hope.

Are we there yet?

Finally, I'm ready to write my game.

Game Progress Preview

I've got a grid, player and moveable camera working. It's a start.

ECS When?

I'm already in a situation where I want an ECS library to manage state and update logic. Amethyst's ECS library, specs, is available to use as a standalone crate. I didn't understand how it worked at first, but having progressed, I kind of do. However, it uses unsafe to bypass some of Rust's compile-time checks which puts me off using it. I don't have the experience to understand why it needs to do that, so maybe I should try writing my own to find out.

Writing an ECS framework seems a bit beyond my Rust skillset right now so I'll refactor over to one once my current per-entity update becomes unmanagable.

Compile Times

One of the benefits of using an intepreted/just-in-time language is not having to wait for compile times. There is some transpiling (e.g. converting TypeScript to JavaScript), but it's on a per-file level rather than a single big executable. In comparison, a small change in Rust takes minimum a couple of seconds to compile. Kind of slow, and it adds up.

I guess this is another "we'll see". I don't want to be at a point where I'm writing most of the game in Lua to skip compile times. Kind of defeats the point in using Rust!

What is it?

I've codenamed this game 'Project Tactics'. I don't want to give it a proper name until it's actually worth playing and has a stable theme.

It's going to be a turn-based strategy card game. I've done some research and there's so many indie games working on roughly the same idea. That's always going to be the case and I just need to get over it. I'll work on differentiating it once I've got the basics done.

I'm thinking of publishing a base implementation as a separate game and then introduce new mechanics one game at a time as a way to iterate and gain experience in finishing games.

That's about it for this update. It was a long one since I haven't updated in a while. Next week should be a bit more focused.

Thanks for reading.