Tuesday, March 1, 2022

BlipJoy Newsletter: Issue 2

The BlipJoy newsletter is a monthly indie gamedev publication that provides company updates and news about the game I am currently making. I'll keep with the theme introduced in the first issue with in a "technical postmortem" layout that goes over what went well, what went poorly, and what to do in the future.

Updates

To start off, I have an observation to share from my time spent with godot-rust. I have been using it extensively for a little over 6 weeks and have been able to begin familiarizing myself with how it handles the safety tradeoff between C++ and Rust. I won't be going over details in this newsletter, but I did provide some feedback on the project's GitHub issue tracker. I recommend reading the entire thread if you have any interest in the specific tradeoffs and how the interaction between the two languages may be improved in the future.

The linked comment contains a scary caveat that can easily lead to crashes and undefined behavior in developers are not careful about lifetimes of references that can be obtained unsafely. This is the kind of issue you might see referred to as "unsoundness" in Rust circles. The code is already unsafe, but it can be used in a way that violates Rust's memory safety invariants. Namely extending the lifetime of a temporary reference to an infinitely long duration (called 'static). It is possible to use this code soundly, though. Just be careful about how you store or return TRef<T> types.

Contributions

A quick aside here, I've been working in tech for about 15 years, and one of the things that drew me to the industry (apart from the obvious, I'm a computer and gaming nerd) is that the entire industry benefits directly from open source software and sometimes also contributes back. It was also frustrating when I wanted to open source a proprietary project, but the business disagreed or just didn't put forth enough effort to make it happen. When I launched BlipJoy, I made open source contributions a tenet of the business. Everything I'm using to build my game is licensed under MIT (or equivalent) rights and restrictions.

So, I'm excited to give back to the community, and proud to show off my first contribution to godot-rust. As I was working on the GUI for my terrain generator (more on that later!) I noticed that godot-rust offers a Rect2 type that doesn't export any methods, not even a constructor! The Aabb type also had the same issue. I opened a feature request ticket for it, and a few days later I decided to sit down and wrote all of the missing code.

The next godot-rust release (0.10.0) appears to be imminent, and the feature request is scheduled for 0.10.1. I don't expect the PR to be merged for quite a while, still. It was about a day and half of work. That's a fair donation, I think.

Gamedev News

On to the good stuff! One of the newsworthy updates since last month is that I have fully redesigned and built out the GUI for my terrain generator. In fact, having a terrain generator at all is new since January! It was something I quickly put together with the OpenSimplexNoise class. I got sick of recompiling the code to test minor changes like parameter adjustments, so I made this little GUI with property exports:


The only thing I kind of like about this GUI is that I was able to get property groups working with an incomplete hack. Otherwise, the GUI itself is impossible to understand. What do these numbers mean? What should I expect to happen when I change one? How might one affect another? These questions make the UX particular bad for this GUI. To remedy the issue, I scrapped it and rewrote it entirely using GraphEdit:


The nodes in the screenshot are still incomplete. Those large black rectangles are empty images where I want to put noise previews to aid the design of terrain features. But given the same value inputs, the connections between nodes make it much more obvious how these properties will interact with one another. And those image previews should help level set expectations. Even if it's still hard to predict exactly what will happen when changing something like the noise frequency, at least the result of such a change will be immediately apparent.

This new GUI is greatly inspired by NoiseTool and Blender. There are several other similar GUIs to point out as well, including Unreal Engine Blueprints and Substance Designer.

In terms of what to do next, I still haven't hooked up any of those outputs on the right-most node. (It's still just for show.) And I probably want to do some more optimizations like removing the "Constant" node and just allowing the various math nodes to take an optional constant value in place of a connection input. The underlying noise function that these nodes produce should be fairly straightforward when I get around to serializing the graph. At least I hope so.

Cycle Detection

The other issue I had, and the more interesting one, IMHO, is that all of the inputs and outputs in my graph are the same type. This made it easy to create cycles, even obviously dubious cycles by connecting a node's output directly to one of its own inputs. The solution I came up with is elegant because it is a cycle detection algorithm that runs in linear time with linear space.

A little background is in order. I know there is a way to detect cycles in linear time on a linked list with two pointers that walk the list in different speeds, apparently called Floyd's tortoise and hare, an apt name I admit. I don't believe my approach is novel, but I will share it here. The primary observation is that if the graph is already acyclic, it can remain acyclic by checking for potential cycles when adding a new edge connecting nodes.

To do this, I needed a hash table to lookup connections by node name. GraphEdit only gives you an array, and that is not very useful in this case. My connect and disconnect signal handlers just duplicate this information into a new HashMap, and I am careful about cleaning up the data structure when removing edges, so it doesn't leave any empty vectors.

