Sending and Receiving Data


When it comes right down to it, a network session is a way for the various players within the session to transfer data to one another. That is the heart of a network game. So what you need to do first is figure out the way to send some data to another player. Look at the method in Listing 16.9 for the implementation of sending the player's location and orientation to the other peer member.

Listing 16.9. Sending Position and Orientation
 /// <summary> /// Will send the player's position and orientation to the other peer /// </summary> public bool SendPlayerLocation(Vector3 position, float rotation) {     // Check to see if we can send this message     if (CanSendData())     {         // Create a new network packet to send the player location         NetworkPacket packet = new NetworkPacket(PacketSizePlayerLocation);         // Write the message         packet.Write((byte)NetworkMessage.UpdatePlayerLocation);         packet.Write(position);         packet.Write(rotation);         // Now send the message         networkDevice.SendTo(0, packet, 0, SendFlags.NoLoopback |                              SendFlags.NonSequential);         // Message sent         return true;     }     // The data cannot be sent, too many messages in queue     return false; }. 

The position and orientation of the player are sent quite often throughout the game's lifetime. Because they are, this message doesn't have to be delivered every time. First, you check whether the network is available to send data (which I discuss in just a moment), and if so, you create a network packet of the appropriate size. You then write the message you're sending, followed by the data (or in this case, two pieces of data). Finally, you send the piece of data out, passing in the NoLoopback flag as well as the NonSequential flag. Because you're the one sending the message, you won't need to receive the sent data back, and it doesn't really matter what order these pieces are received in. You then return true if the method was able to send the data and false otherwise.

What about the method to check whether you're able to send the data? The implementation is pretty simple, as you see in Listing 16.10.

Listing 16.10. Checking Network Statistics
 /// <summary> /// Uses the send queue to determine if data can be sent /// </summary> /// <returns></returns> private bool CanSendData() {     int numMessages = 0;     int numBytes = 0;     // Get the number of messages     networkDevice.GetSendQueueInformation(remotePlayerId, out numMessages,         out numBytes, GetSendQueueInformationFlags.PriorityNormal);     // Don't send if the message queue has more than 5 members     return (numMessages < MaximumMessagesInQueue); } 

DirectPlay is smart enough to queue up data if the computer you are sending the data to cannot process it fast enough. Although that's a great feature to have, it can also cause problems if you continue to send data at a rate too fast for the remote system to maintain. To prevent this problem, you'll build a "throttling" system that allows you to lower (or raise) the amount of data that you're sending. The point of this method is to detect the number of messages waiting to be sent in the queue; if there are more than the maximum you've allowed (five, in this case), you return false, signifying that you shouldn't send data. What about some messages that you want to be guaranteed, though, such as the firing of the weapon? See Listing 16.11 for an implementation of this type of method.

Listing 16.11. Sending the Fire Message
 /// <summary> /// Will send the player's position and orientation to the other peer /// </summary> public bool SendFireMessage() {     // Create a new network packet to send the player location     NetworkPacket packet = new NetworkPacket(PacketSizePlayerFire);     // Write the message     packet.Write((byte)NetworkMessage.PlayerFired);     // Now send the message     networkDevice.SendTo(0, packet, 0, SendFlags.NoLoopback |                          SendFlags.Guaranteed);     // Since this is guaranteed, we should always send it, but let the game     // engine slow down data transfer     return CanSendData(); } 

Notice that the packet is created and sent no matter what, and when it is sent, the Guaranteed flag is included. You still want the game engine to realize whether network data is being sent too fast, so you still do the check and use that as the return value of the method. You want to send two other messages to the other player in this session; see Listing 16.12 for the implementations.

