After a lot of work, the next version of WebDungeon has been deployed here. Whilst to my disappointment it looks too much like the old version, under the hood it is radically different – it’s running a real game engine. So what’s new under the hood?
- A proper game loop with a fixed number of frames per second
- Dedicated collision detection
- Dedicated rendering and movement modules
- Scrolling when the character hits the screen edges
The best thing about the imaginatively named ‘GameEngine1’ is that it is modular, and all the effort over the last few weeks will mean much faster iterations from now on, as I update single modules without (hopefully) breaking the others.
There is currently no interaction with my web socket based Game Server, as I am focusing on single player issues for now. Here is what each module does, and the trick is to minimise dependencies between them so that they are as isolated as possible. Each coloured box is a Dart library.
Note: For all experienced developers out there, this is software development 101 and not that interesting. You have been warned, so don’t blame me if you read it and it gives you a rash.
This does all the drawing, of the both the dungeon and all characters, items and monsters.
Used by the Movement module to see if a character or monster can move into a square.
The game engine forwards all keyboard input to the movement controller, which then moves the character around on the screen.
You can guess what this does. This one is a TODO.
Every dungeon, sprite sheet and image is managed by common resource manager, which loads these from the server, based on a YAML manifest file which look something like this:
- id: 100
name: wall palette
- id: 101
name: zoom in
- id: 102
name: zoom out
- id: 103
name: rusty sword
- id: 104
- id: 105
This makes life simpler for all code that needs an image. These other class just ask the manager for a named resource like this:
ImageResource rustySword = resourceManager.imageByName(‘rusty sword’);
No fiddling with paths, file names or any other messy file IO. It just works, and caches things too.
In the truest ‘Not Invented Here’ spirit, I wrote my own resource manager, as it does things EXACTLY the way I want. There are dart packages which do similar things, but I am happy with mine for now.
Game State Model
This is data model for the game, and had objects like Dungeon, Character, Monster and Item which are all logically linked. No drawing or anything else happens here, just the state and location of everything for a particular game. This is the stuff which is saved with a ‘Save Game’ button.
This is currently managed in the browser code, but eventually will be centrally managed by the game server, updating each players’ browser game model in multiplayer mode.
The Game Loop
The game loop was a bit tricky to do, especially figuring out how to render at a fixed frame rate, and in the right order, and finally how to draw the character movement cleanly. I used a dart game loop library which simplified things enormously, and got it to render 20 times per second. The game loop library (it’s called game_loop BTW) keeps track of elapsed time, which I use every time requestAnimationFrame calls my render method:
- Has enough time passed since last time I drew everything?
- If yes, then draw everything again
- If no, then do nothing
Before I tried this, I tried adding timers to the game loop, and whilst this worked, my peers at StackOverflow advised that this is not the correct way to do it, as it bypassed the screen drawing process.
Drawing The Character
Before I settled on the fixed frames per second approach, I tried to (prematurely) optimise rendering to only those blocks that needed it by storing a list of ‘dirty’ blocks which would be redrawn on the next frame, leaving everything else untouched. This seemed like a good idea – fast, only a fraction of the dungeon is redrawn in each frame. What could go wrong? Well, lots apparently. Maybe for people with a black belt in game-jitsu this is feasible, but I had issues in getting it to work. After getting a frame rate of 20FPS or so to work, I now just do the simple thing and redraw the visible portion of the dungeon and then the characters sprite on top. This worked, didn’t look excessively bad, and so I went with that. Still not entirely satisfied with the result, but will live with it for now.
Also, the character moves (and is redrawn) at a specific velocity measured in pixels per frame, and I then convert the character’s position in the dungeon from pixel coordinates to dungeon coordinates. This looked better than moving the character 1 block (about 24 pixels) at a time.
Since the sprite has 6 frames and the frame rate is 20 or so, I redrew each sprite sheet cell several times before transitioning to the next sprite sheet cell. This prevents the character animation running too fast, and instead at a more leisurely and realistic pace. I found some good, advanced articles on this sort of stuff and plan to read up to see if it could be done better.
Next I created a dungeon which was bigger than the viewport (the 20×20 part of the dungeon you can see) and had a think about scrolling. After some experimentation, I went with adjusting the view once the character got near the edge of the screen. This works and currently moves a few blocks at a time, which is a bit jerky. Room for improvement there, for smoother scrolling in the future. Once again my ‘just ship it’ mindset took over.
This was (suspiciously) kind of easy, as each position in the dungeon can be queried to ask ‘can I move here?’. If the answer is yes, then the character moved there. Works OK, and will work with monster collisions as well, so chalk that up as a quick win.
I will add a monster to the screen and get it to fight my character. To keep things simple, and keep the updates flowing faster, I will keep the monster in one place and just work on the combat.