Lessons learned from building a turn based strategy game in Phaser

Hey, someone asked me to describe the process and challenges with building https://www.konkr.io/. It’s a pretty damn wide topic worth a number of blog posts, but let me at least share some quick notes on what I wish I knew before I started.

For context, this was my first real Phaser project, I mostly do web dev so not an experienced game developer at all. So take anything that resembles advice here with a huge grain of salt :slightly_smiling_face: .

Tools:

  • dat.gui is amazing when you need a quick temp UI for testing or playing around with things
  • Texture packer is awesome for automatically generating texture atlas from your png files
  • BM font can be used for generating bitmap fonts from your system fonts; I kind of hate the tool, horrible UX, but didn’t find anything better
  • I started the project from this template GitHub - yandeu/phaser-project-template: 🕹️ Phaser 3 - Starter Template with TypeScript and webpack. and never regreted it. Maybe there are cooler options now (swc/esbuild?) but to this day even with the project becoming quite big this setup gives me smooth and fast dev experience.
  • Which reminds me yeah, typescript. I love TS and honestly can’t imagine completing a project of this scope in plain js. The complexity will grow and you will need to rewrite and refactor a lot, and typescript makes that actually possible without breaking everything. I’m sure I would spend like 20x the time debugging various stupid bugs without it (well not really, because I would just quit at some point).

Phaser tips:

  • Don’t try to understand the Camera object API intiutively, you will suffer :slightly_smiling_face:. Some methods deal with camera position on screen, others with world coordinates, some take into account zoom and some don’t… Spend the effort to go through the API and make sure you know exactly what you’re doing when you use it.
  • If you want to use HTML for parts of the UI, you might want to go all in on this - mixing phaser objects with DOM elements in one UI gets messy in my experience. Just be aware of the limitations, mainly that you can’t render anything with phaser on top of DOM elements. For Konkr I mostly use Phaser even for UI elements and I’m happy with it now, but it took a lot of effort and I kind of built my own UI framework in the process lol. Going all in on building the UI in something like React might have been a better choice.
  • Scene.tweens.timeScale is awesome for debugging tweens - go into bullet time and see exactly what’s going on with your messy transitions
  • If you are returning a sprite/image into a pool, make extra sure there are no tweens still acting upon it, that can lead to really bizzare bugs after you reuse that object somewhere else.
  • Second largest source of glitches was accidentally allowing multiple tweens to act on the same object. I am paranoid now and usually attach dedicated controller classes to UI and game world objects which ensure there can’t be any conflicting tweens playing on them simultaneously.

The process of building a game:

  • Building a game that expands on an existing proven gameplay loop (in my case proven by Sean O’Connors Slay) was a huge boon and helped me stay focused and confident in the end result during the many months it took for the game to actually become fun to play. If you don’t have this, you should definitely throw together some quick and dirty prototype to prove to yourself that the core formula of the game is fun before you dig into it any deeper.
  • Be ready to throw away days or weeks of work when you find out the feature just doesn’t work out the way you expected, isn’t as fun as you thought, or is fundamentally flawed. Pretty sure that’s an inevitable part of the process and falling for the sunk cost falacy is one sure way to doom your game.
  • A lesson I learned with software development in general is don’t try to design sophisticated abstractions before you deeply, intimately understand the problem you’re solving. When you’re out of your comfort zone, just go for the most straighforward solution that kind of works and run with it until until it becomes too painful, then rewrite it. Almost every part of Konkr got completely rewritten at least once, and the only thing I regret about this is spending too much time upfront trying to come up with the perfect future proof solution form the start. The result was often an overengineered mess that of course had to be rewriten later anyway.

Design patterns that seem to be working out well for me:

  • one scene for game world, number of dedicated scenes for various UI layers
  • Scenes are not the main controller of the game. I use custom classes i call Screens to represent each “screen” in the game (like main menu, level select, play screen, map editor, etc.) - these orchestrate the individual scenes and contain most of the application logic - Scenes on the other hand are pretty dumb and only concerned with displaying and animating stuff, which makes them reusable across different Screens (so I can for example use the same world scene for the actual gameplay, map editor and level preview).
  • a global event bus for UI events; buttons and other interactive elements are not wired to any particular method call, they just trigger an event and the currently active screen handles it
  • UI scenes usually derive their look and behavior from a single state object that is updated by the controlling Screen. This coupled with the event-driven architecture leads to a redux-like way of managing state in the application and it made things much less buggy once I began to stick to it.

Thanks for reading, hopefuly you got something useful out of this :grin:.

6 Likes

Thanks for taking the time to write it down.

  • agree with the animations / tween bugs. Very nasty stuff.
  • HTML for menu items etc are actually the way to go, I also went through the process of realizing I’m creating a ui framework (buttons / lists / popups etc), which are of course way easier with a js framework.
  • what do you mean by ‘global event bus for UI events’? redux? I’ve been using rxjs to wire state and events. Simple, works ok, but a bit of a pain to debug.

Anyway, interesting read for someone in the middle of making a game. Which has proven quite more difficult than I initially thought.

Cheers,

Elger

Thanks for the comment!

what do you mean by ‘global event bus for UI events’?

I’m just rolling my own simple event bus implemetation with typed events inspired by typescript-fsa - npm. Rxjs sounds like a fine choice, although probably overkill for my usage.

The point is just that there’s one global event bus on which anything can emit various events and anything else can subscribe to them.

For example once a new unit was bought a PawnBought event is triggered, into which a number of independent system can hook up:

  • the game world updates showing the new unit
  • game UI updates (updating the current gold reserves and buyable units)
  • an interactive tutorial defined for this level may react by showing a new tutorial message
  • stats collector records the action for analytics

Especially once I started adding the tutorials and analytics, the ability to hook into any of the existing events without adding any coupling or new complexity to the existing systems felt really nice.

That said I always make sure that if a given event has multiple listeners, they are truly independent of each other. Probably wouldn’t be a good idea to have the correct function of the game depend on multiple event listeners being triggered in some specific order :slight_smile:.

I never had trouble debugging it, to the contrary I feel it increases my visibility into the games internal workings. When hunting down a bug, just logging all the events often quickly reveals where things started to go wrong, and by logging the stack trace whenever an event is triggered I can always find out what code exactly triggerred which event etc. I guess the ease of debugging may be one point in favour of just rolling a super simple custom implementation over some third party library like rxjs :slight_smile:.

@michal-bures this post’s a blast. Could you give some insights about how you implement the stack trace? It must be a bit chaotic when debugging a lot of code, do you enable/disable the whole stack trace or do you manage to show only from certain modules? To avoid interfering with other debug messages?

Thank you very much for these pointers, I wish more people would be encouraged to talk about architecture and pattern design.

@josemwarrior Aww, glad to see people still find this post useful two years later :grin:.

I use debug - npm to help organize logging, come to think of it that should totally have been part of the original post :slight_smile:. I give each module it’s own logging namespace and then when I want to see detailed logging for let’s say the event bus, I put debug="game:events" into local storage to enable those log messages.

I also strip all the debug calls from production build (with string-replace-webpack-plugin - npm). These two things combined give me an unlimited license to spread detailed logging messages everywhere without causing any console clutter or performance hit. And after nearly two years of continued incremental development on this game, I can say this has been really invaluable for quickly investigating and fixing issues in code that I didn’t touch for a while. This was the first project I used this library on and now I won’t start any new one without it :grin:.

Edit: To really adress your question though, I have to mention I don’t print stack traces by default in those logging messages. When I need the full strack trace in whatever place, I will just manually put in a temporary console.warn to get a full stack trace instead of the plain debug message.

1 Like