– Disclaimer: I’m no programming master, I’m only a self-taught student. I’m sorry if any info here is wrong and I won’t be getting too much into the actual room creation. I’m also not responsible if your computer blows up or anything. –
First things first; if you’d like to follow along and read the code, you can download the .capx here. You’re free to use it however you want, but if you do use it for anything, if possible, credit R0J0hound and I (AndreYin).
Optimizing R0J0’s random room generation code to accept Tilemaps
I’ve been studying a lot about random room generation and the new multiplayer object in Construct 2. It all began early this year, when I found this example by R0J0hound on the Scirra forums. At the time the Tilemap object didn’t exist (or did it? If it did it was really archaic). R0J0 basically had 30 different layouts, each with their own objects, then he went on each layout at the beginning of the game and stored each one on an array, together with some info of which exits it had.
After that he generated the map using a small piece of code that’s just too much for me, since I suck at math. He used a small mini-map to create the random room layout, and based on that, he created the “rooms” (which were just a bunch of objects placed like a room) themselves.
This code was already pretty amazing, but I decided to try and optimze it a bit to accept Tilemaps instead of objects. This would increase performance a lot, make it a little bit easier to mess with rooms and things like that.
The way it works (apart from the actual room generation) is pretty simple. The game starts on the “init” layout and it has every possible room, each on a separate instance of the Tilemap object. Each instance also has 4 boolean variables that correspond to their own exits. The original code from R0J0 has a separate text object that held these vars. I also ran a loop on each tilemap and check if there are tiles on the exits; then I change the boolean. This is much better than having to set each variable manually every time you create a new room.
Also, on the original code R0J0 stored all the walls on an array for each room. I decided to keep using one array for each room, but now it only stores the .TilesJson from each tilemap on it. Each array also has a variable that keeps which exists the room is supposed to have. This probably could be a little more optimized.
After all the rooms have been stored on different arrays (which could probably be optimized by a lot if you used just one array maybe?) and the player has typed anything and pressed the button, the game goes to the next layout (game).
At the “mapGen” group (which turns on once if the player is the host) is where the magic happens. This first part of the code is R0J0’s; First there’s a small loop using a variable that repeats 30 times, and each time he creates a new room. Using some math he was able to generate a small maze of rooms and doors, and then he creates a mini-map based on that. Then he proceeds to check which doors are overlapping which rooms, picking a room where the “doors” variable on the array is equal to the doors on the actual room that was generated. I’ve left some of R0J0’s comments. For each X element on the array (that meaning, for each “room” on the array), it will create a new tilemap based on the mini-map room position, then load the TilesJSON based on which exits the mini-map has.
There’s a small piece of commented out code here; this is a loop that checks if the tilemap has a certain tile on it. If it does, it erases the tile and places an object on it’s place. IIRC it works and it could be useful creating objects on each room, so I left it there. I haven’t tested it in a while though.
When the room creation is done, it adds 1 to the temp. loop variable (making the room generation stop), then adds 1 to the “checkMapDone” variable (meaning that the map has loaded).
The next events are only important to the multiplayer part – if the map has been loaded, since ONLY THE HOST can generate the map, it sets the “howManyRooms” variable on the player to the current Tilemap.count, and sets the “isHost” to 1, meaning that player is the host. After that there’s a small loop – it gets the state of each tilemap object and saves it as json (which is a string containing all the info about that object – it’s position, the tiles it’s supposed to have, etc) together with an asterisk. It will end up looking somewhat like this:
This is enough for the map to be created and the game starts. After I got this down I decided to try out implementing the mutliplayer object to study it a little.
Implementing the multiplayer object
I have to admit, I was really scared by the multiplayer object. I’m not a good programmer so I thought it would be hard as hell to implement, but at the end it wasn’t so bad. Just to be clear, this part is probably not optimized AT ALL; I’ve used a few workarounds to get some things going and it probably could have been made way better.
I’ve used the “Ghost Shooter” multiplayer example as a base to get it going; created similar groups to the ones on it and much of the code is the same. I’ll try to explain from what I understand.
First, the signalling. This tells the server which variables it’s supposed to keep an eye on. I’m seriously not sure if there’s a better way to do this since the events say “client input value” and apart from the actual input I’m also using the signalling to store how many rooms have been generated. The most important thing to keep in mind here though is that we’re asking the server to sync the “player” object’s position, then it’s inputs. This way the server can tell both the movement the player should be making and the position he’s supposed to be at. After that we contact the signalling server.
When connected, we use the username the player first typed in the first layout. You can also have different game rooms if you’d like so players could have specific rooms to play with whoever they wanted; on the “init” layout there’s a text box that I’ve left out of the layout.
Before we keep going I forgot to mention something important; there are 3 really important global variables at the beggining of the event sheet. They’re GAME_NAME, INSTANCE_NAME and ROOM_NAME.
The GAME_NAME var tells the server which game you’re running. IT’S REALLY IMPORTANT THAT YOU CHANGE THIS VARIABLE if you make any modification in the game at all. Otherwise people with other versions will be able to join your modified version and things will not go as expected. Yes this has happened to me. The other 2 vars are pretty simple to understand.
Continuing, the game checks if someone has entered the game room. If they’re the host, it activates the “Host” and “mapGen” groups, sets the player variable “peerID” to your own ID. If the player joining is not the host, it only activated the “Peer” group and destroys the player object that currently is on screen – this is so only the host has the player objects created and synched (I think?).
When the “Host” group is activated, whenever a new peer connects to the game it will create a new “player” object and set his variable “PeerID” to the current PeerID of the player that just connected. He also associates that player to the current PeerID of whoever just connected. This way that peer will have control only over that player object.
Then comes the controls. There’s a small variable called “changingScreen” that is supposed to see if the player is changing rooms, and if it is, it’s supposed to make him stop while the camera lerps to the right place, but this is currently not working. I’m not sure why, either.
After that it’s pretty simple; since we’re on the “Host” group it checks if your own ID matches one of the players on the game. If it does and you press a keyboard button, it simulates the 8 direction behavior.
Under that, we check if the player object does not match our own ID – this means that it checks for each player object that isn’t the host. If it isn’t, it fetches the “inputs” variable from the current player.peerID directly from the server. Then it compares it to see which movement the peer should be doing; it’s a simple boolean check – if the input is 1 then the key is being pressed. If it’s 0, it’s not.
For a quick reference, in this case we’re using 0 for “left”, 1 for “up”, 2 for “right” and 3 for “down”. So for instance, if we check if “input 0 is 1” we’re checking if “left” is being pressed.
Onto the “Peer” group – whenever a new “player” object is created (by the host), we set it’s peerid to the current ID of the peer that just connected then associate that object to that ID. We also turn on “input prediction” for the player, but I haven’t messed around with that too much yet (this is probably what’s causing a bug where peers won’t stop moving after entering a room).
We check again if the player is changingScreens, and if he isn’t, on every client update where the player.peerID is the same as your own ID (you being the peer), we check each key to see if it’s being pressed. The way it’s set it means that if LEFT is pressed, it sends the info that “input 0” is 1 (which means that “input 0” is being pressed). Else, you have to specify that if it’s not being pressed, “input 0” is 0. This code is straight from the Ghost Shooter example so I’ve left the comments. We do this for each key, then we send this info over to the host using the “set client input state” to match the current input being pressed. There’s a comment here that explains why we’re going to compare the player.inputs and move them accordingly.
The common group is where I’ve set the tilemaps to load on peers too. When a new peer connects to the room, the Host broadcasts a message; this message contains the state of every single room that was generated on the “mapGen group” in JSON format, in a single string, separated by an asterisk. Remember the part where we set the host player to receive all the rooms on his “loadrooms” variable? That’s the message that’s broadcasted. Broadcasting a message means that every peer in the room will receive it.
Under that we check if the “loadRooms” message has been received, if it has, the global var “currentRoomsAsJson” is set to the message string, then a small loop creates every room and sets it to the .JSON state using TokenAt and the asterisk as a separator.
That’s pretty much it! I won’t be getting too much into some things but hopefully this is enough.
Again, you can download this .capx here. You are free to use it however you want, but if possible please credit R0J0hound and me. You don’t have to, but it would be nice.
Thank you for reading!