The journey of Multiplayer FPS game with PlayCanvas

Today I’m going to talk about MiniRoyale.io and how I made it. MiniRoyale is a multiplayer FPS shooter with battle royale mode.

The game has been released at March 4, 2019 and since that day, 630.000 unique visitors have played game. It still goes well and we still update game with new features.

The game has a community in Discord and they request new features, they report bugs, they help a lot! Without them, the game wouldn’t be like that. I also want to thank them for their support.

The game has 10.000 daily users and 100-200 avg. concurrent online users.

Game has 2 different maps, and 3 different scenes including maps. We have a login screen, map1 scene (North city map) and map2 scene (South city map).

Here is a screenshot from login screen :

As you can see, it has a 2D screen, which is the coolest feature of Playcanvas. You can create cool UIs with this feature of editor that is not possible with HTML. The UI will be rendered in canvas and all mouse interactions will continue to work! It’s like new and better way of DOM :slight_smile:

The background scene is 3D and you can see some animations while you get ready for matchmaking.

In this scene I have a script that allows me to create DOM input element. I needed that because Playcanvas doesn’t support input and select field yet. Users need to enter their usernames, so I created this script.

Here is a screenshot from my input script. So basically, it allows me to create DOM element with this element’s position and size. I can define an ID to use it on scripts later and for selection, I can add options in that element…

As you can see, input script has some attributes that works in Playcanvas editor. This is another cool feature of this editor. You can simply create an attribute field with using “attributes.add” function. Here is a screenshot from input script of mine and how it creates these fields in Playcanvas editor :

I uploaded input script here : Playcanvas input · GitHub

When you press “Join Match” button, it calls a function that will load other scene. But before that, it saves your variables in localStorage, so it could connect correct server IP when scene is loaded.
I also use these variables on your next visit. It remembers your preferences.

When It loads map, on each map, there is an object that makes all network connections. It has lots of attributes to run game, overlay and players. Here is a screenshot from it :

At the beginning, it shows map selection entity, so users can select their deploy position. It’s also 2D scene. The locations are static and they are connected to spawn positions in 3D world. When you click on that button, your deploy position is being sent to network script and is being broadcasted to all users. If there is a signal from server about another user’s position, then network script disables that button. That’s why network script has all authority. Here is a screenshot from position selection buttons :

It stores flags in array type attribute.

It also enables helicopter entity, so we could see cool animation before we jump to city :slight_smile:

You know, even for helicopter, I used scripts to rotate it’s rotor. You don’t need animation for this kind of animations, it can be done by scripts and their update functions.

There is also another cool thing that I would like to talk about. It’s border. As you may guess, it’s connected to network script. But it’s a light! I used spot light and define a range and angle to make it like circle. Its y position is 500 and each tick it comes closer to map. That position change provides a visual, that is similar to CS:GO’s death zone mode.

Here are two images for different positions :


I use circle distance function to check if you are in circle or not. I can also start zone thing in random position with changing x and z.

Then game interface becomes enabled and player entity with that change, we start to play game. When I start this game project, I started with Playcanvas’s FPS template. Here is a link for template :
https://developer.playcanvas.com/en/tutorials/first-person-movement/

You can build everything top on that. It provides all essential stuff like locking mouse, keydown and keyups, collisions and physics.

As I mentioned before network script runs everything. For other players, it clones an enemy entity and enable its script, so he could emulate opponent’s actions.

Enemy entity is an object that contains a soldier model, script and weapons in it. Weapons will be disabled at the beginning and when user takes a weapon from loot locations, network script will enable a weapon in this entity element. With this way, we only show one weapon that is attached to them.

I used findByName to find soldier model’s hand in node tree and attach my weapon model to it.

We also have a player control that provides FPS interface. It only has hands, we don’t need rest of its body. This hand model is more detailed than enemy models, since it’s FPS, it’s important to keep detailed near models.

The player model has also same structure to hide and show weapons in hand. It’s also attached body’s arm, weapon and hand move in same structure.

It also has lots of sounds on them for different cases.

All actions, like keydown, key up, mouse position changes, will be transfered to server. And after some calculations, your actions will be rendered as an enemy on other player’s clients.

