Main Site Links Resources Tutorials
News VB Gaming Code Downloads DirectX 7
Contact Webmaster VB Programming Product Reviews DirectX 8
  General Multimedia Articles DirectX 9
      Miscellaneous

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!

DirectX 4 VB 2000 Jack Hoxley. All rights reserved.
Reproduction of this site and it's contents, in whole or in part, is prohibited,
except where explicitly stated otherwise.
Design by Mateo
Contact Webmaster