Listing 16.12. Sending Data
 /// <summary> /// Will send the player's position and orientation to the other peer /// </summary> public bool SendTurretInfo(float turretRotation, float gunRotation) {     // Check to see if we can send this message     if (CanSendData())     {         // Create a new network packet to send the player location         NetworkPacket packet = new NetworkPacket(PacketSizePlayerTurret);         // Write the message         packet.Write((byte)NetworkMessage.UpdateTankTurret);         packet.Write(turretRotation);         packet.Write(gunRotation);         // Now send the message         networkDevice.SendTo(0, packet, 0, SendFlags.NoLoopback |                              SendFlags.NonSequential);         // Message sent         return true;     }     // The data cannot be sent, too many messages in queue     return false; } /// <summary> /// Will send the player's position and orientation to the other peer /// </summary> public bool SendNewColor(TankColors newcolor) {     // Create a new network packet to send the player location     NetworkPacket packet = new NetworkPacket(PacketSizeColorChange);     // Write the message     packet.Write((byte)NetworkMessage.UpdateTankTexture);     packet.Write((byte)newcolor);     // Now send the message     networkDevice.SendTo(0, packet, 0, SendFlags.NoLoopback |                          SendFlags.Guaranteed);     // Since this is guaranteed, we should always send it, but let the     // game engine slow down data transfer     return CanSendData(); } 

These implementations are just like the two previous ones; they just send different data. Now that you've got the data being sent, you need a way to handle receiving that data. See the event handler in Listing 16.13.

Listing 16.13. Receiving Data
 /// <summary> /// Fired when data is received from the other peer in the session /// </summary> private void OnDataReceive(object sender, ReceiveEventArgs e) {     System.Diagnostics.Debug.Assert(remotePlayer != null,                                     "Remote player should not be null.");     using(NetworkPacket data = e.Message.ReceiveData)     {         NetworkMessage msg = (NetworkMessage)data.Read(typeof(byte));         switch(msg)         {             case NetworkMessage.UpdatePlayerLocation:                 remotePlayer.Position = (Vector3)data.Read(typeof(Vector3));                 remotePlayer.Rotation = (float)data.Read(typeof(float));                 break;             case NetworkMessage.UpdateTankTurret:                 remotePlayer.UpdateTurret((float)data.Read(typeof(float)),                     (float)data.Read(typeof(float)));                 break;             case NetworkMessage.UpdateTankTexture:                 remotePlayer.Color = (TankColors)data.Read(typeof(byte));                 break;             case NetworkMessage.PlayerFired:                 remotePlayer.RaiseFireEvent();                 break;         }     } } 

The first thing that happens is that the remote player is checked for null. It should never be null, which is why the assert is there, just in case. Notice that the event handler gives you a network packet just like the one you used to send the data. First, you read a single byte from the packet to get the message type, and then, you do your switch statement on the possible values. In each case, you update the remote player with the appropriate action, by either updating a property or calling a method. One of these methods hasn't even been defined yet. Add the method in Listing 16.14 to your player class now.

Listing 16.14. Setting Player Properties
 /// <summary> /// Updates the gun turret and barrel /// </summary> public void UpdateTurret(float turret, float gun) {     gameTank.GunTurretAngle = turret;     gameTank.GunBarrelAngle = gun; } 

You could have just as easily used two properties rather than a method, but because they're both updated simultaneously every time, a single method was "cleaner." You might have noticed that there is nowhere the remote player variable can be updated either. Add the following method to the networking class to allow it to be:

 public void UpdatePlayer(Player p) {     remotePlayer = p; } 

Cleaning Up Resources

Last but not least, you probably noticed that the class implements IDisposable just as everything else does. You want to ensure that your networking session can be cleaned up easily, so add the Dispose implementation in Listing 16.15 to your networking class.

Listing 16.15. Setting Player Properties
 /// <summary> /// Done with this object, clean it up /// </summary> public void Dispose() {     // Clean up all data here     if (networkDevice != null)     {         networkDevice.Dispose();     }     if (deviceAddress != null)     {         deviceAddress.Dispose();     } } 



Beginning 3D Game Programming
Beginning 3D Game Programming
ISBN: 0672326612
EAN: 2147483647
Year: 2003
Pages: 191
Authors: Tom Miller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net