I won’t go too much detail for networking, since it’s more complicated thing. But maybe, we could talk about it in another post.

There are some other interesting thing I would like to mention. It’s map. It is a huge image and its position is connected to player’s x and z coordinates.

But I used mask feature to show in container. Here are two images, that shows how map exactly looks like :


When I disable mask functionality, we see whole map and border :slight_smile:

House models don’t have doors and glasses, they are actually in Playcanvas’s entity structure. We created one prototype for house and cloned it. They all have same scripts and structures. Doors have their own scripts, glasses have own their scripts etc…

For network side, we simply use their guid values. All of these glasses and doors have unique guid values. And guid value is generated while we create map in editor. We send guid value and state id like, open or break over network. Then all players see if glass breaks or door opens. When information arrived to other clients, their network entity finds object by guid and trigger same functionality on script.

Without Playcanvas editor, this game wouldn’t be possible. We worked on this project with two other friends of mine at the same time. While they design map, I worked on coding stuff, and we were able to test it without uploading and deploying to anywhere.

I want to thank to PlayCanvas for all their effort.
I’ll try to share more information about networking, optimisation, account system, leaderboard soon.

21 Likes

Awesome work and thanks for the write up!

I like the use of a light to quickly create a danger zone.

What are you using for the backend? And how are you coping with different latency issues with players (who shot first).

Are you making use of batching as well to help with performance?

1 Like

This is nice though could be better optimized for lower end devices (it works fine on regular gaming computers), but maybe i’m wrong as i may have played an older build of it. One could get away with not using physics (for a basic FPS). I’d had used my own collision framework to handle custom collision clipping via my own Dynamic BOunding VOlume Tree polysoup structure (structure similar to the Physics engine for triMesh, but without the actual physics engine), which I had been using for all my projects that doesn’t require full Bullet Physics and just want optimized Quake clipping. (Actually, the main reason why I use my own collision framework is because the default Playcanvas BulletPhysics doesn’t handle walking up steps very well to begin with.)

