How To: Build a Simple Networked Game on Zune

Demonstrates networking functionality on Zune.

This example builds on the code shown in Your First Game: Microsoft XNA Game Studio in 2D. The code from that sample is expanded to include some basic game play and simple networking. The following sections show you how to transform the simple example code into a networked game on Zune:

  • The Complete Sample
  • Adding Networking Objects
  • Creating or Joining an Existing Networked Game
  • Reading and Writing Network Packets
  • Using Network Events
  • Encapsulating Code

Note

Not all APIs are available on Zune. See the Supported Platforms section on individual reference pages to check for Zune support. If Zune is not on the supported platforms list, the API is not supported. If listed, there is support for the API.

Network connections between Zunes do not form instantaneously. When running this example, either creating or joining a network session will take a few moments.

The Complete Sample

The code in this tutorial illustrates the technique described in the text. A complete code sample for this tutorial is available for you to download, including full source code and any additional supporting files required by the sample.

Download NetworkSimpleGame_Sample.zip.

Adding Networking Objects

First, add necessary networking objects to the game class that derives from Game.

// networking objects
NetworkSession networkSession;
PacketWriter packetWriter = new PacketWriter();
PacketReader packetReader = new PacketReader();

Add a GamerServicesComponent GamerServicesComponent in the game constructor.

Components.Add(new GamerServicesComponent(this));

Creating or Joining an Existing Networked Game

Two types of networks are supported on Zune, NetworkSessionType.SystemLink and NetworkSessionType.Local. This example creates a NetworkSessionType.SystemLink network using the Create method. This example game allows players to join in the middle of game play, and it allows for a maximum of eight players.

networkSession = NetworkSession.Create(NetworkSessionType.SystemLink, maxLocalGamers, maxGamers);
HookNetworkSessionEvents();

Reading and Writing Network Packets

This method updates the server on the status of the game. The code sends the number of gamers in the game, the enemy location, and the location of each player sprite. Notice the order that the data is sent, and that the data is read back in reverse order in the method that executes on the clients.

// send a packet that first indicates how many players it has data for
packetWriter.Write(networkSession.AllGamers.Count);

// send a packet that has position of enemy on server
packetWriter.Write(enemyPos);
packetWriter.Write(enemySpeed);

// send to all gamers in network 
foreach (NetworkGamer gamer in networkSession.AllGamers)
{
    // look up this player
    Rodent r = gamer.Tag as Rodent;

    // write state of mouserat to the output network packet
    packetWriter.Write(r.Position);
    packetWriter.Write(r.Dead);
}

// send the combined data for all players to everyone in the session
LocalNetworkGamer server = (LocalNetworkGamer)networkSession.Host;
server.SendData(packetWriter, SendDataOptions.InOrder);

This method updates a local game based on data from the server. Again, note the order of data received.

// Keep reading as long as incoming packets are available.
while (gamer.IsDataAvailable)
{
    NetworkGamer sender;

    // Read a single packet from the network.
    gamer.ReceiveData(packetReader, out sender);

    // If a player has recently joined or left, it is possible the server might have sent 
    // information about a different number of players than the client currently knows 
    // about. If so, we will be unable to match up which data refers to which player. The 
    // solution is just to ignore the packet for now: this situation will resolve itself 
    // as soon as the client gets the join/leave notification.
    if (networkSession.AllGamers.Count != packetReader.ReadInt32())
        continue;

    // read the position of the enemy 
    enemyPos = packetReader.ReadVector2();
    enemySpeed = packetReader.ReadVector2();

    // next read the position of all current network gamers
    foreach (NetworkGamer remoteGamer in networkSession.AllGamers)
    {
        Rodent r = remoteGamer.Tag as Rodent;
        r.Position = packetReader.ReadVector2();
        r.Dead = packetReader.ReadBoolean();
    }
}

In this example, the sprite added to the game in Your First Game: Microsoft XNA Game Studio in 2D still moves around the screen without interruption from user input. The addition of networked play allows for each player to control one sprite. This method updates the position of the local gamer based on inputs and sends out update packets including the new local player position.

int MaxX = graphics.GraphicsDevice.Viewport.Width - r.Width;
int MinX = 0;
int MaxY = graphics.GraphicsDevice.Viewport.Height - r.Height;
int MinY = 0;

