Type-safe Phaser Scene changes and some updates about my game's backend

Search for a command to run...

No comments yet. Be the first to comment.
Hello. I'm writing a Lambda "tick" function to simulate the universe for my game. It's a different architecture than my previous idea of maintaining a 24/7 server, which simulates a busy real-time game loop. I wanted to give it a try. In this post, I...

A small tool that lets you put a "marker" on a song and have a button to recall that position with a click.

Hey there! I had a blast participating in the Appwrite hackathon and I want to share my journey with you. So, buckle up and get ready for some timer magic! Team Details Okay, before we dive in, let's get to know the team (well, it's just me actually)...

Hello. I've decided to try something for the game I'm developing. The game is a long-running online solo/coop space strategy game with resource balancing and 4x elements, but that's not the subject of this post. I decided to separate game logic code ...

Hello. For this one, I ended up using Hammer.js. I did try Interact.js which is definitely newer and more updated, but that just... well... didn't work nicely. Judging by the examples on both libraries' pages you can see if the core functionality of ...

What's up? In this post I'll write how I use some TypeScript hints for writing the Phaser client for my online web persistent-universe single/co-op player game. Also, I'll mention some substantial architecture changes I had to make to the backend when I realized I was doing things that are too complicated and brittle and should KISS.
Migrating from Excalibur js, which proved to not be stable enough for me currently, both documentation-wise and API-wise, I decided to switch to Phaser. The switch was planned rather early in the project timeline and only took a few Pomodoros to make the whole change. I was up and running again rather quickly. I must say that I am very impressed with Excalibur, they are doing a great job. Will definitely follow that project in the future.
One thing that I did with Excalibur which I wanted to keep with my Phaser adventures was typed scene name changes. From past project I learned that it's always good to have a BaseScene of your own extending the Phaser scene, so with that I could add some TypeScript magic to help me. First, I declared my scenes in a type and a variable, which looks like this:
// Data a scene might get in `init`
export type sceneDataDefs = {
loader: undefined;
galaxy: undefined;
solarSystem: { starId: string };
};
// Scene name to actual class implementation mapping
export const sceneDefs: Record<keyof sceneDataDefs, typeof Phaser.Scene> = {
loader: LoaderScene,
galaxy: GalaxyScene,
solarSystem: SolarSystemScene,
};
Here I defined both my available scenes as well as any parameters (data definitions) they might be getting on their init, like the SolarSystemScene which needs to know which star it shows. A nice bonus I get from declaring my scenes this way is in my Phaser gameConfig:
// I can declare all my phaser scenes in my gameConfig easily
scene: Object.values(sceneDefs),
And now, in my base scene, I did something like this. I should've however overridden the existing Phaser methods, I think I'll try that soon and let you know how that worked for me.
// In my BaseScene, I declare this typed method:
goToScene<T extends keyof sceneDataDefs>(
scene: T,
data?: sceneDataDefs[T]
): void {
this.scene.start(scene, data ?? undefined);
}
We get type-safe scene changes:

As I mentioned, it would probably be wiser to override existing Phaser scene-change methods. Let's try this together.
class TypeSafeSceneManager extends Phaser.Scenes.ScenePlugin {
start<T extends keyof sceneDataDefs>(
scene: T,
data?: sceneDataDefs[T]
): this {
return super.start(scene, data);
}
switch<T extends keyof sceneDataDefs>(scene: T): this {
return super.switch(scene);
}
// I might be missing some methods, but the idea stays the same
}
Re-type the scene object in the BaseScene:
export class BaseScene extends Scene {
scene!: TypeSafeSceneManager;
...
Oh my, that works! How nice is it writing a dev-blog and discovering new stuff. We get scene name completion and data type-safety.



🙂
Previously, for building ships in the backend, I wanted to create a delayed Redis or some other queue task. As I approached implementing that, I realized that from a gameplay perspective the player might cancel a build, or research or improve their production capabilities, making the colony ship building faster. I would have had to cancel the delayed task and create a new one. That doesn't sound fun, right? It's not. It would have been a hell to test and debug and it would be a host of many, many bugs that I could foresee. Plans cancelled.
KISS. Eventually I decided that I should go the game.tick() way and just implement a method that updates the whole world. Meh. It's ugly and I wanted to avoid that, but it seems to be the simplest solution and maybe even the right solution at the moment. I hacked up a game tick manager which on startup creates a promise-queue as well as add new games to the queue when these are created. It was not thought of very thoroughly but I didn't want to have all my games updated at the same time for premature optimization reasons. I actually don't feel like writing about that at the moment, but in general I create a promise that calls game.tick, waits a bit, and then re-inserts itself to the back of the queue.
I'm excited, I feel the first POC version of the game is getting in shape slowly. I got my ClickUp Gantt set up and I follow that successfully at the moment. That version will probably not be public yet at the beginning, but drop me a line if you're interested to check it up.