Oblikvo
Esperanto for slash, because our game is a FPS. 'Why slash?', you ask. Can you slash someone? Nope, but it sounds cool.
Our game is a multiplayer FPS, written using socket.io and p5js because our teacher sure lóves shitty languages.
Development
This project uses the amazing Bun runtime coupled with a Bakefile
, a easy Bash-based replacement for Makefile
. To build, run bake
:
bake setup # Installs dependencies
bake
The bundled, minified JS + sourcemap for the client will be built into the dist
folder, along with any static assets.
To start the socket.io backend server, run:
bake serve
Additionally, our Bakefile
also includes a snippet for starting a live server with code reloading. This automatically builds the frontend and watches for code changes while also running a backend server. This is the preferred mode to use in development.
bake dev
TODO
- [x] Menus
- [x] Lobbies
- [x] Nicknames
- [x] Shooting
- [x] Hitting
- [x] (Re)spawning
- [x] Health
- [x] Non-flaky collisions
- [x] Launchpads
- [x] Shader
- [x] Camera effects
- [x] Chat (without chat)
- [x] Start button disabled with < 2 players
- [x] Game end with play again button & podium
- [x] Leaderboard
- [ ] Level design
- [ ] Sprites
Chat messages:
- PeculiarPinguin joined (total players: 5)
- Robijntje left (total players: 4)
- PeculiarPinguin is now SeanTheGamer
- SeanTheGamer hit BumblingBummblebee
- SeanTheGamer slashed BumblingBumblebee
- Kevuit hasn't been hit yet!
- FissaMetTissa hasn't been slashed yet!
Design ideas
This game was built as a project for computer science. Our aim was to push ourselves to see if we could defy all expectations and build a 3D multiplayer game :)
We tried to build a game that we would like ourselves. That means pixelated retro graphics, fast pacing and multiplayer fun. It also means that the game should be simple and easy to understand for new players. The premise is simple: here's a laser gun, have fun!
We might add multiple game modes later, but for now the only game mode will be "slash". In this game mode, the goal is to get the most kills ("slashes") within a set timespan of about three minutes. With one caveat however: every time you respawn, your stats get bumped a little bit; this way the power imbalance between new players and experienced players will be a little more fair, and hopefully it will make the game more fun.
Matches are intentionally short and chaotic by design. Short matches make people go "let's play another one". It creates a rapid feedback loop, and most people like rapid feedback loops. This makes the game fun, which is one of the lenses of game design that we needed to implement in our game. We also incorporate the lens of surprise, by making the rounds unpredictable and chaotic.
(Other) ways in which we incorporate the element of surprise
- By making a 3D game without any game engine, using a framework that was designed for making interactive/generative art and visualisations.
- By making the game multiplayer, we might surprise our teacher. Same for our exotic build pipeline I guess.
- By making the matches short and chaotic.
- The main game mechanic of our primary game mode ("slash") is surprising.
- The name is weird and goofy?
Another game mode we might consider is "teams", in which players could make teams themselves beforehand.
We wanted the game to be easy to pickup, but also easy to put away again. We want to build something that is fun, and brings people together, not something that nurtures addiction.
The game should be played with at least 5 players. They can join in two ways:
-
By playing the game in their web browser. It automatically connects to the backend of the server it's running on. Only an invite code is needed.
-
By playing the game via desktop client (built with Tauri). Think "Open to LAN" functionality. When opening your game to LAN, it presents you your local and external IP address as well as an invite code. You can send these to your friends and they should be able to join by simply entering them in their respective clients.
Oblikvo should support multiple input methods. Most notably, mouse/keyboard, bluetooth controllers (such as Xbox, Playstation and Joycons) and on-screen touchscreen controls (because we expect our playerbase to primarily play via their smartphones). This makes the game accessible to a wide audience :)
One of the important design goals of the game is hackability. You should be able to create levels yourself easily. We're thinking about encoding levels in a multidimensional array:
1111111111111111
10000000x1111111
100000000x111111
1000000000x11111
10000000000x1111
100000000000x111
1111111111111111
1111111111111111
1111111000000X11
1111111000000X11
1111111000000X11
1111111000000X11
1111111000000X11
1111111111111111
Above is an example level layout with two floors. A 0 indicates an empty space, a 1 a filled space, and an x a launchpad. An X is a launchpad that launched you two blocks high. One block is a little higher than the height of the player.
The process of making levels should be further automated using a level editor.
The game should aesthetically be similar to a classic retro game, like Wolfenstein 3D. We're thinking of doing that by adding a shader to the end of our drawing pipeline that pixelates the video buffer. We also want items and entities to be 2D planes that are always oriented towards the player, similar to enemies in retro games and dropped items in Minecraft.
There should be as much feedback on player actions as possible. Think FOV changes while walking, head bobbing, visual knockback when firing a gun, red vignette when health is low, camera effects when being hit. This makes the player feel more in control over the game. It also helps immerse the player in the game (I think). And it looks cool too.
Another cool idea is that there would be no UI. It would be part of the environment. Think health bars on your gun, enemy health overlaid in a gradient over their sprite, ammo stats scrolling over the walls. (This is surprising too?)
Project structure
Interfaces
These are used for defining data sent back and forth between the server and client.
JoinPayload
: some data for rendering the lobby.StartPayload
: the world state as pushed to the clients when they first connect.UpdatePayload
: the world state pushed to the clients every tick (60 times per second). If this seems inefficient, that's because it is :)Entity
: positional and movement data for an entity, contained within thePayload
object.Block
: the positional and color data for a block, also contained within thePayload
object.
Implementations
The entity interface is implemented on both the client and server:
Entity
(server) contains logic for physics calculations.Player
(server) contains logic for moving players.Entity
(client) contains logic for drawing an entity.
Serialization
The other interfaces are used for serialization. The World
state on the server serializes to an Payload
, which is then loaded into the World
on the client. (The same for Level
, which is part of World
).
World
(server) contains logic for running the game, calculating physics etc.Level
(server) contains logic for building up a level.World
(client) contains logic for drawing the world.Level
(client) contains logic for drawing a level.