Sorry your browser is not supported!

You are using an outdated browser that does not support modern web technologies, in order to use this site please update to a new browser.

Browsers supported include Chrome, FireFox, Safari, Opera, Internet Explorer 10+ or Microsoft Edge.

DarkBASIC Professional Discussion / Looking for direction and feedback on adding multiplayer to my game.

Author
Message
Alaror
15
Years of Service
User Offline
Joined: 9th May 2011
Location:
Posted: 29th Feb 2012 01:14 Edited at: 29th Feb 2012 05:18
Let me start out by saying that I know next to nothing about networking or how multiplayer commands are used, but that I'm here to learn whatever's necessary to add multiplayer to my game. I've decided to use the default DBP multiplayer commands since I've yet to hear an argument against doing so, but I can be swayed away from it if there's enough reason for me to use something else. EDIT: I've since decided to use the Multisync DLL since I had issues with the default commands.

Along with single player, I'd like to give players the option of connecting with their friends to take on "contracts" (missions with a specific objective that take place on a random map). The maximum amount of players would be capped at 4. The game features large, 2d platformer maps with random, destructible terrain. Below are the questions I've come up with to help me get started with implementing multiplayer. If there's anything you feel I've overlooked please feel free to mention it as well.


1. Am I correct in believing that the only things the client should be in involved in is recording user input, sending that input to the server, receiving the calculated data from the server, and displaying the visual aspect of the game? From what I understand the client should not have final say on any calculations. For example, if the player wants to move left, the client shouldn't move automatically. It should wait until the server tells it where its new location is.

2. On the reverse, the server is in charge of maintaining which players are connected or not, receiving input data from all connected players, recalculating values based on the input it receives, calculating any world values players don't have direct control over (AI, bullet movement, updating destroyed terrain, etc), and then sending out all the updated data to every client that's connected. Is this correct?

3. I want the players to host games on their own computer rather than being able to set up a separate server. Is this realistic if the maximum amount of players is capped at four, or would there be some benefit to having a dedicated server? My goal is to make it dead simple to play with others (no need to download extra server software or any of that).

4. From my understanding, having both single player and multiplayer has the potential to complicate things. Instead of having just one set of code, there needs to be a separate version for each. That is unless I treat every game (even single player ones) as a multiplayer server. Would it be possible to create a server that's local to the player's computer as soon as the player has started the game? The key is that it can not require an internet connection at first, but that the same server should be changed to accept other players if the option is selected. If this is possible, how would I go about this code-wise to ensure that the player is still able to play the single player game without requiring an internet connection?

Thank you all for your time and input concerning these questions.

Millenium7
21
Years of Service
User Offline
Joined: 13th Dec 2004
Location:
Posted: 29th Feb 2012 09:54
Quote: "1. Am I correct in believing that the only things the client should be in involved in is recording user input"


no! this is fine for amateur programmers but has not been widely used since the early 90's (when it was predominantly LAN).
Any game that has server sided movement is frustrating since everything is delayed

If you want to program with good practise from the ground up, then what you should be doing is having the client handle almost everything except damage dealing on their end. Then have the server 'correct' it periodically
I.E. client movement should be client sided. When the server receives packets from the client, it should compare the old position, to the next position according to the delay between packets. You then get the server to check against a velocity value and see if it's correct

i.e. if movement should be 100 units per second, and the server receives its first packet at exactly 1000ms, and it receives another packet at 2000ms (1 second later), the client should not have moved any more than 100 units. If it has moved further than that, then send an 'adjustment' signal back to the client. When the client receives this it will position them where the server said they should be

you need to allow some margin of error, since ping times are never 100% consistent. if it's out by +/- 5% ignore it, any further, force the client's positioning. This will allow smooth movement on all client's. It will cause some jumpyness on very poor connections, deal with it, server sided only will be worse anyway
Millenium7
21
Years of Service
User Offline
Joined: 13th Dec 2004
Location:
Posted: 29th Feb 2012 11:15
Quote: "2. On the reverse, the server is in charge of"


correct, the server has final say. Just don't make the server have the only say, keep as much client side as possible except what directly affects other client's (i.e. bullet damage, buffs/debuffs).
Just have the server check the client's incoming data, disregard and correct what should not have happened

Quote: "3. I want the players to host games on their own computer rather than being able to set up a separate server. Is this realistic if the maximum amount of players is capped at four, or would there be some benefit to having a dedicated server? My goal is to make it dead simple to play with others (no need to download extra server software or any of that)."


