Jump to content

Multiplayer tips and resources.


BrokenSkyDev
 Share

Recommended Posts

Hi All,

 

I have made a simple game before in Phaser, but i am now looking to create a multiplayer game using possibly eureca/socket and node.js.

I know how to set up a simple server, it is the splitting game logic up to be able to use multiplayer i currently do not understand.

Specifically i am going to have one player as a Human which will be the host and then the new client will be a zombie, both will be in their own files, the game will be very simple just the zombie chasing the player and when he catches him the game will restart, this is just so i know how multiplayer works.

 

Most examples i found online, have all the code in one file and usually only have one entity and do not go into much detail about how you keep both clients from controlling each others character and updating movement over the server, if anyone has any advice, examples of good references, it would be very much appreciated.

 

Thanks.

Link to comment
Share on other sites

What you're suggesting is not easy, hence the lack of documentation.

 

The simplest solution is to have the server maintain all state. So you move your character, send an update of its position to the server, which then holds that state (lets say its the state of the game world, which, in this example is perhaps a tile map and a list of characters and their data) and distributes the entire state of the system to all connected clients.

 

So, each tick your client gets a state from the server and renders the entire game state.

 

However, connections being what they are you'll probably have to be slightly smarter about how you diff state i.e. what changed and who needs to be notified?

 

In essence, you could get away (for this example) with all state being maintained on the client. When you move you notify the server of your new position and the server then distributes that data to all connected clients. Each client would then have to interpret the data and re-render the display accordingly and your server maintains nothing, its just a communication connector.

 

i.e.

User 1 (Human) moves to [ 30, 30 ].Update sprite position and render.User 1 sends packet:{  ref: 'Human',  position: [ 30, 30 ]}Server receives packet and passes it straight to all connected clientsUser 2 (Zombie) receives packetUser 2 reads that the packet refers to the Human and updates the human sprite and renders.

Working out some sort of contract for the packets you're sending is incredibly beneficial, the server hop may need to perform checks on the packets or possibly mutate them.

 

Socket.io is very easy to set up for this purpose (disclaimer, I maintain a socket.io wrapper for the koa framework, which is node), socket.io is not the absolute fastest implementation (it sends headers which you probably dont need, writing your own implementation sending binary will be faster but its a hefty bit of work) but it has a lovely API and great support.

Link to comment
Share on other sites

Completely agreeing with @mattstyles, doing a real-time multiplayer game is very, very hard to achieve. In fact it would be very simple in a perfect world but cheating and latency issues make it really hard to do. 

 

So like he said, you need to run the game logic on a server and have players communicate their moves to the server. This authoritative server will have the final word on who's where, and who's doing what. It will send to both players its vision of their coordinates and they will adjust accordingly.

 

If you want to make it well you'll need to add lag compensation mechanisms and client prediction. This is a very tough subject, and the poor performances of the TCP protocol certainly won't help.

 

Check this article if you want to know how this is done for Counter-Strike : https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking

 

I would strongly advise you to do a very quick broadcast prototype first, which is quite easy, to test the gameplay. It's not worth spending months on the PVP engine if the game isn't fun at first! :)

Link to comment
Share on other sites

Hi BrokenDev Sky,

 

I'm building a multiplayer game using Phaser, Reactphp and zeromq for asynchronous websockets.

you can see the multiplayer bit in action here http://www.eclecticmeme.com  insofar every 10 seconds (could easily be every second) the server updates the NPC locations to all phaser clients. (updating player position and actions is next on my list). 

(you could open it in chrome and firefox to see how the server is updating both)

 

The code is here: https://github.com/emc23   It's complicated but maybe better than trying to do it all by yourself.

Link to comment
Share on other sites

Hi all, thanks for the information so far i will taking time to look into all of it, currently my biggest problem is how to go about the controlling of the two Players in game, the human is spawned in on phasers Create function and then the new player the zombie is spawned in when the new player connects, this works however at present both players can control both of the characters in game, what is best way to limit each player to just controlling their own character?

Thanks.

Link to comment
Share on other sites

First you probably shouldn't start the game before both players have connected. So make some state before the GameState whose role will be to wait for both players to connect (a bit like LOL or SCII, with a loading screen where you see both players progress). Then, when they both have connected, switch to the game state. And in the state create() function, create both characters.

 