#[derive(Debug, NativeClass)]
#[inherit(Panel)]
pub struct Gui {
    /// Maps node names to its input and output connections
    connections: HashMap<String, Connections>,
}

The code begins simple enough. The connections map is what I want to focus on:

/// A list of all input and output connections for the owning node.
#[derive(Debug, Default)]
pub struct Connections {
    /// Maps our input ports to `(Node ID, output port)` combos.
    pub inputs: HashMap<i64, (String, i64)>,

    /// Maps our output ports to a list of `(Node ID, input port)` combos.
    pub outputs: HashMap<i64, Vec<(String, i64)>>,
}

This could be structured a bit differently. But in this form, it allows only one edge to connect to each input port but allows multiple edges from each output port. To handle the former case, connection requests to an input which is already connected to something else will be rejected. This check is trivial with the mapping:

/// Handler for connection request events.
#[export]
fn connection(&mut self, base: &Panel, from: String, from_port: i64, to: String, to_port: i64) {
    // Only allow one connection per input
    if let Some(conn) = self.connections.get(&to) {
        if conn.inputs.contains_key(&to_port) {
            return;
        }
    }

    // Avoid creating cycles
    if self.is_connected(&to, &from) {
        return;
    }
    
    // etc...
}

I'm not including the rest of the function here, because it isn't relevant. It just calls GraphEdit::connnect_node() and then adds the connection to the mapping. At this point, all of the background info you need is in place, and we can look at the is_connected() method, which is what I feel is the best part.

/// Check if there is at least one direct path from one node to another.
fn is_connected(&self, from: &str, to: &str) -> bool {
    let mut names = VecDeque::from(vec![from]);
    let mut dupes = BTreeSet::from_iter(names.iter().copied());

    loop {
        let from = match names.pop_front() {
            Some(name) => name,
            None => return false,
        };

        if from == to {
            return true;
        }

        // Collect names for all nodes connected to the outputs
        if let Some(conns) = self.connections.get(from) {
            let outputs = conns
                .outputs
                .iter()
                .flat_map(|(_, outputs)| outputs)
                .filter_map(|(name, _)| dupes.insert(name).then(|| name.as_str()));

            names.extend(outputs);
        }
    }
}

There are a few properties of this method that I admire. I think the algorithm itself is self-explanatory, so I won't provide an in-depth analysis. The use of VecDeque that is mutated while walking the tree is interesting. As well as the filter_map deduplication and safely modifying the search queue while iterating it. Deduplicating ensures that this function always completes in linear time; any cycles cannot lead to an infinite loop by definition.

What I don't particularly like about this method is that the search is brute force. Several graphs will exhibit non-optimal walking behavior. And the deduplication doubles the memory requirement (still linear, regardless). It also requires very careful coordination to keep the connection mapping in sync with the source of truth. Any deviation will cause all kinds of problems. This should still perform adequately until graphs become unreasonably large (hundreds of thousands of nodes I would guess, but I haven't tested).

Shadows and Head Kinematics

I was surprised by just how easy it was to give my FPS camera a shadow. Shadow rendering is still a huge pain with Godot but adding a model and making it cast shadows only was way less involved than I thought it would be. That includes animation blending and everything! I probably only put 15 minutes of effort into all of it, and I feel like it really improved the immersion.

A side effect that came out of adding a shadow was just how jarring it was to look straight down and not see a body or feet! What I came up with as a solution was limiting the view rotation just enough so the feet will always be out of frame. This was actually difficult to get right, because you still want to be able to look down at a steep enough angle in situations where you need to use a ranged weapon, for instance.

Simply clamping the angle means it will be too shallow to feel comfortable due to the shape of the camera frustum. I had the eyes placed basically right in the center of what would be the person's head, and the eyes would rotate in place, leading to this issue with the angle. I tried pushing the eyes out from the center and then simulating a head tilting, but that was a disaster. Instant motion sickness from this complex movement. Part of the problem was likely because the head simulation did not include a neck and it caused some very unfortunate deformations of things rendered in the periphery.

Instead of trying to work out the kinematics of a neck and head, I ended up with just pushing the eyes out from the center of the head and continuing to do eye rotations only. So it's like your character is wearing a neck brace and they can only move their eyes up and down and turn their whole body left and right. Weird thing to imagine, but it solved all problems I had with motion sickness and aspect deformations from rotating the camera. It also allowed me to increase the max angle while looking down while keeping the feed out of frame. Problem solved?