Up to you how you handle it. It's easier to code seperate client/server code and go the dedicated route. There's nothing stopping you from running 2 instances of your game on the server side, one is hosting, and the 'host' joins themselves as a client in order to play

I myself opted for the playable server/client code in the same .exe. Host runs the same main loop as the client and gets to play. It's a little more difficult to code but is simpler for the end user. My method is every player on their machine is Player(0). All client's also contain player(1),(2),(3) etc as well, but only the host has the ability to manipulate those variables and broadcast them to clients (position, speed, damage etc). With some differences in the netcode along the lines of 'if server then do extra work and broadcast it'

Quote: "4. From my understanding, having both single player and multiplayer has the potential to complicate things. Instead of having just one set of code, there needs to be a separate version for each"


I've not bothered to implement singleplayer in my current project as yet. This one just depends on how you want to code your game. You can certainly have it host a game with a max of 1 player and join itself, or as above a single .exe with an empty server. You can stick with using the same loops. If however a player has no network interfaces, the game will fail when it goes to set up the game. This would be extremely rare these days

At the moment my game for singleplayer is just to host a game and start before anybody joins. If I ever bother to implement singleplayer i'll add more 'if' statements and skip all the netcode setup/sending in the same loop. No way i'll code 2 seperate loops
Alaror
15
Years of Service
User Offline
Joined: 9th May 2011
Location:
Posted: 29th Feb 2012 17:39
Awesome, thanks for all the info! It's a good thing I posted here before jumping in head first haha. So from my understanding, this is what the client/server would handle if the game simply had players in a static environment:

Client:
-Input from the player
-Agent movement and collision (player and NPCs)
-Projectile movement and collision
-Updating visuals

Server:
-Damage
-Correction of agent movement and collision (players and AI)
-Correction of projectile movement and collision


That's a pretty short list, but sadly the list for my game is going to have to be longer. More questions seem inevitable; my apologies for how many there are; multiplayer is such a big topic that it's hard to keep things brief.

1. What should be responsible for the creation of new AI? I'm guessing it's handled server side with a message sent to the player of what type of AI to create, as well as its location? In the same line of thought, should the server be 100% responsible for the AI's actions (such as when to attack, what direction to move, etc)? Should the client still try to move the AI given its current speed and direction, or should it only move when the server tell it that the position has changed?

2. A similar question is how "world events" should be handled. For example, I recently added earthquakes which cause random tiles to become loose and eventually fall to the ground. Since every player is affected by the terrain only the server should decide which random blocks should fall, right?

3. Rylod has dynamic environments that players, AI, and the game itself can influence. The maps are currently 512x512 tiles, with each tile having around 35 bytes of data currently (though thinking through it again I think I could get it down to 20 bytes). That's a lot of data, and I'm not really sure how it'd be handled in a multiplayer setting.

Maps are all random so there's the issue of getting the map data to each player at the beginning of a game, but I think that can be solved fairly simply because I use a "world seed" system to create each map. Every client will be given the seed (simply a number) and then generate the map on their own. After every connected client has generated the map the server knows it can move forward.

That still leaves the question of how the data is handled once the game has started. While the data is held in a single, large array I do use 16x16 "chunks" to break up that data at times. The player only needs to be aware of a 3x3 grid of chunks (that is 48x48 tiles) at a given time, with them at the center. How would I go about distributing terrain updates efficiently? My thinking is that each chunk has a time stamp which is the most recent point at which any block in the chunk has been updated. Every tile has its own time stamp as well. The client will tell the server its time stamp for each chunk it might need an update to. If the server time stamp for that chunk doesn't match up with the player's time stamp, then it goes through and checks the time stamp for every single tile within that chunk. For every tile that doesn't have a matching time stamp the server will send the client all the data for that specific tile. I feel like I'm overlooking something but I'm not completely sure to be honest.

4. Referring to your reply to question 3 above, that sounds like a really elegant way of doing it. Essentially all I'd need is code that determines if the player is current host. If not the host, then send the host the necessary data; if the host, check all the client data being received and send out new data to clients. Instead of the host having two sets of data on their system (one to act as the player's data, the other to act as the server's data) they only have one set (the player's) which is treated as the master data.

I'm a little confused about why each player treats themselves as player 0. Wouldn't it be simpler to have their number change when they join a server to ensure that the data is the same on every machine? How do you tell each player apart on the server's side if they're all 0?

