ATTENTION READERS! Lucky's VB Gaming Site is no longer active. For updated game programming information and tutorials, please visit The Game Programming Wiki!
In-Game Messaging
Vegetarians beware, we're about to discuss the meat of DirectPlay: Messaging. This is
not for the faint of heart! Get out the BBQ because we're gonna cook us up a mess of juicy
code!
Mmmmmm, DirectPlay burger.
There are two types of messages that we'll encounter, user-defined messages, and
system messages. A user-defined message is one that we've created ourselves for the
purposes of exchanging game related information between players. A system message is one
that's been created by DirectPlay itself for the purpose of maintaining DirectPlay state
information.
For example, a user-defined message might be used to send ball movement data between the
two players in a game of NetPONG. A system message could be used by DirectPlay to inform
the host that a new player has joined the session.
System messages are sent automatically by DirectPlay when appropriate, but we can create
and send user-defined messages whenever we so choose:
Const MSG_CHATTEXT = 0
Dim dpMsg As DirectPlayMessage 'Create a message object
Set dpMsg = dp.CreateMessage
dpMsg.WriteLong MSG_CHATTEXT
dpMsg.WriteString strChatText
dp.SendEx lngPlayerID, DPID_ALLPLAYERS, DPSEND_GUARANTEED, dpMsg, 0, 0, 0
Enter the DirectPlayMessage object. Initialize a new one with the
DirectPlay4.CreateMessage method and then we're ready to go. DirectPlayMessage
objects expose methods that allow us to write data using various data types (Long, Integers,
Strings, whatever). In the code above, we're first writing a Long using the constant
MSG_CHATTEXT. It is customary to start off a message with a Long indicating the message
type (you must create and keep track of these constants yourself!) so that it can be
sorted and handled appropriately--as you'll see later. After we've
written the message type, we can proceed to fill the message with our desired data. In
this case, we're sending a "chat string" which could be used for player-player communication
during a game, for example. Assume that "strChatText" contains the string we're trying to
send.
The DirectPlay4.SendEx method is used to actually send the message object after it
has been loaded with data. The first parameter accepted by SendEx is the "playerID"
of the sender.. remember, we stored this value back when we created the player? The second
parameter indicates which player(s) we would like to send this message to. We can specify
any single player (or "group"... see SDK!) or use the DPID_ALLPLAYERS constant to send the
message to every player in the session.
The third parameter is where we indicate the "Send Flags" we'd like to use. There are
quite a few, including DPSEND_ENCRYPT and DPSEND_SIGNED, but for the most part we'll
likely be using DPSEND_GUARANTEED and DPSEND_ASYNC. Use DPSEND_GAURANTEED when you need
to be sure that your message gets through, use DPSEND_ASYNC when you're sending messages
that are not vital, and are possibly made obsolete by subsequent messages.
For example, when the host decides to "start" the game, guaranteed messages should be
sent to all of the players in the session since it is imperative that they receive this
information. Once inside a game, however, data is often sent in a redundant fashion. For
example, in a game of StarCraft (or any other Real Time Strategy game), if the
player loses one packet describing the current position of his enemy's troops, it is of
little consequence since another packet will soon be along to replace it, and the previous
information would have been obsolete even if it had been received. In situations like this,
sending messages asynchronously is advantageous since they can be sent somewhat more
quickly and in a rapid-fire fashion.
The fourth parameter accepted by the DirectPlay4.SendEx method must be the
DirectPlayMessage object that we wish to send. The fifth, sixth, and seventh
parameters are used for Priority, TimeOut, and Context. They will not be discussed here.
There's just too darn much stuff to get through!
So, you know how to pack and send a message... it seems logical now to learn how to
receive and unpack one!
Dim lngFromPlayerID As Long
Dim lngToPlayerID As Long
Dim lngMsgType As Long
Dim dpMsg As DirectPlayMessage
Dim lngMsgCount As Long
lngMsgCount = dp.GetMessageCount(lngPlayerID)
Do While lngMsgCount > 0
Set dpMsg = dp.Receive(lngFromPlayerID, lngToPlayerID, DPRECEIVE_ALL)
lngMsgType = dpMsg.ReadLong()
lngMsgCount = lngMsgCount - 1
First, we use the DirectPlay4.GetMessageCount method to retrieve the number of
messages we have waiting for us. We must pass our "PlayerID" to the GetMessageCount
function so that DirectPlay knows which player we're receiving messages for (yes, you can
have more than one player active at a time!).
Next, we loop through and process each of these messages one by one. A new
DirectPlayMessage object is set up, and is initialized by the
DirectPlay4.Receive method which receives the next message in the queue. The
Receive method returns some other data in addition to the DirectPlayMessage
object. If we pass it a couple of Longs, it returns the "PlayerID" of the sender as well
as the "PlayerID" of the intended recipient. The third parameter, the receive flags,
allows us to indicate which messages we'd like to receive. Passing the constant
DPRECEIVE_ALL indicates that we'd like to receive ANY message. With other constants,
however, we can indicate that we'd like to receive messages only from a certain player
(ie. the value of the first parameter), or messages sent to a certain player (second
parameter).
Moving right along... once we've obtained the new message object (however we've done it),
we can extract the first Long from it. This Long should contain the "message type"
value described previously. For example, a value of MSG_CHATTEXT would indicate that this
was intended as a chat text message, since this is the constant we defined earlier for
this purpose! Also, we must decrement our message counter so that we only loop as many
times as we have messages to process.
Now that we have all of this info from the message, we can process it. First we must
distinguish between system messages and user-defined messages:
If lngFromPlayerID = DPID_SYSMSG Then
'...handle system messages...
Else
'...handle user-defined messages...
End If
The "lngFromPlayerID" variable now contains the "PlayerID" of the message's sender. This
value will be equal to the constant DPID_SYSMSG if this message was NOT user defined. We
can therefore use this IF statement to handle system messages, and use an ELSE to handle
any other case--a user-defined message.
Whether it be a system or user-defined message, it can be further sorted according to
its message type (the first Long). A "Select Case" statement is ideal for this:
Select Case lngMsgType
Case DPSYS_DESTROYPLAYERORGROUP, DPSYS_CREATEPLAYERORGROUP
'...a new player has joined/left the session, take action...
End Select
Above we witness the handling of two possible system messages. If a player joins or leaves
a session, this code will be activated. We can then take steps to accommodate the occurrence.
Select Case lngMsgType
Case MSG_CHATTEXT
'...we have receive chat text, take action...
strChatText = dpMsg.ReadString()
End Select
Here we see the handling of a user-defined message. Once we know that this is a chat text
message, we can extract the text string it contains. This can then be stored in some
variable (here we use a predefined string, "strChatText") and used appropriately.