Skip to content

Multiplayer

Valk edited this page Oct 9, 2024 · 17 revisions

Please try refreshing the page if the video does not load. The visual glitch you see on client disconnect has been resolved.

Multiplayer.Preview.mp4

The 2D Top Down genre includes a client-authoritative multiplayer setup, demonstrating how player positions update on each other's screens.

Note

Each packet comes with a small overhead—either 1 or 2 bytes, depending on reliability configured—and a one-byte opcode to identify its purpose. Everything else in the packet is strictly the data we need to send.

🌱 First Look at a Client Packet

Below is an example of a client packet. The client uses this packet to inform the server of its position. To actually do something with Position on the server, override the Handle method from ClientPacket.

public class CPacketPlayerInfo : ClientPacket
{
    // NetSend attribute param indicates the order of which this gets sent
    [NetSend(1)]
    public string Username { get; set; }

    [NetSend(2)]
    public Vector2 Position { get; set; }
}

🌿 First Look at a Server Packet

Below is an example of a server packet. The server uses this packet to inform each client about the position updates of all other clients. To actually do something with Positions on the client, override the Handle method from ServerPacket.

public class SPacketPlayerPositions : ServerPacket
{
    [NetSend(1)]
    public Dictionary<uint, Vector2> Positions { get; set; }
}

Note

If you need more control on how data is sent in a packet, read this.

📦 Sending a Packet from the Client

// Player.cs
Net.Client.Send(new CPacketPlayerInfo { Username = playerUsername, Position = playerPosition });

🎁 Sending a Packet from the Server

// GameServer.cs
Send(new SPacketPlayerPositions { Positions = Positions }, peerId);

⛔ Net Exclude Attribute

Lets say you want to [NetSend] this PlayerData but you don't want to send the PrevPosition.

public class PlayerData
{
    public string Username { get; set; }
    public Vector2 Position { get; set; }

    [NetExclude] // [NetSend] will ignore PrevPosition
    public Vector2 PrevPosition { get; set; }
}

Side Notes

  • Do not directly access properties or methods across threads unless they are explicity marked as thread safe. Not following thread safety will result in random crashes with no errors logged to the console. Things on the client thread should stay on the client thread and things on the server thread should stay on the server thread. If you need to communicate between them use the existing ConcurrentQueues.

  • A common oversight is using one data type for writing and another for reading. For example, if you have an integer playerCount and you write it with writer.Write(playerCount), but then read it as a byte with playerCount = reader.ReadByte(), the data will be malformed because playerCount wasn't converted to a byte prior to writing. To avoid this, ensure you cast your data to the correct type before writing, even if it feels redundant.