Then, just grant the players the control of their own character. If I'm the zombie, I will only be able to move the zombie and I will send zombie actions to the server. The server should know that any message coming from my computer should be related to the zombie only. So that if I cheat, tweak the code and try to move the other character (which you cannot prevent, whatever you will try), the server will not take these actions into account.

Link to comment
Share on other sites

Hi all,

 

im currently develop a multiplayer too... i use peerjs (http://peerjs.com/) for the player connection. The (cloud-)server handles all things for you and its really easy to use.

BUT... im a little bit angry now - I develop my game for android and ios with cocoonjs from ludei... all things works for me... then i read on the cocoon docs that WebRTC (which peerjs use as networklayer) is not supported.. yey :angry: 

 

I dont know how can i handle this - but not your problem :D

 

If you develop for (mobile-) browsers i recommend peerjs and other good stuff like: eureca.io from a forum-member here. Its a node client/server bidirectional RPC like socket.io but easyer to use ( for me :) )

 

Regards,

 

Oleg

Link to comment
Share on other sites

Hi all, thanks for the information so far i will taking time to look into all of it, currently my biggest problem is how to go about the controlling of the two Players in game, the human is spawned in on phasers Create function and then the new player the zombie is spawned in when the new player connects, this works however at present both players can control both of the characters in game, what is best way to limit each player to just controlling their own character?

Thanks.

 

You can paste some code, the one that handles unit movement, and we can help.

 

Assuming that you have a Player class, you could create var myPlayer = new Player('human') ;and var otherPlayer = new Player('zombie');. So, when the user wants to move you only move myPlayer, not otherPlayer. Now, on one client you are the human, on the other client you swap those and you are the zombie. To know which is which, you could have a variable on the server that stores the player number (eg: first player that connects will receive a message saying that he is player 1, the second player will be player 2).

Link to comment
Share on other sites

I am currently creating such a game and I will share my thoughts. You have 2 ways to do this:

 

A. Asynchronously (like Call of Duty, World of Warcraft player movement)

 

This way you move your own character in your client, you send the server when you are and when you move to, and the other players see what you are doing around 200 ms behind.

 

PROS

- A LOT of players can play in your server (maybe some thousands) because your server does almost no calculations

- It's more easily made. The client does all the movement work. The server does only basic calculations like score, players health

CONS

- Players can hack your game (like they do in Call of Duty or World of Warcraft). They can send the server whenever they wish to be if they can hack your javascript. You monitor them with administrators in exchange server performance and player numbers (as we said less server calculations means more players in server)

- Asynchronous data can be attached only to things a player controls (like a hero or a zombie). You can't use this method for server-controlled units (pve mobs). World of Warcraft uses asynchronous method with players but synchronous with mobs.

- You can't use any server-controlled thing. You can't use skillshots even if are server based because the players positions will be stored 200ms behind from the shots. That's why in WoW all spells are targeted to unit or targeted to ground. CoD does not use skillshots, but instant firing

 

B. Synchronously (like League of Legends)

 

This way everything you do in the client will be to send the server things. Server does all the calculations. Client will only show firing, movement and effects if the server says so. Thats why in LoL if you have latency you will move with delay while in WoW or CoD you will move instantly despite of lag. In LoL everything is fair. In WoW or CoD if you have lag you will cheat on other players.

 

PROS

- Everything is fair and accurate

- You can create pve mobs and other server-based events with the same functions (in asynchonous method, player units and mob units with different functions on the server)

CONS

- Much less players can play at the same time (heavier server. in LoL 10 players, in WoW 1000 players)

- Some things must be wrote 2 times (server and client) in order to be smoother

- You have to write your own unit-collision logic at the server side. World, buildings and other things that collide units must be in the server. I don't think such libraries exist for websocket servers.

 

General Thoughts

 

Some games can't be synchronous and they are doomed to hacking and no-skillshots. If call of duty was synchronous, then you should ask permission from server to rotate your camera! That would be bad.. If only the camera was asynchronous, then some players if they rotate fast they would fire from their backs!

 

You must accept you can't do anything. You must plan your game first and choose what you really want.

 

In your case: I would asynchronous method and forget the skillshot kind of things. It is too hard to create a collision system on the server. Each player can select the other, then press the ATTACK button "send server to damage him". Server only calculates range, and if in range, calculates health and damage. If dead, full the health of the dead player and send him to his base point to fight again.

 

TIPS:

- There is a delay when the server sends data to the client too. The server must send his time-stamp too. In the client you calculate the difference time between server stamp and client stamp and move the data a bit forward

