One of the cool new features appearing in a lot of modern engines is the ability to work in an editor on the PC and have the level changes replicate on a running console build of the game in real-time. For commercial games this often happens over the high speed USB connection, however due to the cross platform support in XNA, we can replicate this [in single player games/modes] using a System-Link connection and the XNA networking libraries.
The first step is deciding which side will create the session and which side will join. Considering we only have the option of working with the Xbox 360, and you might not want to include any debug views or information in your Xbox code, the option I chose is to have the Xbox create the session. The code is compatible on both platforms so if you’re willing to add some system to find and join an active session, you can easily change this to create the session on the PC. For the purposes of this article the session will only contain two “Players” and the Xbox will create whilst the PC side joins.
Creating
We start by creating a session, in XNA you can do this with the NetworkSession.Create() command, which creates a NetworkSession object that maintains the session. Then specify session.AllowJoinInProgress to be true, this lets you create the session and at any time join or rejoin from the PC side.
_session = NetworkSession.Create(NetworkSessionType.SystemLink, 1, 2); _session.AllowJoinInProgress = true;
Another key thing to note here is that you will want to specify the network session type as SystemLink, this lets you sign in with your Creators Club gamer tag on the Xbox, and use a local player on the PC side – no extra accounts!
We then specify a maximum of one local gamer on the Xbox side, and a total of two gamers in the session. (Change as needed)
Finally we handle the session.GamerJoined event and in the handler we start the game if the current session state is set to Lobby.
Receiving
At this time you might want to also create a PacketReader and PacketWriter, as we will be using these to do our communication. These can be created at any time, and are not tied to a session, so it is a good idea to create them once and re-use over multiple sessions/games.
How you want to receive the data is up to you, however I chose to allow the Xbox game to register some callbacks for named commands. That way the PC side can specify the name of the command and send data along, which can be passed through as a byte array. This is rather simple to setup, you simply maintain a Dictionary<string, Action<byte[]>> that contains all of the callback delegates and names. Then when a packet comes in it will have the name of the callback as well as the byte[] data, which lets you gets the callback from the Dictionary and call it, passing in the data as is.
On the PC side you simply send the callback name, length of the byte array and the bytes themselves, ready for the Xbox to accept and use.
I further split this into “Commands” and “Data”, to provide a clear separation of things like console commands, and raw data, but both use the same system of a Action<byte[]> delegate callback.
Joining
When joining, the PC needs to do two things:
- Search for sessions
- Join a chosen session
The NetworkSession.Find() method provides a list of available sessions currently running over SystemLink, which is usually going to be just one, however if you’re working in a larger team with multiple systems on the same network, you might want to list them and display the host gamertag, so that you can choose the right one.
Once you have chosen the network session, you pass that to NetworkSession.Join() which creates a NetworkSession object that you can use just like you do on the server – except this time you don’t worry about starting the game.
Updating
Once the session has been created and is up and running, on both platforms you need to call session.Update() in your update loop. This handles the networking side of things and gets/sends the packets as needed.
Once you have updated, you can make use of the PacketReader and PacketWriter objects to receive and send packets respectively. To receive the data, you need to first check if there is data available by checking the session.LocalGamers[0].IsDataAvailable flag. If there is data available, you can call session.LocalGamers[0].ReceiveData() passing in the PacketReader which you can then use to parse the packet and call the appropriate callback.
If you have data you want to send, you just need to call session.LocalGamers[0].SendData(), and pass in the PacketWriter with the data you want to send.
In the background XNA handles the network magic, so you just have to worry about handling the data on either end.
Code
I didn’t have time to put up a complete code sample for this article, however I hope you understand the general technique. Code for something like this really needs to be tailored to your purpose. If you are just writing a debug console or tweak system, you can just use the command system mentioned below. However if you want to write a sync system for your editor, with camera sync and everything else, you will probably want to change how you send and receive data to optimise for what you are sending.
Create
_session = NetworkSession.Create(NetworkSessionType.SystemLink, 1, 2);
_session.AllowHostMigration = false;
_session.AllowJoinInProgress = true;
_session.GamerJoined += (s, e) =>
{
if (_session.SessionState == NetworkSessionState.Lobby)
_session.StartGame();
};
_session.GamerLeft += (s, e) => _session.EndGame();
Find
AvailableNetworkSessionCollection sessions = NetworkSession.Find(NetworkSessionType.SystemLink, 1, null);
Join
(‘session’ is an AvailableNetworkSession taken from the collection in Find)
_session = NetworkSession.Join(session);
Update
if (_session != null)
{
_session.Update();
if (_writer.Length > 0)
_session.LocalGamers[0].SendData(_writer, SendDataOptions.None);
while (_session != null && _session.LocalGamers[0].IsDataAvailable)
{
NetworkGamer sender;
int numBytes = _session.LocalGamers[0].ReceiveData(_reader, out sender);
if (numBytes > 0 && !sender.IsLocal)
{
string methodName = _reader.ReadString();
int numBytes = _reader.ReadInt32();
var data = _reader.ReadBytes(numBytes);
Action method;
if (!string.IsNullOrEmpty(methodName) && _commands.TryGetValue(methodName, out method))
method(data);
}
}
}
Sending
public void SendCommand(string methodName, byte[] data)
{
_writer.Write(methodName.ToLower());
if (data != null)
{
_writer.Write(data.Length);
_writer.Write(data);
}
else
{
_writer.Write(0);
}
}
You can follow any responses to this entry through the RSS 2.0 You can leave a response, or trackback.
