At the start of this month there was a pair of simultaneous jams, 7DFPS and proc-jam, both taking apart over approximately a week, and with very relaxed rules, essentially being "make something in first person" and "make something that makes something". I've had a bit of a dearth of personal dev recently (2020 huh?) so decided to take some time off work to make something for these jams.
Play Vorld Decay.
A procedurally generated multiplayer puzzle game made in WebGL, the code is available to view on github
My goals for this jam were to create a game with:
- First person exploration / puzzle solving
- Networked gameplay with the ability to play when the server is offline
- Procedurally generated voxel levels
Whilst the rules on 7DFPS were extremely relaxed I decided I'd only do investigatory tech work before the jam dates and make the actual game during the dates, quite a few worked on their games both before and after the jam dates, which I think it's great that this is allowed but timeboxing really helps me keep focus and not worry about if I could be doing something else more productive. So I spent a few evenings in the week before the jam making some proof of concepts and firming up / adding features to Fury my JS game engine / library.
First up I did some research around networking games using web tech, reading around about WebSockets API and WebRTC. Web Sockets essentially provides a TCP connection between a client session and a server, allowing for two-way communication. Web RTC on the other hand supports a lot more of the capabilities you'd want from a networking system, UDP data streams as peer to peer communication which would allow for client host games - great if you're concerned about costs and scaling. However it's also *quite* complex and you still need a signalling service in order to set up WebRTC connections, there are many ways to do this but the most common and most achievable for me is to use a Web Sockets server.
So first up was a web sockets proof of concept! As I run this website server on Node.js I picked µWebSockets.js for the server side implementation and made a very simple chat app. This took a couple of evenings on its own between the server side app, the client side websockets code, deployment and testing on my VPS. This was pretty painless, and as I've seen plenty of games implemented perfectly well with TCP and I'm not concerned about needing to scale this jam game, and with the ease of setup I figured I'd would just use web sockets for networking. Having decided this I made a simple transform sync test where you could move squares on a 2D plane, which could form the basis for the jam game.
This allowed me to move on making Fury a bit more than a renderer (really a glorified wrapper around the WebGL API), the obvious missing feature for making a first person game is some collision code. As you might have seen from my terrain experiment and fort generator I've done a little bit of work with Voxels in WebGL, so it made sense to stick with this for this jam game. The bonus of course is that Voxels are all axis aligned boxes, which means I could write some serviceable collision code just using AABB.
Having implemented AABB it was then pretty easy to add frustum culling to my scene module, which is a pretty basic feature I've been meaning to implement for a long time (read years). Interestingly in almost all my demos it's actually faster to not do any culling CPU side, but perhaps given most of my demos are tests to make sure I'm not unnecessarily rebinding shader buffers, this isn't that surprising, however given the cost of recalculating AABB on dynamic objects I also put in a sphere / frustum check option for said dynamic objects, it might end up rendering them slightly more often but until I implement object oriented bounding boxes it'll have to do!
Once the jam actually started, it took me a good two days to incorporate all of the tech work I'd done into a new project, as I wanted to be able to run the game server logic locally or on node it required some actual planning of folder structure and then splitting out the game server logic from the web socket server logic, and finally setting up a local relay if the server wasn't available to just pass the messages directly between a local game server instance and the game client.
Having converted from 2D to 3D and added rotation to my synchronisation code I added the most basic game mechanics I thought I'd need for the puzzle aspect of the game. So you would pick up coloured cubes and place them on a console / panel (another cube) which would allow teleporters (different coloured areas on the floor) to activate and send you somewhere else in the world. Without any proper lighting system and it not being particularly important towards my goals I added some simple depth based fog to the shaders and made it black as an approximation of a dark environment, and make the full colour shaders used for the coloured cubes and players have a lower fog density so they would appear to glow.
The next day or so was spent bringing in the voxel code from the fort generator and adapting it so it wasn't reliant on web workers for the voxel generation stage, so I could use it on the node server, although I kept the web worker for the meshing code as that would only be required on the clients. I also had to adapt the collision code to work against voxel chunks which have a set of implicit bounding boxes rather than explicitly defined bounding boxes, followed by at least a full day making sure you couldn't get caught on the edges of boxes when sliding along them.
Somewhat aware that different coloured cubes isn't really a very accessible way of making different types of keys for your puzzles I took a detour into vertex colours and creating assets in blender, to see if I could make different shaped keys to go with their different colours. However after spending a day on this, and having problems getting the scale right of the generated assets and not being particularly happy with how vertex coloured assets looked in a game with textured walls, I decided to shelve this as it wasn't one of the headline goals and it felt like it would take a day or two more to resolve, and there was no procedural generation yet!
So the last couple of days was spent making that procedural puzzle generator, it consisted of first working out a good way of thinking about key / door puzzles and how you can prevent players getting trapped which might be fine in certain types of games but in a jam game people are unlikely to play more than once, not so much. Having done this and come up with some terms to prevent myself getting confused, the next thing was to work out something akin to a markup language (or more actually JSON which conformed to a certain set of rules) to describe the different valid puzzles, the idea being the server could generate this markup and then send it to client to generate actual geometry and matching entities to talk about. If you'd like to see what that looked like you can see the code which has quite a lot of comments on github.
The final day I spent categorising the cases I had used to test the generator, adding logic to mutate them without breaking them and creating a very simple algorithm to chain the puzzles together so you would start with easy puzzles and finish with a higher chance of the hardest puzzle case. The hardest puzzle you can find is a pair of overlapping room loops with the exit keys spread between the loops, I had considered nesting these overlapping loop pairs inside higher order loops but it felt like perhaps it might be better if I didn't at this stage as adjusting the generation code would likely result in bugs and I might be pushing the limits of what could be considered fun.
Having achieved my stated goals I deployed the game and made the itch submissions on the morning of the final day and got some very useful testing and feedback from the discord that allowed me to fix a couple of minor but annoying bugs, not to mention testing in other browsers.
I'm relatively happy with the final product, it may not be particularly well polished as a game, but I've added features to my personal JS game libraries and explored web technologies which have been sitting on to do lists for years!
The importance of knowing your tools was re-enforced, I made significantly faster progress for having done some investigations the week before, but my lack of experience with creating assets for use in game in Blender caused me to lose a day to what was a noble cause but ultimately had to be cut to mean the stated goals I'd set for myself. Figuring out a workflow for building assets and making sure they'll be a good scale, as well as have a consistent look in game is something I clearly need to practice.
I didn't even attempt sound or music because I had no familiarity with any creation tools nor any chosen libraries for using them effectively in my web games, so if I want to do this in future I should get to grips with some!
Figuring out deployment early in the jam allowed me to test with other people at multiple points during the week and which helped me spot and prioritise issues, and I'm sure that it would have been a far worse experience had I not done this!
Finally, something I always knew was the case but I did it anyway, making something multiplayer takes at least twice as long to do, not least because you'll always have to test multiple approaches. I was very happy with the project structure where code was either common, client only or server only, and local relay ran the game server locally but still completely separately which meant that "client hosted" games acted almost identically to remote server hosted games. That said the only games I'm aware of where anyone played together was when I waited on the server for people to join or organised it myself, so clearly there was little value in this effort for those that played the game.
I find the idea of cooperative multiplayer games far more exciting than purely single player experiences and motivation is an extremely important part of personal project, I'm glad I made it a goal to prove out a web tech way of doing this for these jams. However for future jam games, if the goal is going to be the more classic "as polished a game as you can make in the time", or if it's to make a complex interesting procedural generator, I'll probably choose not to do multiplayer and give myself that extra time.