- A place to start with socket.io: http://buildnewgames.com/real-time-multiplayer/

 

This is a very good topic, anyone who is experienced please post your own thoughts or correct my mistakes too!

Link to comment
Share on other sites

I think most of what you said makes a lot of sense, even though I find that calling both categories "synchronous" and "asynchronous" is very misleading, because the only difference between both is just server verification. In the first category the server acts as a basic broadcaster, that is, it broadcasts what the clients send and clients are responsible for their actions. In the second category, the server counter-checks every client action and has the final word over it being accepted or not (and then sends the info to other clients). 

 

Of course second option is much more complicated to implement.

 

Please also note that in LoL or SCII or CS, if you have lag you won't experience any delay before moving, because of the strategy called 'client prediction'. However, the server will not allow your high latency to decrease other player's experience and will correct your actions (based on its own calculations), thus it will send you back to where its vision of you is. So your character will most likely jitter and teleport a lot.

 

And also, I think you're kind of confused regarding the "200ms in the past thing". If you try to implement such a real-time game, you will notice that it's very hard to deal with latency. A common strategy (used by Valve for CS, and most likely by any other competitive game) is for the client to display *other* players in the past, so that it can interpolate between their past positions rather than trying to guess what they'll do next. Of course, "the past" is like 200 or 100ms in the past, so the human player behind the keyboard will never notice the difference.

 

The article you paster is a very good one, another few which are really worth reading : 

Link to comment
Share on other sites

skeptron this is interesting. can you provide with a simple example about how we can draw other player sprites 100ms in the past with phaser? how we can store the position/speed in an array in each sprite and how access that array to draw this in the past? a simple code structure

Link to comment
Share on other sites

skeptron this is interesting. can you provide with a simple example about how we can draw other player sprites 100ms in the past with phaser? how we can store the position/speed in an array in each sprite and how access that array to draw this in the past? a simple code structure

 

Showing "in the past" is done when you use interpolation (animate your character between two known positions). It's easy to do this, if you receive a game state update from the server, instead of directly updating to that game state you can animate from the previous state to it instead.

 

If you use a physics system it's very easy to extrapolate too. You just set the state from the server (positions, velocities, etc...) and the physics system will extrapolate (run the simulation) by default so you won't have to code a thing.

Link to comment
Share on other sites