It's not intended as a PvP game so it shouldn't be too big of an issue, but, theoretically speaking, wouldn't a side effect of this be that the host doesn't have to deal with latency at all? Would this advantage be something to be concerned about if I wanted to add PvP elements?

Millenium7
21
Years of Service
User Offline
Joined: 13th Dec 2004
Location:
Posted: 29th Feb 2012 18:42
I don't have enough experience to properly answer your questions above. If it were me doing this for the first time i'd be aiming to

1) I havn't fully implemented my own system yet so can't comment too much. But mine will check for variances in configuration files (I use my own scripting engine to load any AI/weapons/effects etc) and have client's download from the server when there's a difference. The server will decide when and what to spawn, and simply send an instruction to the client. Client's obviously have to create the object model and set parameters. Server should ideally update those parameters such as health, damage dealt, position. Position is a bit tricky though as it could cause stuttery AI for client's. I'd firstly do this though, and put up with any stuttering. Down the track when i'm super confident i'd implement much smarter prediction. Server would send less specific commands such as 'ai unit 4 is currently at X but is moving to Y at Z speed while doing 123' and the client will interpolate. Server will enforce variances periodically. This would require a lot of testing and fine tuning to get the right mix of bandwidth and accuracy

2) server sends the event of what/where when it happens. Otherwise client's might 'think' they've triggered an event but there's a slight variance in position or something, and the event doesn't trigger on all system's, causing a big problem. Every single event should be triggered at the servers discretion

3) parts of the map only need to be updated when they are changed. World traffic should be idle most of the time, when someone does something, server sends what/where and the client interpolates. If a tile is rising for instance, don't send 1000 messages for each unit it moved, just send a single message saying 'it will be this height after X milliseconds' and the client interpolates. If it moves dynamically you may need to send that message more often, but it's still a better way to go than 'this is at that height' every loop

3.5) if the map seed is guaranteed to be the same just send the seed number. Otherwise send the map layout when the client first connects, and send updates as required. Might not be a bad idea to have a global update check every once in a while to ensure it's not out of sync. Maybe split up your map into quadrants and generate a number from it, such as the average height of each tile and then compare it to the client, if there's a variance, update the exact height of each tile in that quadrant to the client

Quote: "I'm a little confused about why each player treats themselves as player 0. Wouldn't it be simpler to have their number change when they join a server to ensure that the data is the same on every machine? How do you tell each player apart on the server's side if they're all 0?"


Each player is player(0) on 'their' computer, but is also their actual player number, though they don't their player number for their variables
For instance if i'm player 3 on a 4 player server, then my position on the map would be
Player(0).X
Player(0).Y
and if i'm moving, I update the values in Player(0). However i'm 'also' Player(3), I just don't use the values from there directly when doing stuff. I have the values of all players stored in their correct positions, from Player(1)-(4)
Now what if i'm lagging behind the host a bit? I might currently be standing at 50x. But 500ms ago I was at 45x. So Player(0).X would show 50 and Player(3).X would show 45x on 'my' side, all other players would also see Player(3).X as 45x and that's where i'd be on their screen. I'll receive the updated Player(3).X when I receive the updated data back from the host

Make sense? in this way all my control's are done with the same functions. So it doesn't matter what player number I am, or whether i'm host or not, or even running singleplayer.
Obviously it gets a bit more specific when looking at doing damage or controlling AI. But movement remains client sided and for the sake of simplicity every player is Player(0) on their side
Alaror
15
Years of Service
User Offline
Joined: 9th May 2011
Location:
Posted: 29th Feb 2012 19:30
Yep, that cleared up pretty much all the questions I had for now. The concept of interpolation is something I hadn't heard of yet but seems pretty useful. Thanks for taking time out of your day to help! I'll probably update this post if, or rather when, there are more questions.

