In-Game Editor & Unit Persistence


Today I managed to squeeze some time to work on Kikai while my daughter was sleeping in the afternoon and I have some news!

A very basic form of the sandbox is starting to take shape:

What was implemented today:

Unit Persistence

One of the first things I had to build in order for the sandbox to make sense is a way to persist unit types and their code. To do this I’ve decided to keep it simple, I’ve added rusqlite, rusqlite_migration and rusqlite_pool as dependencies to the project and I’ve setup a very simple schema to represent units and their code versions:

CREATE TABLE units (
    unit_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL UNIQUE,
    current_version_id INTEGER,
    FOREIGN KEY (current_version_id) REFERENCES unit_versions (version_id)
);

CREATE TABLE unit_versions (
    version_id INTEGER PRIMARY KEY,
    unit_id INTEGER NOT NULL,
    code TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (unit_id) REFERENCES units (unit_id)
);

Two very simple tables to store units and a history of code versions (at some point I’d like to present this list of versions in the UI so you can go back and even name versions etc. But for now this part doesn’t exist.

Maybe it would be bad to try to replicate what Git already does but it seems to me that a git integration at this point would be too much work and I want to be able to edit in-game so this is it for now.

After this I created a Bevy Resource to act as a Unit Repository that encapsulates the logic to fetch all units, to create new units and to update the code of units. This is then injected into systems that need this knowledge (like the Sandbox UI) and they can interact with the unit persistence system in a fairly nice way.

Plugins

Now, this is my first Bevy project so I’m not sure how to structure things yet, but after writing quite a bit of spaghetti code over the last few days today I also took the opportunity to attempt a bit better organization in Bevy Plugins.

I’ve created three Plugins that encapsulate some basic functionality:

  • UnitRepoPlugin, that encapsulates setting the whole Unit Repository and Persistence, including runnning database migrations and injecting the resource in the app.
  • UnitSpawnPlugin, that encapsulates logic to Spawn units into the world through Bevy Events. I’ll talk a bit more about this in the next section.
  • SandboxPlugin that encapsulates all the logic of the Sandbox systems and initializations as well as the logic of the Sandbox different UIs.

Now instead of polluting my main.rs with a lot of unrelated systems and structs (as until now) I have it all in independent files that can be individually injected into the application, like so:

    App::new()
        .add_plugins(UnitRepoPlugin)
        .add_plugins(DefaultPlugins)
        .add_plugins(HelloPlugin)
        .add_plugins(EguiPlugin)
        .add_plugins(UnitSpawnPlugin)
        .add_plugins(SandboxPlugin)
        .insert_resource(ClearColor(BACKGROUND_COLOR))
        .run();

I still have some things to extract out of here, like the debugging system, but I think it is a good first step!

Unit Creation

Since all resources that are needed in a system need to be injected this makes creating a Unit a bit cumbersome since at this point it needs three resources:

    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<ColorMaterial>>,

This felt wrong to me to include everywhere so I created a small Plugin called UnitSpawnPlugin, that basically exposes an Event type

#[derive(Event)]
pub struct SpawnUnitRequest {
    pub unit_id: u64,
    pub position: Vec2,
}

This event can be sent from other systems to spawn a unit asynchronously (the actual creation will happen after the Update phase).

This has three advantages:

  • Current systems that create units do not need Assets to be injected. This is already a pretty big win in my opinion. System signatures are already humongous as they are.
  • If in the future we need more or less things in order to spawn a unit, it only needs to be changed in a single place.
  • It creates a explicit interface to create units that is easy to follow and well specified instead of multiple ad-hoc ways everywhere.

Sandbox UI

Writing multi-staged UIs using Egui (the immediate-mode framework I’m using for all GUIs in Kikai so far, similar to IMGUI but Rust native), was a nice experience today. The New Unit Type interface happens directly inside the Sandbox Window. I added an Enum with both UI modes (maybe Screens was a better name? dunno):

enum SandboxUIMode {
    MainMenu,
    CreateUnit {
        unit_name: String,
    },
}

with per-mode data as a struct enum and then I just match when drawing the window. The experience was painless and nice, structural enums are very very nice for this kind of work. Apart from that most of it is standard Egui code.

Next Steps

Next up is hooking up the in-game assembler to the editor and to be able to spawn different unit types + buildings. Then after that I’ll probably spend some time developing Unit code + devices (radio and factory) using the in-game editor and debugger and refining those workflows. Once this is done I’ll release the first demo here on Itch!

If I get stuck someday with this I may add some sprites to make the units a bit more appealing.

Leave a comment

Log in with itch.io to leave a comment.