Hi all, thank you for all the interesting responses, i have been following a guide, but somewhat attempting to adapt it as needed (http://ezelia.com/2014/tutorial-creating-basic-multiplayer-game-phaser-eureca-io) i have hit a brick wall with trying to make sure each client controls the correct character (i have changed the idea somewhat Fly vs Fly swatter now) i have been sitting looking at it trying different approaches to trying to get it work, but right now my brains has turned to mush, i have uploaded the project to GIT https://github.com/jbwakeham/MultiplayerTest if anyone could  point me in the right direction, would rather code it myself, im just not sure how to fix the issue at this point. Thanks everyone.

I have reverted everything i tried to the point i originally got stuck where both players are able to control the Fly swatter but from the other players perspective they are moving the fly.

Link to comment
Share on other sites

If you are confused about how each client can control each character I will show you some code structure.

var serverWorldData = null;tryingConnectingToServer(function(serverData) {   serverWorldData = serverData;   startPhaserGame();   // game = new Phaser.Game( ... ) ..    });function preload() {}function create() {   buildWorldBasedOnServerData(serverWorldData);   loginToServerWithNameAndPassword();}function update() {   clientUpdateYourGame();}

- FIrst you connect to server ("tryingConnectingToServer"). Start the phaser game after you connect, if you want the starting world to be based on some primary server data.

- In server side, a "client" object is made (based on socket.io)

- When the connection is established, you save in a variable "serverWorldData" the server data and start the phaser game

- When phaser "create()" starts, you use the "serverWorldData" and build the client world. Then you send your login or your name to the server. You want to login/request world data inside the "create()" function, otherwise you won't be able to draw the sprites you request if they arrive too early.

- In server side, server receives the name and a "player" object is made containing the "client".     player = {client: client, name: receivedName, x:0, y:0}.   This player is stored in some "players = []" array

- The server sends the logged player ONLY HIS OWN data.

- Client does something like create_me(player);

- The server sends all the world/effects/players data to the login player EXCEPT HIM. ( players = [player[0], player[2], player[3] ...]; ) (in case i am the player[1])

- Client does something like create_player(player[0]); create_player(playerData[2]); create_player(playerData[3]);

 

what "create_me(player)" and "create_player(player)" do?

// CLIENT SIDE// create Player classvar Player = function(playerData) {   this.name = playerData.name;   this.sprite = game.add.sprite(playerData.x, playerData.y, "hero");   makingMyPlayerSpriteBeautiful();}function create_me(playerData) {   var me = new Player(playerData);   // THIS IS ME   makeMyHealthBar_GREEN();   createMyPhaserControls(); // when i am moving tell the server too   ifIAmDeadIWillNotMove();}function create_player(playerData) {   var enemy = new Player(playerData);   // THIS IS ANOTHER PLAYER   makeHisHealthBar_RED();   // no controls}
Link to comment
Share on other sites

 

If you are confused about how each client can control each character I will show you some code structure.

var serverWorldData = null;tryingConnectingToServer(function(serverData) {   serverWorldData = serverData;   startPhaserGame();   // game = new Phaser.Game( ... ) ..    });function preload() {}function create() {   buildWorldBasedOnServerData(serverWorldData);   loginToServerWithNameAndPassword();}function update() {   clientUpdateYourGame();}

- FIrst you connect to server ("tryingConnectingToServer"). Start the phaser game after you connect, if you want the starting world to be based on some primary server data.

- In server side, a "client" object is made (based on socket.io)

- When the connection is established, you save in a variable "serverWorldData" the server data and start the phaser game

- When phaser "create()" starts, you use the "serverWorldData" and build the client world. Then you send your login or your name to the server. You want to login/request world data inside the "create()" function, otherwise you won't be able to draw the sprites you request if they arrive too early.

- In server side, server receives the name and a "player" object is made containing the "client".     player = {client: client, name: receivedName, x:0, y:0}.   This player is stored in some "players = []" array

- The server sends the logged player ONLY HIS OWN data.

- Client does something like create_me(player);

- The server sends all the world/effects/players data to the login player EXCEPT HIM. ( players = [player[0], player[2], player[3] ...]; ) (in case i am the player[1])

- Client does something like create_player(player[0]); create_player(playerData[2]); create_player(playerData[3]);

 

what "create_me(player)" and "create_player(player)" do?

// CLIENT SIDE// create Player classvar Player = function(playerData) {   this.name = playerData.name;   this.sprite = game.add.sprite(playerData.x, playerData.y, "hero");   makingMyPlayerSpriteBeautiful();}function create_me(playerData) {   var me = new Player(playerData);   // THIS IS ME   makeMyHealthBar_GREEN();   createMyPhaserControls(); // when i am moving tell the server too   ifIAmDeadIWillNotMove();}function create_player(playerData) {   var enemy = new Player(playerData);   // THIS IS ANOTHER PLAYER   makeHisHealthBar_RED();   // no controls}

This is similar to what i am currently doing, at present im just unsure if im going wrong on the client side or server side, tried some different checks on both server side and client side today, but still getting the mirrored control. Both players controlling the fly swatter, but on the other players screen the fly is moving not the swatter. Did you get a chance to check my GIT?

Link to comment
Share on other sites

This is similar to what i am currently doing, at present im just unsure if im going wrong on the client side or server side, tried some different checks on both server side and client side today, but still getting the mirrored control. Both players controlling the fly swatter, but on the other players screen the fly is moving not the swatter. Did you get a chance to check my GIT?

 

Your eurecaServer.exports.handleKeys  sends the keys input to all connected clients, and not to the origin one. 

Link to comment
Share on other sites

Your eurecaServer.exports.handleKeys  sends the keys input to all connected clients, and not to the origin one. 

 

If by origin you mean the player controlling that object passing the id through with the function 

eurecaServer.handleKeys(this.input , myId);

and then server side.

 

eurecaServer.exports.handleKeys = function (keys , origin) {var conn = this.connection;var updatedClient = clients[conn.id];   if(players.length > 1) //make sure both players are in before    {        var remote = clients[origin].remote;        remote.updateState(origin, keys);        clients[origin].laststate = keys;}}

With this implemented both players are still controlling the swatter but nothing is updating on the other players screen, unless i misunderstood.

Link to comment
Share on other sites

i have just had a little go changing over to socket.io having looked around at another setup using it with Phaser and again the same issue with the mirrored player sprites, both screens play as one sprite but to other player they are moving as the other. But this is not apparent till you change the sprite of the second player, using the same sprite for both makes it look normal. Still struggling with this concept and why it happens/how to fix it. 

Link to comment
Share on other sites

There are a lot of things, mostly structural, that could be causing it.

 

Have you got a working link to the code with directions for firing it up?

 

If its a two player game then a simple implementation (might not be the best, hard to say what is best though without knowing the project) it to let player A update themselves and send info the server about their position, the server then emits that data to the other connected client who is doing the same (thats confusing, sorry, trying to explain, I'll use an example)

start state:client A - player A [ 100, 100 ], enemy [ 200, 100 ]client B - player B [ 200, 100 ], enemy [ 100, 100 ]Each client knows about a player and an enemy, so for player A then player A is the player and player B is the enemy (and vice versa)player A moves to [ 110, 100 ]client A sends a packet, as there are only 2 players in this scenario we can get away with *only* sending the position, so it sends [ 110, 100 ]server receives the response and bounces it out to all connected clients except the initiator (which, in this case is client . socket.io has a helper function to bounce a response out to all other connectees but not the initiating connection, in this case client A is the initiator so all other connections will get the bounce, but not client A (this is critical).client B receives new positional data from the server [ 110, 100 ]. As the simulation is strictly 2 player it knows that the position refers to the enemy and updates the enemy position.Repeat

To extend this beyond 2 players the simplest solution is to simply assign ID's to all connections and have that ID passed along with positional data. When a client receives a position packet, they grab the object corresponding to the ID and update that position (there is a syncing problem here as the ID's are critical, but its not too hard to get right)

 

There are different ways of achieving all this of course, but I'd say this is the simplest way to do it.

Link to comment
Share on other sites

There are a lot of things, mostly structural, that could be causing it.

 

Have you got a working link to the code with directions for firing it up?

 

If its a two player game then a simple implementation (might not be the best, hard to say what is best though without knowing the project) it to let player A update themselves and send info the server about their position, the server then emits that data to the other connected client who is doing the same (thats confusing, sorry, trying to explain, I'll use an example)

start state:client A - player A [ 100, 100 ], enemy [ 200, 100 ]client B - player B [ 200, 100 ], enemy [ 100, 100 ]Each client knows about a player and an enemy, so for player A then player A is the player and player B is the enemy (and vice versa)player A moves to [ 110, 100 ]client A sends a packet, as there are only 2 players in this scenario we can get away with *only* sending the position, so it sends [ 110, 100 ]server receives the response and bounces it out to all connected clients except the initiator (which, in this case is client . socket.io has a helper function to bounce a response out to all other connectees but not the initiating connection, in this case client A is the initiator so all other connections will get the bounce, but not client A (this is critical).client B receives new positional data from the server [ 110, 100 ]. As the simulation is strictly 2 player it knows that the position refers to the enemy and updates the enemy position.Repeat

To extend this beyond 2 players the simplest solution is to simply assign ID's to all connections and have that ID passed along with positional data. When a client receives a position packet, they grab the object corresponding to the ID and update that position (there is a syncing problem here as the ID's are critical, but its not too hard to get right)

 

There are different ways of achieving all this of course, but I'd say this is the simplest way to do it.

Here is the GIT repo https://github.com/jbwakeham/MultiplayerTest  i have added a README with instructions for setting it up.

Link to comment
Share on other sites

Eureca (or the way you're using it) looks like its a crazy complicated way of doing something really simple (I'm sure it has its reasons), but, that aside, can you tell me the intention behind this function?

 

I dont know Eureca or its `handshake` or `handlekeys` method but it look like you are looping through all clients and invoking their key handlers? This means that whenever any client hits a key it does a round-trip via Eureca to the server and you then update all state for each client.

 

Oh, actually, I see it is passing id's which I guess is how it references state for each client. I see your commented log regarding the id there, have you also logged out all id's that clients know about?

 

It sounds like maybe something is getting confused as to which update related to which id. First step would be to identify that each client has a unique id and that each client knows about other clients and that id state is replicated between server and client. Once that is established then you'll have to ensure that your update code is relating to the correct id, because both characters moving on screen sure sounds like your update code is updating the wrong id, or you're the wrong id is being sent.

Link to comment
Share on other sites

I think i may be on to something, took the second player  out (the fly) to focus on getting the swatter to function correctly, player 1 moves it, it updates on screen 2 and screen 2 cant move it, this now works. I am going to get the fly in tomorrow hopefully it will be similar to implement. I will update my GIT afterwards and post on here if it is successful. 

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...