Doors and windows cannot be static batched unless you use Default Playcanvas scene editor Dynamic batching which does has slight per-frame overhead (I did check, Playcanvas engine code will attempts transform update checks per frame for each dynamic batched item assuming the possibility of those objects changing transforms per frame, again, still acceptable as long as you don’t have too many of 'em which I won’t mind either), (though it has limitations to dynamic removal/addition) of items, unless you use a custom repeater setup to avoids that inherant Playcanvas engine limitation and allows for dynamic removal/addition of repeater objects without having to recreate the entire batch. (see my LOD tree forest/terrain example for repeater batches that allows for dynamic removal/additions…). Anything else repeatable, could use static batches if they aren’t movable/destructible. WIndows, doors and trees could have LOD, but generally i think they are low poly enough to not require them (processing LOD has overhead anyway and the models aren’t that hi-fi to really need LOD swaps). So, maybe you already have dynamic batching for doors/windows, but the caveat would be: to “kill” a window, you’d have to move it to somewhere else “non-visible” under the default Playcanvas scene setup to avoid rebuilding batches. Since there aren’t a lot of windows/doors in the map, whether it’s batched or not would make hardly any difference at all on desktop PCs.

Assuming you didn’t combine materials into same mesh instances for base Model’s door/window stuff, you could actually fish out door/window meshInstances within your Model instead (and hide/modify/manually transform them by code) instead of having it as entities in the scene. But having it as entities does make it easier to track from the editor itself or add yr own entity scripts… (and do Dynamic batch groups without needing any code). Since it’s a small map and not an actual PUBG/Operation flashpoint island size map, etc. it’s not much of an issue either to just use good old entities and just baking as many other static things into a single model as pre-built models or static batch groups.

So, i guess from a coder persective, there are other more improtant priroities like HUD, multiplayer, and such for this game. For high-end desktop computers, draw calls/polycounts aren’t that much of an issue anyway especially for the size of this map which is lo-fi to begin with.

I’m more interested in how you do the particles though. Like some of the bullet impacts. Is it regular Playcanvas particles?

You should put this up as a public project on Playcanvas so others can collaborate/improve on it, or include their custom maps.

1 Like

I use NodeJS and socket.io for backend. There isn’t proper functionalities in backend to calculate who shot first but, it basically checks timestamps to check who shot first.

Yes, the project uses batching to improve performance. Especially in our web design, we tried to merge things and group them with batch functionality.

1 Like

Thank you for your detailed feedback and comment.

To be honest, your comment helped me a lot and pushed me in something that I couldn’t even imagine. I’m currently using ThreeJS on another project and since you’ve provided too much information about optimisation, I started to search and understand what can I do to optimize my new game.

After making detailed research, I understand some basic things about GPU rendering and dynamic batch groups. And applied some of these optimisation techniques to my new game and got 60FPS even on old Sony Vaio (2010 computer).

For this project, we’ve applied batch groups to optimize it little bit but it’s surely not enough for most cases, since it works in browser. I know there are many things to optimize game but for now I though it’s enough for most computers and leaved it in that state.

For particles, we created particle animations with Playcanvas editor and yes it’s just regular particle system of Playcanvas.

Again, thank you for your comment.

1 Like

Cem you really should make this public project so that people who wanna make games like this can see how you did it👍

1 Like

Given that the OP is making money off it, I don’t think they will unless the game has run it’s course.

:unamused::thinking: I see guess I have to do the samething I did 5 months ago more RESEARCH

Maybe you could do up some Lite version as a Playcanvas Public sample project to let others set up custom maps . And maybe another version that is client-side only without the server side dependencies for client engine experimentation. Then track your forked projects, etc. (is that even possible on Playcanvas?) and see what you can assimilate.

You can check my proejcts on some of the custom hacks i did on Playcanvas to allow for buffered repeater/resizable batchings and Skinned Model animation Batchings (and things like integrating my own custom BVH/hierchical frustum/collision culling/LOD swapping/etc.). BUt for yr first person shooter, it may only slightly benefit with manual set up dynamic repeater/resizable batchings (a bit tricky to manage them by code…),as you have to modify the indexed matrix pallete arrays directly instead (and set the number of triangles for the quantity of the repeater batch) on your own.

1 Like

it wasn’t clear to me at least but what were the software you used to create this game and which software did what for the production. just wondering because I have no idea what softwares to use to make a .io game.

I appreciate this post so much because it truly shows the power of the engine, I started a multiplayer game in this engine weeks ago, took a break and tried to rebuild it in other engines when I was stuck yet ended up coming back to PlayCanvas due to how easily multiplayer can be implemented considering an authoritative network/server.

As someone who didn’t appreciate JavaScript as much, PlayCanvas motivates one to continue the project and learn more of JavaScript’s concepts. Everything is just so simplistic and often just works when you least expect it to.

In conclusion, I managed to tackle a lot of things I never have before my first go around in PlayCanvas. The fact that you can build 3D games in the browser attracted me initially, learning to make successful network games lured me into becoming a community member for sure.

The knowledge of guid values may be the fix to an issue I was facing syncing map state, your approach just made much more sense than other articles and resources I was referring to. In addition, I attempted embedding other scenes and having players travel between them yet I was unsuccessful the first go around. Seeing that you got it to work pushes me to give it another try rather than having separate builds per map which is inefficient. Great stuff :sunglasses:

1 Like

This is such an awesome post for who wanna start multiplayer game. Recently I also start creating multiplayer and socket.io really helpful in send and receive data in order to achieve real time method. My game is only for two player per session but that is impossible that only client able to join game at the time. I trying to create an custom room like what u did in MiniRoyale.io (create custom session) so they able to join room in another space.


it will automatically create a random link and your friend able to join through this link. I try to research on this function and already stuck here for whole month. I try using socket.io namespace and join room function but the output not that I expected and I think I lack some kind of element.

If u see this post can reply me what the tech slack that u use to create this random link custom session or any tip that I able to explore it. Thank you so much if u able to reply. Btw I love this game :heart_eyes: :heart_eyes: Thanks for create this kind of game that is fun and addictive.

1 Like

@commention
Do you use another scene for the menu or just have it in the game scene?

hey love this game! just wondering how you got the socket.io server working. i am trying to do the same thing for my game but i can’t figure it out.