Alaror
15
Years of Service
User Offline
Joined: 9th May 2011
Location:
Posted: 1st Mar 2012 17:20
I've come up with another question if anyone wants to take a stab at it. How exactly is data synced between each person that's connected? In a single player game I would use the object number to tell each one apart (makes it dead easy since it's unique to the object, and can be used to delete/alter it as well). In a multiplayer setting I don't think you'll be able to use the object number since it might be different in one computer or another.

The example I think of right away is how to handle projectiles. If a client decides to fire their weapon the request to do so is sent to the server. Should the bullet be created as soon as they fire the weapon, or only after the server replies to them and says it's okay? If it fires automatically that means the bullet will exist on the client's computer before the server even knows it's created, using whatever object number it decides to use. What if at the exact same time another client fires the same weapon and creates a bullet with the same object number as the first client? We'd now have a situation where the server has two bullets using the same object number.

That's a non issue if you put the server in charge of telling the client when to create objects, but wouldn't that result in some weird lag? When you fire a weapon you expect it to activate immediately, not delay.

Millenium7
21
Years of Service
User Offline
Joined: 13th Dec 2004
Location:
Posted: 1st Mar 2012 18:01
there's a few ways you can handle it depending on how flexible you want your code to be, and how much you are willing to invest in getting it right.

The easiest way might be to simply add your player number to objects. For instance



so player1's object is 11 for every client, player2's is 12 and so on. Projectiles for each player could be created within the (MyPlayerNumber*1000) range so Player1 gets 1000-1999, Player2 gets 2000-2999 etc. This is simple but it limits you to predefined ranges and object numbers. If you use a projectile range of 2000-2999 for player2 then you can't have any more than 1000 projectiles for that player

Another way is to use independent object numbers which are stored in the client's own arrays. i.e.



You can use this method to create out of order objects. And not require each client to use the same object numbers, or even not have loaded the same number of objects. You can theoretically do away with predefined object ranges. Whenever loading a new object you could simply increment the object number each time, ensuring nothing overlaps

The method I use is indexing and I do away with having to specify object numbers altogether. My example code would be similiar to the one above except I use the 'find free object()' command (part of matrix1utils). So it will find the first available object, I don't even know or care what the actual number is I just refer to it with arrays. It gets a bit more sophisticated when handling things like projectiles, where there might be hundreds of them and their actual object numbers could be all over the place, amongs other sorts of objects, missing/deleted objects, or even reusing old object numbers. I tackle this with an index. All projectile object numbers are sorted and stored in arrays.
The array might read 14,53,72,11,6,109 as object numbers. The game will run through and do all the appropriate actions for each projectile. As things happen, obviously more objects will be added, and some will be removed. This is reflected in the index, if objects 53 and 6 are deleted the array would read as 14,72,11,109. So the game no longer deals with those projectiles, they disappear never to be seen again. All array data relating to those specific projectiles is cleared and free'd up for the next one created. This is complicated and you need to write several functions to handle it. The upshot is if you do it correctly you can have almost unlimited objects of any type, independent of any other client.

The last method was the only way for my game to work effectively, because as I mentioned before I use a branching scripting engine. Someone might decide to modify a map with lots of extra enemies, or even new types of enemies. So I can't have any hard limits. Your project may not need that scope and it could be a lot simpler and faster to use a different method. There are more than these 3
Alaror
15
Years of Service
User Offline
Joined: 9th May 2011
Location:
Posted: 2nd Mar 2012 00:28
Thanks for replying again, you've been a huge help!

I'd like to explore the indexing option first since it's closest to what I have working in my single player game (seems that way at least). I'm also using the "FIND FREE OBJECT()" command to get the object numbers, with the data all being stored in an array specific to what type of item it is (projectile, loose object, unstable block, etc). With the method you described all the arrays across every client would have to be ordered exactly the same, correct?

Thinking through this again I guess my question isn't so much about the data but whether or not clients should be allowed to create an object without the server telling them to do so. Going back to the bullet example in my previous post, if there are ten projectiles in the array and two different clients create a new projectile, both clients would have an array with 11 projectiles in it. When those two new projectiles get to the server it would send out a message to all the clients saying that there should now be 12 projectiles total. The result is that one of the client's lists is out of order.

Is it okay to have the server be the only one allowed to decide if a new object of any type should be created?

Ortu
DBPro Master
18
Years of Service
User Offline
Joined: 21st Nov 2007
Location: Austin, TX
Posted: 3rd Mar 2012 02:47 Edited at: 3rd Mar 2012 02:51
Quote: "With the method you described all the arrays across every client would have to be ordered exactly the same, correct?"


well... they all have to have the same method of handling data but the data itself doesn't have to be identical. Basically each client should just look at it's bullet array lets say and and say to itself ok i need to update each of these bullets. it shouldn't need to care what specific numbers are attached to each bullet, just that each has a number that actually exists in its own environment. the numbers in another client's environment are meaningless.

Quote: "if there are ten projectiles in the array and two different clients create a new projectile, both clients would have an array with 11 projectiles in it. When those two new projectiles get to the server it would send out a message to all the clients saying that there should now be 12 projectiles total. The result is that one of the client's lists is out of order."


so A starts with 10, B starts with 10.

A fires and adds 1 to itself making 11 and tells the server it has done so. This message does not say "I made bullet 11." it says "I made a bullet"

B fires and adds 1 to itself making 11 and tells the server it has done so.

B receives a message from the server that A has fired and adds 1 making 12. this message shouldnt be "A made bullet 11, add it to your list." it should be "A made a bullet, add a new one to your list"

A receives a message from the server that B has fired and adds 1 making 12.

The fact that on A, bullet 11 is owned by A, bullet 12 is owned by B and on B, bullet 11 is owned by B, bullet 12 is owned by A and that these number don't match makes no difference. all that matters is that if A gets hit by a bullet owned by B, A takes damage etc.

These things should be handled in terms of from FirstObject to LastObject rather than from 1 to 100 if that makes sense.

Fallout
23
Years of Service
User Offline
Joined: 1st Sep 2002
Location: Basingstoke, England
Posted: 3rd Mar 2012 14:27
I'll be implementing multiplayer in Carnage soon too. It's definitely a challenge. Here are some of my observations and thoughts that may help.

Client
The client handles almost everything locally. So all controls cause local movements, firing etc. as if it's a local game. The client also handles it's own collision with enemy units. So when the player runs into a multiplayer enemy, I use my local collision functions to move the player accordingly. All this arrives at positional data and projectile creation data that is sent to the server. So packets will be something like:

Routinely sent positional data packet (maybe every 100ms):
- X,Y,Z player position
- X,Y,Z player speed
- X,Y,Z player angle

Special packets sent as soon as the event occurs:
1. Fire button pressed. Packet contains information on the projectile created and it's data, e.g:
- Bullet created
- X,Y,Z bullet pos, speed, angle

2. Movement key pressed. As well as the routine position updates, whenever a movement key is pressed or release, that would result in a sudden shift of direction/velocity, an additional position packet is sent.

Server
The server is responsible for predominantly handling game logic and damage as millenium7 said. So it's running it's local version of the game, receiving packets from players describing their positions, speeds and angles, and also their creation of projectile entities. It's main purpose is to forward all these packets onto the other player clients, so they can run their own "position other player" and "create bullet projectile" functions, based on the packet data.

To keep things fair, it does the calculations on projectile to player damage, and sends packets back to the clients with that information. A list of possible packets:

1. Client position packets
- The player ID
- X,Y,Z position, angle, speed of this player

2. Projectile to player collision packets:
- The projectile ID that is hitting a player
- The client ID being hit by the projectile
- The X,Y,Z positional data of projectile/clients at point of collision

3. Game logic packets like:
- A weapon has spawned in X,Y,Z of a certain type
- The timer is currently at 2:23 secs (to ensure game synchronisation)

4. Client state packets like:
- Client 3 has just dropped, so remove him from the game
- Client 5 has just joined the arena, so create a new player

What I'll end up with is a load of unique functions for the client, and special functions for the server, which handle all these potential packets and handle the logic appropriately. So for example:

- CreateBullet(x,y,z,xspd,yspd,zspd,xang,yang,zang,owner_id,timestamp)
-PositionPlayer(PlayerID,x,y,z,xspd,yspd,zspd,xang,yang,zang,timestamp)
- InjurePlayer(PlayerID,damage,timestamp)
- NewPlayer(playerID,x,y,z,timestamp)
- DropPlayer(playerID,timestamp)

A top level HandleReceivedPacket() function analyses the packet and it's contents and calls the appropriate function.

Similarly, there will be sending functions:
- SendPosition(myPlayerID,x,y,z,xspd,yspd,zspd,timestamp)
- KeyPressed(myPlayerID,keypressed,timestamp)
- QuittingGame(myPlayerID)

And all those functions call a top level HandleSendingPacket() function which packages the data up with the correct headers and sends it off to the client or the server.

You've mentioned issues to do with object number ID and things, but those have to be completely unrelated to the game logic in multiplayer. It should be irrelevant if client1 has player 2s character model as object number 3, for example. You need to be writing functions that create player models, projectiles, pick-ups etc. without regard to DBPs object ID system. Instead, they need to have an ID you program yourself, as part of a Type structure. Since the games will never be synchronised 100% across clients, because of latency, bullets, players, particles etc. will all be generated out of order with each other, so you can't rely on object IDs ever being consistent.

The key thing mention above is latency, and you'll notice timestamps mentioned in all the functions. As Millenium7 mentioned, it's all about interpolation based on speeds and latency.

The first step is to synchronise the client and server. So before a game starts, the client sends some ping packets to the server, which the server receives and sends back straight away. The client includes a timestamp in that packet, and waits to get it back from the server. Once it receives it, it compares that packet timestamp to the current time, and can determine a ping from that. If 10 ping packets take an average of 100ms to do the round trip, the client can assume it take 50ms for his data to reach the server. The server does the same thing, and keeps a table of how long it takes to send/receive data from each client. This is performed routinely throughout the game, so this table of latency is up to date.

You can use this ping to ensure clients all start at exactly the same time. So when the server sends a "GameStartsIn1Second" packet to the client, he knows it takes 50ms to get data from the server to him, so he starts the game in 950ms. This ensures everyone is synchronised.

When you know the ping between each client and the server, you can start to figure out where things should be, and interpolate positional values. Here's a step by step of a latency example:

1. Client1 pressed Fire at an imaginary time of 0ms.
2. A FireBullet packet is sent to the server at 0ms.
3. Server receives this packet at 30ms.
4. Server knows he has a 30ms ping to Client1, so he runs his CreateBullet() function, passing it a value of 30ms.
5. The CreateBullet() function creates the bullet, then moves it along in space whatever distance it should travel in 30ms. Now Client1 and the Server have synchronised positions for that bullet.
6. The server sends a FireBullet() packet to each of the other clients, using this new position (or alternatively the original position, but with a message describing Client1s latency)
7. Client 2 receives this packet at 100ms.
8. Client 2 runs its createBullet() function, moving the bullet on by whatever distance it travels in 100ms.

In that example, even though the data takes 100ms to go from Client1 to Client2, the world will still be in perfect synchronisation. The only problem is the bullet starts further ahead in space on Client2 than is ideal, but there is NOTHING you can do about that. That is why online games require low pings (sub 50ms ideally), so the effect of latency is negligible, and thar's why your server should kick players with high ping.

Anyway, that's the crux of it! Key steps are:
1. Write code that is independent of DBP object IDs etc. and use your own IDs to describe entities in the game.
2. Write a load of functions to handle positioning and creation of game entities, with the ability to reposition them based on latency of the data received.
3. Write some top level functions which handle packets and call the appropriate creation/position functions.
4. Keep the games perfectly synchronised by sending ping packets regularly so the clients/server know how long it takes to send data back and forth.
5. Time stamp everything
6. Pray to God it works

Multiplayer coding is a mega headache and will take ages to tweak and get right with a complex game, but it's worth it.

I hope that helps a bit. It helps me to write it down at least!

Millenium7
21
Years of Service
User Offline
Joined: 13th Dec 2004
Location:
Posted: 3rd Mar 2012 16:04
that's another thing I should add
This is completely optional and if you already have a lot to thin about, please by all means ignore it.

In regards to how many packet's are sent per second. I use a variable to specify a local net rate. This is how many packets the local client will send per second. At present mine is fixed at 30 but i'll allow it to be changed later on. This means at 60fps i'm only sending data once every 2nd loop
It also means all messages must be queue'd and sent only when necessary

I do this to keep traffic a bit more uniform, and to reduce bandwidth and congestion. If you use sync rate 0 and someone is running at 500fps then traditionally they'll be sending 500 or more packets per second. Somewhat overkill and is counter productive

Again it's completely optional, most people use sync rate 60 or 100, both of which should be fine. I opt for 30 as it's high enough to be effective for my project at this stage. Note that I keep my 'ping/pong' messages on their own 1000ms timer. So it could be either 30 or 31 messages per second in reality.
Alaror
15
Years of Service
User Offline
Joined: 9th May 2011
Location:
Posted: 3rd Mar 2012 18:31
@Ortu

Thanks for the feedback, but I feel like there needs to be some way for the server and clients to tell which data is which between each other. Like you suggested, my plan is to have the client handle movement and all that, but for the server to send corrections every now and then. If a client is laggy then their bullet object will fall behind everyone else's on the screen, causing it to get deleted after the damage should have been dealt. The server should be able to tell the clients that a bullet needs to be deleted, but the only way to do that is if each object shares some type of ID between the host and all the clients. A better example might be AI. The host is going to be responsible for telling all the clients when the AI has decided to move in another direction/attack/etc. If there isn't a common ID then there's no way to tell if the host is talking about AI #1 or #2. Or do you just mean that the position in the array shouldn't be used as a way of telling one apart from another?


@Fallout

Wow, thanks for writing all that up! I've started working on some of the packet transfer functions but hadn't considered ping/latency at all in them. Time for some more thinking I guess.

Alright, so each object in the game needs to share an ID. I'm still not clear on when that ID is assigned. If a client creates a bullet should it do so automatically, without waiting for the server to respond? If that's the case the server would need to send a message back to the player who created the bullet and tell it what ID it decided to use, correct? The best way I can think of handling this is:

-When a player creates a bullet it assigns its own ID automatically, and that ID is sent to the server along with the message that a bullet should be created.
-The server assigns an ID that will be assigned across all the clients.
-The server sends a message to all the other clients saying that a bullet needs to be created using the particular ID.
-It also sends a message to the original client who created the bullet that contains the new ID assigned by the server, as well as the original ID assigned by the client.
-The client searches through the appropriate array to find an object that matches the original ID it decided to use. I then replaces this ID with the ID assigned by the server.

Is there a more strait forward way to go about it? Am I missing anything important?


@Millenium7

Your post made me think about something I hadn't considered up until now: is it okay to send more than one packet of data per step of the program, and if so is there a limit to how much data should be sent each step?

Millenium7
21
Years of Service
User Offline
Joined: 13th Dec 2004
Location:
Posted: 3rd Mar 2012 19:48 Edited at: 3rd Mar 2012 19:50
Quote: " is it okay to send more than one packet of data per step of the program"


Yes but my method (which I feel is superior due to reasons I mentioned) is to queue all my traffic. I use a single function for everything to do with 'sending' data, regardless of server or client (the function determines this, and sends additional data)
It's only executed when the required time has elapsed, in my case once per 33.3ms or 30 times per second

If a client does something, or the server receives messages which need to be bounced back to other clients, it will just add it to a queue. Then when the function is called it sends everything in the queue. I don't send data immediately upon executing the action. The exception is the 'pong' in response to a 'ping' which will be sent back immediately

as for your bullet problem, it wasn't occuring to me before but now that you've re-explained your problem I see what's missing. Yes you need an ID of objects. If a bullet is spawned it must have some kind of identification. The simplest method would just be to use a UDT i.e.



Ideally you want to keep the (1) or whatveer the same across every client, you don't 'have' to, there are other ways to get around this, such as indexing/grouping, 'closest match' or even server assistance to make it irrelevant

I'm thinking you already have a full plate so i'll offer a simple - though not perfect - solution to the 'what if 2 clients shoot at the same time' scenario. First and foremost the server is the decision maker. So let's use a temporary 'local' array for all client data, whilst its awaiting the servers ruling



Everything on the client's side gets stored there temporarily. Run a basic 'for next' loop to check if any data is present within it, and use it to show the clients side of the bullet appearing and moving and so on.
If 2 client's shoot 1 new bullet each at the exact same time they'll both be stored as TempBullet(1) on their own client, following so far? Until a response from the server is received it'll remain that way
Now when you send the message to the server indicating you have shot a bullet, also include your TempBullet's ID whether it be (1) or (2) or whatever.
Now lets look at it from the servers point of view. The server is about to receive messages from either client indicating a new bullet. So have the server decide which ID number gets assigned where, it might choose Player2's bullet to be bullet #47 and Player3's bullet to be #48. The server should now send a message back to each client informing them of the creation of the new bullet, as well as the ID number it has assigned it, along with what the original TempBullet ID was
Now flicking back to the client's side
The client will receive this message from the server, and transplant all information from the corresponding TempBullet(1) into Bullet(47), as well as the information about a new bullet created as Bullet(48)

Make sense?

Something to keep in mind as well is that on the client's side, bullets should be nothing more than a visual interpretation of something. It doesn't have to be entirely accurate, so it doesn't matter if the bullet looks like it just missed, or hit something and disappeared. If it actually 'did' anything, a response will be heard from the server. That's when you apply the desired results
Fallout
23
Years of Service
User Offline
Joined: 1st Sep 2002
Location: Basingstoke, England
Posted: 3rd Mar 2012 19:59 Edited at: 3rd Mar 2012 20:01
@Alaror

You may be over complicating things. I think it depends on your implementation, the relationship between your objects and how identical the environments on each client need to be.

For Carnage, I don't think I'll care about unique instances of bullets. For me it'll be enough for a client to tell the server it has made a bullet at xyz. The server will then make that bullet in it's environment, and tell the clients to do the same. Each client and the server will then move that bullet along, until it hits something, and then destroy the bullet as appropriate.

In the majority of cases, with the games synched, the same result will happen on all machines. In some rare circumstances, due to positional errors because of latency, client 1 may fire the bullet at client 2, and see it hit client 2, but on client 2s machine, the same bullet may miss them and go past. For some games, that'd be unacceptable, but I'm thinking it'll be ok for Carnage. There are so many bullets flying about in Carnage, a 1% inconsistency in what a client sees and what is actually happening probably won't matter.
It'll be the server's instance of the game that determines the damage. So if the server sees the bullet hit the player, it'll tell both client 1 and client 2 to injure player 2.

If you want to ensure more accuracy, then I would probably do something like this:
1. Client 1 fires a bullet. It gives it an ID made up of it's name and unique to it's system. e.g. Client1_Bullet1. This will then be unique throughout the game, since it's prefixed with Client1.
2. Client 1 sends CreateBullet packet to the server.
3. Server creates the bullet with ID Client1_Bullet1. If, by some rare instance, Client1_Bullet1 already exists on the server, it simply deletes the one already in existence.
4. The server forwards this packet on to the other clients.
5. When the bullet is destroyed on the server, it sends a DestroyBullet packet for Client1_Bullet1 to everyone.

I would still have the clients destroy the bullet locally when it hits something, to avoid bullets moving through objects then getting deleted afterwards.

In reality, I'm not sure if that'll work for my game. With the minigun, 50 bullets are fired per second. I think 50 bullet creation packets per second is too many, and is going to cause performance issues, bottle necks, and other strange issues like 2 bullets firing at once, then a pause, then 3 fired at once etc. due to packets arriving at differing times. So I will imagine for my game, I'll have FiringStart and FiringStop packets, based on pressing the fire button. So when the user hits fire, it tells all the other clients its firing, and they create the bullets themselves at the correct refire rate.

It's definitely a head fudge! I think I'll have to experiment with my game to figure out the right approach, as you'll probably have to with yours. I would just say keep the packets down to a reasonable number per second, and if there are game rules/laws that govern how things should happen (like fire rates for guns), it might be worth exploiting them by letting each client figure out when the next bullet should fire, rather than sending a pack for each and having to deal with timing inaccuracies.

Alaror
15
Years of Service
User Offline
Joined: 9th May 2011
Location:
Posted: 4th Mar 2012 20:25
@Millenium7

Alright, I think I understand the que system now. I actually have the packet sending system set up in a way that should work nicely with it (using an array to store which messages need to be sent to who). The only concern I can think of right now is that player movements (which ideally are real time) wouldn't be as responsive. Is this a serious concern? Like you said, there's a lot to think about still so I'm going to hold off on playing around with it until everything else is at least in a working state.

About the object numbering system, I really like the idea of using the array position as the object's ID. Would definitely simplify things. But on that note...


@Fallout

If I don't have to mess with IDs at all that would be amazing. I've thought through what you said about having on/off states for abilities and can't think of any reason it wouldn't work. It would lead to inaccuracies, but I've probably blown the seriousness of it out of proportion. Doing it this way would also seems like it would reduce the number of packets by a tremendous amount. The best part is that there wouldn't be a whole lot of code that I'd need to change to get it functioning; that's a nice benefit in and of itself.

Oh, I also checked out your game's forum topic. Even in its unfinished state it's very impressive! Keep up the good work

Fallout
23
Years of Service
User Offline
Joined: 1st Sep 2002
Location: Basingstoke, England
Posted: 4th Mar 2012 21:35
@Alaror - Thanks for the comments on my game.

As for the on off states system, don't forget about packet reliability. If you're using UDP (which is best for performance reasons), packets aren't guaranteed to get through, so the odd one will get lost. If the lost packet is a "fire button off" packet, then that gun is going to carry on firing on the server and other clients, long after the sending client released the fire key. So you need to code in your own safety nets. Perhaps sending the keystates in every regular update packet would cover that possibility.

Just another headache of multiplayer.

Login to post a reply

Server time is: 2026-07-09 00:07:58
Your offset time is: 2026-07-09 00:07:58