// check pad input
if (IsPressed(Buttons.DPadRight))
{
    if (!(r.PositionX > MaxX))
    {
        r.PositionX += 1.2f; // move mouserat right
    }
}
else if (IsPressed(Buttons.DPadLeft))
{
    if (!(r.PositionX < MinX))
    {
        r.PositionX -= 1.2f; // move mouserat left
    }
}
else if (IsPressed(Buttons.DPadUp))
{
    if (!(r.PositionY < MinY))
    {
        r.PositionY -= 1.2f; // move mouserat up
    }
}
else if (IsPressed(Buttons.DPadDown))
{
    if (!(r.PositionY > MaxY))
    {
        r.PositionY += 1.2f; // move mouserat down
    }
}

// check for collison with the enemy cat, update dead property
CheckIfEaten(r);

// if not the server, send this update info to all other network gamers
if (!networkSession.IsHost)
{
    // write updated position and life status to network packet
    packetWriter.Write(r.Position);
    packetWriter.Write(r.Dead);

    // send to server
    gamer.SendData(packetWriter, SendDataOptions.InOrder, networkSession.Host);
}

This method updates the position of other players.

// Keep reading as long as incoming packets are available.
while (gamer.IsDataAvailable)
{
    NetworkGamer sender;

    // Read a single packet from the network.
    gamer.ReceiveData(packetReader, out sender);

    if (!sender.IsLocal)
    {
        Rodent r = sender.Tag as Rodent;
        r.Position = packetReader.ReadVector2();
        r.Dead = packetReader.ReadBoolean();
    }
}

Using Network Events

This method hooks up event handlers. It is called when you create the network.

void HookNetworkSessionEvents()
{
    networkSession.GamerJoined += GamerJoinedEventHandler;
    networkSession.SessionEnded += SessionEndedEventHandler;
}

These are the event handler methods.

void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e)
{
    // new gamer, add new rodent player object
    e.Gamer.Tag = new Rodent(this);
    Components.Add(e.Gamer.Tag as Rodent);
}
void SessionEndedEventHandler(object sender, NetworkSessionEndedEventArgs e)
{
    DrawMenuMessage("Network Session Has Ended.");
    networkSession.Dispose();
    networkSession = null;
}

Encapsulating Code

The original simple game example contained all functionality within one class. Encapsulating logic and data for the added sprite into in new class adds organization. Each game instance in a networked play can use the new class. The new class is derived from DrawableGameComponent.

class Rodent : Microsoft.Xna.Framework.DrawableGameComponent
{
    Texture2D rodentTexture;     // alive texture
    Texture2D deadRodentTexture; // dead texture
    SpriteBatch spriteBatch;     // spritebatch for drawing sprites to the screen

    bool dead;         // is this rodent dead or alive
    Vector2 rodentPos; // rodent position

    # region properties

    public bool Dead
    {
        get
        {
            return dead;
        }
        set
        {
            dead = value;
        }
    }

    public Vector2 Position
    {
        get
        {
            return rodentPos;
        }
        set
        {
            rodentPos = value;
        }
    }

    public float PositionX
    {
        get
        {
            return rodentPos.X;
        }
        set
        {
            rodentPos.X = value;
        }
    }

    public float PositionY
    {
        get
        {
            return rodentPos.Y;
        }
        set
        {
            rodentPos.Y = value;
        }
    }

    public int Width
    {
        get
        {
            return this.rodentTexture.Width;
        }
    }

    public int Height
    {
        get
        {
            return this.rodentTexture.Height;
        }
    }

    #endregion

    public Rodent(Game game)
        : base(game)
    {
        dead = false;
        rodentPos = new Vector2(204.0f, 287.0f);
    }

    public override void Initialize()
    {
        base.Initialize();
    }

    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
        rodentTexture = Game.Content.Load<Texture2D>("mouserat");
        deadRodentTexture = Game.Content.Load<Texture2D>("deadmouserat");

        base.LoadContent();
    }

    protected override void UnloadContent()
    {
        base.UnloadContent();
    }

    public override void Draw(GameTime gameTime)
    {
        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
        if (dead)
        {
            spriteBatch.Draw(deadRodentTexture, rodentPos, Color.White);
        }
        else
        {
            spriteBatch.Draw(rodentTexture, rodentPos, Color.White);
        }
        spriteBatch.End();

        base.Draw(gameTime);
    }

}

See Also

Concepts

Your First Game: Microsoft XNA Game Studio in 2D
Zune Programming Considerations

Reference

Guide Class
NetworkSession Class
NetworkSessionType Enumeration
GamerServicesComponent Class
NetworkSession.Create Method