It all looks and feels good now, but there are still some moments that don't feel genuine. Especially when running while looking down. You can see the leg on the shadow apparently touch the ground, but there is no foot making contact with it. Maybe it is worth rendering a body (or least legs and feet). I'm not sure that I want to go that far, but I will probably try it out sometime.

Mistakes Were Made

The node lifecycle methods in Godot are pretty reasonable. They are very similar to what I used to do with melonJS. We had callback s for event handling when a pooled object was being recycled and added back to the scene tree. Very similar to Godot's _enter_tree() and _exit_tree() methods. But for some reason, it surprised me that adding children in _enter_tree and not removing them in _exit_tree (because I don't want to recreate them in _enter_tree after they have already been added) causes children to be added unbounded every time the node is removed and readded to the tree.

It is obvious in retrospect. But my brain thought it was doing everything correctly with the lifecycle events and then could not explain the unusual behavior of GUI state getting reset when switching tabs or being unable to get the scroll offset of the GraphNode (it always returned zeros). Of course, these are the kinds of things you should expect when you have duplicate children sharing the same name and the newest child sits on top (gets all of the user interactions) but the oldest child is the one you get a reference two when trying to look at its properties like scroll offset.

I was able to debug it with the help of Godot's source code and debug printing in the GUI node's constructor and destructor. I could then identify that I was creating a lot of duplicates without freeing any until the application was closed. Rather than changing how I was using the lifecycle events, I instead added a quick and dirty "ready flag" to my _enter_tree() method that will ensure children nodes are only created once. And then I had to go through the rest of the GUI and undo some workaround in various parts that I needed to save and restore state between removing and readding the GUI to the tree. None of that was necessary since the child nodes themselves are persistent and I didn't have fresh new children to restore the state to anymore. That was a dumb mistake. But it is good that I caught it and it wasn't a big deal to fix.

Looking Forward

The current plan is to wrap up work on the GUI in the next week or two and turn my focus on making this plugin easy to use. Did I mention this GUI is for a terrain generation plugin? I am already aware of both HTerrain and godot-voxel. Neither of these quite captures what I'm going for, so I decided to write my own plugin.

As it matures, I plan to release the source code (MIT license, of course!) so others can use it as well. And it should have some longevity because my game is going to rely on it heavily. I'll talk more about the plugin next month as it starts to take shape.

Concept Art

My brother has offered to do some concept art for the game, which has been so amazing. He's great at what he does, and I hope one day I can start showing it off here. We've had a lot of great conversations on the subject, and I'm always surprised by just how closely we think about these things. It's like he's in my head.

I slapped together some "concept art" of my own, too, but it looks bland in comparison. I have a really old Wacom Bamboo Pen 460 (the small one) which is just not great. The precision is awful (it tends to make a lot of perfectly straight lines when drawing slowly) and it's hard to use because my hand and pen are completely out of sight while drawing. I will be buying an iPad Pro to fix these issues, and then never touch this horrible Wacom tablet again. My plans for that are to wait until I start focusing on art in the game.

Gameplay Mechanics

I have most of the mechanics worked out and written down in my design doc. I was able to prototype the most important mechanic before I started on terrain stuff. It was a little wonky, but there is plenty of time to get it right. A lot of these things should start coming together quickly once I get out of abstract land with GUIs and noise generators. Building out the mechanics is a great segue into the next topic.

Screenshot Saturday

One of my favorite days ever! To contribute my own screenshots, I need to feel comfortable about my progress. I'm happy to share images with placeholder art and untextured cubes standing in for pretty much everything. I don't know how interesting those things are for people. But I do know it's really great watching the evolution of a game unfold over time. Even more interesting is to look back on its humble beginnings after many years in development. One day! :)

I'm going to make a commitment to myself to do a Screenshot Saturday post on Twitter sometime this month. And then try to stick to a weekly cadence posting a new image or video. This is part my self-improvement goal, trying to work on community building. I'm pretty nervous showing off something so early, knowing that none of it will make it to the final game. But it must be done!

Wrapping Up

February flew by faster than expected, and I didn't make as much progress as I had hoped. Realistically, an MVP for the game by the end of march is looking very unlikely. I mentioned earlier that I had prototyped some of the mechanics, and it should be clear that the FPS controller is practically complete at this point. I don't think terrain generation will be in a good enough state in 1 month to settle on it. And there is a boatload of work to be done on prototyping the remaining game mechanics, netcode, and absolutely all of the art and sound design which I haven't touched yet.

On the bright side, I am definitely chipping away at my task list and the project is slowly taking shape. I'll just keep doing that and report back at the end of March!

That's all for now! Thanks to my supporters on GitHub and Patreon, markusmoenig and Aeonoy! I appreciate that you started this journey with me. There is plenty more to come, so stay tuned.

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!