DirectXPlay:
Sending Messages
Author:
Almar Joling
Written: 12th July 2001
Contact: [EMail]
[Web]
Contents
of this lesson
1.
Introduction
2. Connection types
3. Sending messages
4. Receiving messages
1.
Introduction
The
tutorials about DirectPlay will try to cover much
of this interesting Multiplayer API. Before I jump
into the coding part, I will explain some details
that might help creating your multiplayer game.
First you should know that multiplayer programming
is totally different to game programming. You'll
get a hard time finishing your multiplayer game,
with smooth animations by other player controlled
objects. It's often wise to build the game from
the ground up to be a multiplayer game instead
of making it single player, and simply adding
the multiplayer capabilities later. Instead of
DirectPlay you can also use the Winsock API (or
control). Winsock is more flexible but you have
to code everything yourself, like "message priorization",
"guaranteed messaging", and lots more things.
In DirectPlay you do not have to worry about the
protocol decision "TCP/IP", or "UDP". But I'd
like to give a small introduction what these two
mean.
UDP:
This protocol is "connection less". You just sent
the data to a computer, and hopes it will listen.
UDP is also "non-guaranteed", this means that
what you sent does not have to arrive at
the original intended destination ("packet loss").
The advantage of this protocol is that it's 2-3
times faster than TCP/IP. It's widenly used for
action games like Unreal Tournament, Subspace,
Quake3, etc.
TCP/IP:This
protocol first needs to be connected to a remote
host. TCP/IP is a guaranteed protocol,
if you sent something it will arrive. TCP/IP is
more used for data streams. Like downloading
a file! But for turn-based/or slow RPG's this
protocol is great. For modem users this protocol
is also better, because the modem can apply "TCP/IP
header compression" to the data. If too much data
is sent, the protocol will buffer the data, increasing
the latency. TCP/IP packets will always take more
time to reach the destination! (not good for fast
action games)
In
DirectPlay you really don't have to worry about
this. Because you can easily send packets "guaranteed"
using an additional flag to the method that sends
the data.
There's just one thing I'd like to make a note
on: Players don't and won't understand latency!
Make your game work under bad "internet weather"!
2.
Connection types
Something
you should worry about is the connection type. There
are three "practical" types, but since DirectPlay
gives us the ability to use two of them directly,
I will cover only those two.
Peer To Peer
Well, as you can see in the picture above, every
client sents packets to all other clients
This game is far more easier to develop and to
maintain. but there's one thing you should know
about this type of connection: Unless you're
going for a LAN(Network) game, don't use this
type of connection above 4 players! Yeah,
you might wonder why this is bold but you
really have to make sure you don't forget it.
If you start with Peer-Peer and find out that
it doesn't work you have to redesign everything
to support Client-Server.
Client/Server
Client-Server is the common type of connection
these days. Almost every online-game you're playing
is using this method. Games Like Quake, Unreal
Tournament, Subspace, Half-Life and whatever more,
uses this. the main reaons is that bandwith can
be more controlled. The server will make every
decision in the game (player dies, player hit,
etc); the server can decide if players need all
the data; use a database to keep track of player
stats;and much more thing which I won't explain
all. If you're going for an MMOG (Massive Multiplayer
Online Game) then this is the way to go.
3.
Sending messages
The
main thing about multiplayer programming is that
you get data to other players. In DirectPlay we
use "messages" for this. You can also call them
"packets", it's the same anyway. I'm going to
explain the initalisation code in two other tutorials.
But sending and receiving packets is virtually
the same. So I'm going to explain the sending
a bit in advance. This does not really matter.
I think it's better to explain it in detail here
so I can use it directly in the other tutorials.
First we should be able to "choose" what type
of message we will send. If you're not using any
system to do this, you won't be able to discard
two messages from each other. I'm always using
constants for this. It's also possible to use
an "Enum" for it but the capitals won't stay capital
if you use it in your project. That's why I use
constants, so I can immediatly see that there's
an error somewhere. Note that I declare those
constants as byte. You could make them longs of
course, but that will only make the identification
of the packet type take 4 bytes. And with a byte
you can have 256 different packets, which should
be enough for you. (if not go for integer, Range:
-32678 To 32677... should be enough, =-P)
//Put this in the declaration section of a module, or form
Option Explicit
Public Const MessageType1 As Byte = 1 '//1 will indentify messagetype1 in the receive part(we don't want a variant!)
Public Const MessageType2 As Byte = 2 '//2 will indentify messagetype2 in the receive part(we don't want a variant!)
|
|
|
|
Building up a message in DirectPlay8 is a lot
different from the way in DirectPlay7. But this
method is very efficient and allows you to control
the packet size very accurately. A message is
built up using a "byte buffer". This might sound
a bit complex but it ain't so hard at all. I recommend
that you create a sub for every type of message
you sent; your project will be much easier to
maintain this way.
Public Sub SendAMessage()
Dim bMSGBuffeR() as Byte '//Create a byte array, which will serve as a data holder.
Dim lngOffset() as Long '//This long will keep track of the bytes we have used (in total)
lngOffset = NewBuffer(bMSGBuffer) '//Create a new data buffer, and reset the offset number
'//Add the data to the buffer. You've got to get the length of the variable you're using, with "LenB"
'//You can't use "Len" because it will return the characters, not the bytes!
'//It will also increase the offset with the number of bytes returned by "LenB"
'//You can call this as much as you like... if you want to add something else, use the same line.
'//(but don't forget to change the variable "AValueorString")!)
'//If possible to specify the byte sze of the value to add directly, like in the 2nd method
AddDataToBuffer bMSGBuffer, MessageType1, LenB(MessageType1), lngOffset
AddDataToBuffer bMSGBuffer, MessageType2, SIZE_BYTE, lngOffset
'//Send the message. We are now assuming that this is a client. (from Client-Server! So no Playerid needed...)
'//"DPNSEND_NOLOOPBACK" is one of the many options you can use to sent the message.Be sure to check the sdk docs!
'//0 = The additional timeout value. I recommend that you leave this alone...
DirectPlayClient.Send bMSGBuffer, 0, DPNSEND_NOLOOPBACK
End Sub
|
|
|
|
There are several flags you can use for sending
data. Here I'm using "DPNSEND_NOLOOPBACK". This
will make sure the message won't be sent to myself
again. If it did, recursion would follow on some
cases. For example, I receive a packet with an
ID of 1 it will forward it to a client, so a new
packet will be sent with ID = 1. The server will
receive it's own message, so this will happen
over and over, till you get an error "Out of stack
space". For some detailed flag description I recommend
reading the DirectX8 SDK docs.
I have to add a small notice that "Send" is used
for DirectPlayClient, and "SendTo" for DirectPlayPeer
and DirectPlayServer. The differences are minimal,
you've only got an extra option to specify a destination
player/group. With DirectPlayClient you can only
send packets to the server, and that is what will
happen in the code above.
Everything part of your multiplayer code needs
to be optimized to the max. for example, when
a player leaves the game, I could let the server
send "PlayerWithALongName left the game", but
I could also send the PlayerID to the client,
and let the client figure out who left the game!
They have the name already stored in an array!
Now, When we send data we also should let someone
receive it, so this brings me to...
4.
Receiving messages
Another
important part of multiplayer programming that
you receive the data that has been sent by other
clients. After the data has been received, it
is ready to be processed in your multiplayer game.
It's recommended that you do this as fast as possible.
(The longer it takes, the more time difference
between the original sending time, and receiving
time) In DirectPlay7 you could receive messages
by "polling" the message count. If the message
count wasn't zero (0), there were messages in
the queue waiting to be processed. This method
was of course very inefficient. So that's why
we have the magical events in DirectPlay8!
The events are the same as the average form/ActiveX
events. But they won't trigger on mouseclicks,
they trigger on DirectPlay8 events. Now, just
to save you some trouble, DirectPlay8 for VB requires
you to add all events to your eventhandler
(Form, class). So you just can't simply add one
to receive. So just copy and paste it from the
DX8 SDK samples and remove everything unnecassary
you see. In the next sample, I'm not going to
paste all those events. I'll simply use Receive
only. (Unless Jack wants it different :o)
Private Sub DirectPlay8Event_Receive(dpnotify As DxVBLibA.DPNMSG_RECEIVE, fRejectMsg As Boolean)
'//If this event fires, we already have recieved something!
Dim lngOffset As Long '//Used to retrieve data from the received data buffer
Dim lngMsg As Byte '//Will contain the MSG Type we have received
'//In this sample I assume that we got a LONG! So in the code above it was a number, defined as long
Dim lngReceivedLong As Long
'//Get the first value (1 byte!). This will be our "header"
'//It will enable us to determine which message was sent!!
GetDataFromBuffer dpnotify.ReceivedData, lngMsg, SIZE_BYTE, lngOffset
'In
'//Select the message we're dealing with :o)
'//Again, demonstrating the method's to get the byte size. I recommand the first method (SIZE_Long)
Select Case lMsg
Case MessageType1
'//You've received MessgeType1, lngReceivedLong contains the number
GetDataFromBuffer dpnotify.ReceivedData, lngReceivedLong, SIZE_LONG, lOffset
Case MessageType2
'//You've received MessgeType2, lngReceivedLong contains the received number
GetDataFromBuffer dpnotify.ReceivedData, lngReceivedLong, LenB(lngReceivedLong), lOffset
End Select
End Sub
|
|
|
|
Well,
that is practically everything you should know
for multiplayer gaming. In my next tutorials I'm
going to show how to initialize DirectPlay8. Meanwhile
practice your Tic-Tac-Toe skills, because that's
gonna be the sample game!
|