Multiplayer

BlitzMax Forums/BlitzMax Programming/Multiplayer

BLaBZ(Posted 2010) [#1]
I'm just curious as to "what" messages are sent to and from clients and servers in order to sync information.

Do you use WriteLine() and specify some sort of information details like (string)"object1.x=13" then have an interpreter that breaks down the string and assigns the details properly?!

Or is their typically a way to automatically sync data?


_JIM(Posted 2010) [#2]
Usually you have to define your own package structure to fit your application.

You basically need an ID to identify the package and maybe the sender.
For that you can use 2 bytes:

WriteByte(ClientID)
WriteByte(PackageID)

Then, depending on the type of package (updating player position, updating name, etc.) you set up the rest of the package. Ex:

WriteFloat(Player.x)
WriteFloat(Player.y)

On the other side, you interpret the package:

ClientID = ReadByte() 'so you know the sender
PackageID = ReadByte()

Select (PackageID)

...

Case PID_UPD_POSITION
NewX = ReadFloat()
NewY = ReadFloat()

...

End Select


TaskMaster(Posted 2010) [#3]
I find it best to put a whole packet together and send it at once. Rather than writing bits at a time. If you are using UDP and you send the packet bits at a time and on small portion of it doesn't arrive or arrives out of order, then you will have a mess to deal with.

If you put a whole packet together and send it, then if it doesn't arrive, you don't have the confusion of only receiving parts of the packet.

Of course, if you use TCP, then this won't matter. But, TCP is slower than UDP.


Czar Flavius(Posted 2010) [#4]
Raknet takes a bit of getting used to, but for a serious multiplayer game it cannot be beaten. It is fully-featured and faster than any native Blitz multiplayer code I ever wrote.

For a simple game, stick with TaskMaster's suggestion.

It might be a good idea to wrap the data you send (usually called packets) into types.

Strict

Type TPacket Abstract
	Const id_position:Byte = 1
	
	Function from_stream:TPacket(stream:TStream)
		Local id = stream.ReadByte()
		Select id
			Case id_position
				Local playerid = stream.ReadByte()
				Local x = stream.ReadInt()
				Local y = stream.ReadInt()
				Return TPositionPacket.Create(playerid, x, y)
			'case id_another
			'case id_yet_another
			Default
				DebugLog "Unknown packet detected of type " + id
				Return Null
		End Select
	End Function
	
	Method to_stream(stream:TStream) Abstract
	Method get_id:Byte() Abstract
	Method ToString:String() Abstract
End Type

Type TPositionPacket Extends TPacket
	Field playerid, x, y
	
	Method get_id:Byte()
		Return id_position
	End Method

	Method to_stream(stream:TStream)
		stream.WriteByte(get_id())
		stream.WriteByte(playerid)
		stream.WriteInt(x)
		stream.WriteInt(y)
	End Method
	
	Method ToString:String()
		Return "TPositionPacket playerid=" + playerid + " x=" + x + " y=" + y
	End Method
	
	Function Create:TPositionPacket(playerid, x, y)
		Local position:TPositionPacket = New TPositionPacket
		position.playerid = playerid
		position.x = x
		position.y = y
		Return position
	End Function
End Type


It's VERY important you read data out in the exact same order you write it in. It's also very important you read all the required data, and the right types, and don't overread. Or it will screw up the next packet you read. That's why I store the read data in locals instead of putting reads directly as function parameters. You're not guaranteed they will be in the same order that way.

It's up to you to check if the stream is empty or not before passing to the function.