Necking devlog 2

Second weekly devildahu devlog, the first one is available here.

Necking

This week, I only dedicated 3 days to Necking, and three very short days at that.

I wanted to make Necking more editable to enable less technical members of the team to play with parameters of the game, and especially load arbitrary models for the giraffes.

Scene reload

bevy ostensibly supports hot reloading. However, hot reloading in bevy is broken for scenes. This is a problem, since giraffes are loaded as scenes, this bug would prevent loading live new giraffe models.

Solving this involved two things:

  1. Have some sort of management of scenes to keep track of spawned entities related to a scene.
  2. Writing hot-reloading code.

For (1) I was already using bevy-scene-hook. However, as implemented, it didn’t allow for reloading. So I created a reload module, mostly a copy of the original code, but with handling for respawning and despawing.

For (2), I started by shamelessly copying the bevy code for hot-reloading, it’s about 30 lines of code. It’s nothing more than creating a RecommendedWatcher from the notify crate, establishing a communication channel between it and a bevy system and reacting to events it sends.

I kept this code, and when receiving an event, I set the ReloadSate component from the newly created reload module of bevy-scene-hook.

Parametrization

I’ve been a great fan of Sakurai’s series on video games. He explains in one of those videos that he uses an Excel Add-In to manage all the parameters (damage, time, cancel frames etc.) of the Smash Bross characters.

Necking had a lot of parameters hardcoded. But as a good programmer, I avoid just sprinkling the code with arbitrary values. Most of the parameters, even if hardcoded, were named and centralized as a series of const definitions.

Hot programming tip

If in your code, you see a literal value within a function such as 0.132 (something extremely specific), it is probably worthwhile to replace it with a constant. Naming things help build a semantic narrative around what your code is doing.

Furthermore if in the future, you need to re-use the same value, you can simply use the variable name rather than copy/paste the value:

// What is 12.344???
fn do_the_foobar(position: &mut Vec3) {
  position.x += 12.344;
}
// much better,
// I know that I'm adding RETICULAR_OFFSET to `x`!
const RETICULAR_OFFSET: f32 = 12.344;
fn do_the_foobar(position: &mut Vec3) {
  position.x += RETICULAR_OFFSET;
}

My goal was to get rid of all those consts and replace them with a bevy resource that I would load at startup from an external file.

I also wanted to be able to write a template file, ideally with default values taken from the consts pre-existing values.

It was almost straightforward. I decided to create a struct Parameters per bevy plugin/rust module that used to rely on hardcoded const values.

#[derive(Clone, Debug, Resource, Default)]
#[cfg_attr(feature = "debug", derive(Reflect, FromReflect))]
pub struct Parameters {
    pub controls: scheme::Parameters,
    pub neck: neck::Parameters,
    pub camera: cam::Parameters,
    pub input_mapping: input::Mapping,
    pub models: Models,
}
mod scheme {
  #[derive(Clone, Debug, Resource, Default)]
  #[cfg_attr(feature = "debug", derive(Reflect, FromReflect))]
  pub(super) struct Parameters {
      neck: neck::Parameters,
      giraffe: giraffe::Parameters,
      tongue: tongue::Parameters,
  }
  mod neck {
    pub(super) struct Parameters {
      idle_stiffness_multiplier: f32,
      idle_damping_multiplier: f32,
      moving_stiffness_multiplier: f32,
      // ...
    }
  }
  mod giraffe {
    // ...
  }
  // ...
}
mod cam {
  // ...
}
//...

Then, in systems defined in those plugins, I would add a Res<Parameters> to access the parameters, instead of directly using a const.

This worked fairly well, the rust module privacy system also aligns very nicely with this. In a module, I needed to access the root Parameters fields, so the root’s field needed to be public, but each individual module Parameters could keep their field private, hence limiting what can be influenced by which field, resulting in much more robust code.

A snag was the hook for spawning giraffes. The physics parameters for the neck are used in the scheme::neck module, but also in the neck module, where the giraffe is spawned.

I simply made those parameters public across the whole game, so that I could use them in both places. The cleaner solution would have been to get rid of that neck module, but I didn’t have time for that.

Serializing parameters

Only thing left to do was reading and writing to a file the parameters, so that they could be changed by anyone with a text editor.

I hit a snag here. I relied on bevy’s bevy_reflect for serialization, this is because bevy_reflect is readily available. The standard rust crate for serialization is serde, but it has the reputation of drastically slowing down compile time.

I then used the toml crate for the parameters file. When I first launched the game to write the default list of parameters, I hit a very generic error. It said something along the line of “enums are not supported” without further details. I recalled that once upon a time, bevy_reflect didn’t support serialization of enums. But having myself reviewed the code that added support for enums in bevy_reflect, I was very surprised to encounter this error now!

Turned out the error emanated from the toml crate, indeed, the doc string for the toml::ser::Serializer struct has a line explaining that toml doesn’t support enums (It would have been judicious to advertise that limitation in the README or the crate-level doc string, or having a better error message).

In my Parameters, I only had a single enum, for custom input:

enum KmKey {
    Mouse(MouseButton),
    Key(KeyCode),
}

I needed to replace this. I immediately thought of two options:

  1. Use a struct with two Option fields, one for mouse buttons, one for keyboard keys.
  2. MouseButton and KeyCode being two enums, merge them in a giga-enum. Precisely, what toml doesn’t support are enums with data, KeyCode and MouseButton are what rust calls “C-like enums”, they do not have associated data, so making a large enum would work.

I opted for (2), KeyCode is a very large enum, but whatever eh! If at any point KeyCode or MouseButton in bevy changes, I’ll have a compilation error allowing me to update the enum. This is thanks to rust’ exhaustiveness check.

The February game

I solicited game ideas in the devildahu discord chat for the 3 weeks project. Brinzer posted his detailed design doc of PointFusion, a 2d Ik-driven game.

I’m not sure it’s possible to complete it in 3 weeks, so you might not hear much more of it until the distant future.

Cuicui

In the meantime, between two games projects, I thought it would be productive to focus on my UI framework, bevy_mod_cuicui.

A very naive thought.

I spent the past 4 days writing design documentation, just to know what code to write for it! I always feel “so close” to getting somewhere, yet at each point in time, I am seemingly as far from a solution as I was at any given moment in the past.

I think this stems from me not having an exact idea of what I want to do with bevy_mod_cuicui. How much flexibility should it have? What should it do?

After 4 days though, I’m convinced (as I was 2 days ago though) that I’ve a vague – if not clear – idea of what I want.

Bevy and ECS’s strength is in modularity. I don’t really want to write a UI framework. UI frameworks have a tendency to be totalitarian, they deal poorly with things that exist outside of them. This is the antithesis of what I want to do. I want to empower people, give more possibilities.

My current plan is to simply write a few systems, that, if used together, enable a pleasant UI experience in bevy:

  1. The layout algorithm I mentioned last week.
  2. A Prefab trait to spawn things into the ECS, despawn them and query their current state.
  3. A set of Prefabs to build UI elements that are a composition of entities.
  4. A QT-inspired signaling system, where functions associated with certain entities can be “activated” and read the state of children entities to do things.

I’m still not sure for (4). The goal is to avoid footguns and enable developers to reason locally (avoid having to think holistically, accidentally mixing several instances of the UI elements)

I’ll probably need an ultimate layer of coating on this. Raw, it will still be painful to use, so I expect to add something like a macro to thread together multiple UI elements.

Turns out making a UI framework is as difficult as people say.

(you may be interested in this blog’s rss feed)