Search code examples
c#network-programmingserverxnatcpclient

How can I efficiently send objects in my game with client-server architecture


I have made a networked 2d top-down shooter game. It's fairly standard, the user moves their player using WASD, and can cast spells using 12345 and the mouse. The user must kill enemies and stay alive.

I've networked it for multiple users to play in the same arena, but it's too inefficient to run.

Sprites are stored in a Dictionary<string, sprite>, which is stored in the clients and server machines.

It's implemented as so:

  1. The users keyboard and mouse input is sent to the server each frame (KeyboardState, MouseState).
  2. The server takes the input and updates the game state.
  3. The server sends each client their viewport(rectangle), and the position(float,float), sprite key(string), rotation(float), colour(int,int,int,int), of all sprites in that user's viewport.
  4. The server stores this information as instances of a class that holds these values.
  5. To send it, the server uses a BinaryFormatter to serialise the class, and sends it using TcpClient and Sockets.
  6. The client deserialises the incoming data back into the classes and draws them to the screen

Unfortunately this makes the game incredibly slow. I tested this on only one machine, so there isn't latency, so I assume there is too much data being sent.

To fix this I think I would either need to store and send this data efficiently, or implement this differently.

To send it efficiently, I think I'd need to pack the data as bytes and send it, but I don't know how to do this.

How would I send the data efficiently, or implement it differently so that it is efficient?


Solution

  • Minimize the amount of data traversing the network. Only send the needed amount of data and provide your own verification through timestamps and validation.

    Each actionId and AnimationId in the following should exhibit the minimal (power of 2) amount of bytes to uniquely describe all of the possible values.


    Server -> Client:

    Servers known client position and rotation. Acknowledgement of last actionId. A list of all other clients positions, orientations(rotations), animationIds... within the field of view of the client +- maxRotationRate within a distance of MaxViewDistance +- maxMovementRate.

    The client must average its current position with the servers position every step.

    Process all inputs locally(mouse and keyboard), and only send the relevant data to the server:

    Every update, the client will send the relevant data and update its own internal state and process the server state given, and increasing int value(call this a timestamp). Include parity or CRC here.


    Client -> Server Current position, rotation, actionId, animationId, and a monotonically increasing int value(call this a timestamp). Include parity or CRC here.

    All rendering decisions, the Viewport, current animation/sprite/colour should be established on the client(the colour and initial position needs to be given to the client at logon).

    Location data will be shared(local updates and server adjustments).

    Location and rotation and velocity data(both linear and rotational) need to be sent to the server every update. Upon receipt at the server a validation process must occur to prevent cheating. On missed, out of order, or malformed updates, the server will assume the last position + velocity * (time difference from last timestamp + current server time).


    You must use UDP for the communication(note that you are using your own timestamps and CRC to validate the data received, no more crashes on invalid data).

    Only process packets where the parity/CRC checks pass and reject the packets that are received too short or fail the checks. These checks will eliminate the crash that is associated with bad packets.

    TCP's retry mechanism and ordered delivery is detrimental to game performance. Each time a single packet is lost or delivered out of order, the time delta error(time the data is sent versus the time the server processes it) is increased by the RTT(Round Trip Time, i.e. Ping Time) due do the server retries and packet re-ordering. This will lead to multi-second delays that will only increase as the game goes on.

    Network delays and dropped/malformed packets are normal occurrences for games. In the case of out of order packets based on timestamp values, drop all but the last, since this indicates the players latest wishes (for movement). With firing(spell casting in your case), keep sending the spell bits until acknowledged(ActionId). Ignore all but the first received spell on the server(send the Acknowledgement to the client), until the recharge period has expired.

    While this answer excludes all points not brought up in the original question, mainly damage, a structure should be created that when an incidence of a damage inducing element is produced the on the server that distributes the damage according to the defined rules of damage distribution, that needs to be applied to each afflicted client.