Skip to content

Multiplayer Scripting

Valk edited this page Nov 10, 2022 · 28 revisions

This page is a work in progress

Sending Packet from Client to Server

namespace Sankari.Netcode;

public class CPacketPlayerPosition : APacketClient
{
    public Vector2 Position { get; set; }
	public int Health { get; set; }
	public Dictionary<ushort, byte> RandomData { get; set; }

    public override void Write(PacketWriter writer)
    {
		// The order things are written matters
        writer.Write(Position);
		writer.Write(Health);

		// The length must be written whenever sending any kind of list of data
		writer.Write((ushort)RandomData.Count); // cast to ushort because don't need that many values as a int

		foreach (var element in RandomData)
		{
			writer.Write((ushort)element.Key); // sometimes it helps to cast for readability
			writer.Write((byte)element.Value);
		}
    }

    public override void Read(PacketReader reader)
    {
		// The order things are read matters
		// A common mistake I do all the time is forget to assign the reader.ReadValue() to a variable
		// Don't just leave it as 'reader.ReadValue()', do 'var myValue = reader.ReadValue()'
        Position = reader.ReadVector2();
		Health = reader.ReadInt();

		// Read the length from the dictionary
		var length = reader.ReadUShort();

		// Do not forget to initialize the dictionary
		RandomData = new Dictionary<ushort, byte>();

		for (int i = 0; i < length; i++)
		{
			var key = reader.ReadUShort(); // do not make the mistake of doing reader.ReadShort();
			var value = reader.ReadByte();

			// do not use RandomData[key] = value; because this encourages duplicates not being handled
			// if a duplicate is added with RandomData.Add(key, value); the game will crash but this
			// is what we want as this should be handled accordingly
			RandomData.Add(key, value); 
		}
    }

    public override void Handle(ENet.Peer peer)
    {
		// This is handled server-side
		// Do something with the data
		var player = Net.Server.Players[(byte)peer.ID];

        player.Position = Position;
		player.Health = Health;
		player.RandomData = RandomData;
    }
}

AutoHostJoin Property

Set the linker settings to the following for debugging multiplayer.

image

image

The Auto Host Join option makes it so if the game is launched through the editor a server will be setup on local port and host client will auto connect to this server. If the game is launched through an exported release, a client will auto attempt to join this local host server. The game window titles are updated to make this more clear. If you do not check Auto Host Join, you will have to go to the map screen and press Esc to open up the "UI Map Menu" which gives you all the buttons needed to host or join a server.

UIMapMenu.cs

The UIMapMenu.cs script is located in Scripts/UI/UIMapMenu.cs, here you can find functions like HostGame(...) and Join(...). This is where all the multiplayer code starts. Note that you will see use of the Tokens class here, this is used to create cancellation tokens to cancel networking tasks later down the line. I recommend you look through the code to see how these are used.

Netcode Scripts Location

All the rest of the multiplayer code (netcode) is located in Scripts/Netcode. The main areas of interest are Scripts/Netcode/Client, Scripts/Netcode/Server and Scripts/Netcode/Packets. Note that you will see for e.g. ENetServer and GameServer. ENetServer is the underlying enet implementation and as such all game specific code should go into GameServer. Same deal with ENetClient and GameClient.

Packets

Lets talk about packets. Packets are how data is sent over the network. Each packet has its own opcode defined in Netcode/Packets/_Opcodes. Some packets have a secondary or even a third opcode to help prevent going over the 255 byte limit. (The first packet opcodes are sent as a byte to save bandwidth, this means we can't have over 255 opcodes). As such organizing the packet opcodes is critical. Stuff like lobby creation / deletion opcodes should all get one "Lobby" opcode and use secondary opcodes. There are no lobby packets but this was just used as an example. Back to packets. Have a look at the CPacketXXX and SPacketXXX scripts for examples on how to read / write packets from / to client / server.

There are many methods you can use to send packets from the server to clients. These methods can be found in ENetServer.

There are only 2 methods for clients to send packets to the server.

Thread Safety

This game makes use of 3 threads (Godot, Server, Client). Do not directly access public variables or methods from these threads to other threads. If you want to communicate between threads please make use of the appropriate ConcurrentQueue<T> channels. Violating thread safety can lead to frequent random game crashes with usually no errors in console making these types of issues extremely hard to track down when they start acting up.

This issue will stay up until either the wiki is updated with this information or the multiplayer code becomes insanely easy to use it does not even need a guide

Ignoring Opcode Log Messages

In the future a checkbox option will be added to completely turn opcode logging off.

image

Clone this wiki locally