Turn-based Networking in iTorque 2D
by Ben Steffen · 12/16/2011 (12:51 pm) · 6 comments
Overview
So this is a little resource I've been meaning to post for making multiplayer games with iTorque 2D. The class is called TurnBasedMatchManager, and provides the functionality for turn-based networked games played through Apple's Game Center.In addition to the TurnBasedMatchManager source files below, I've included a short tutorial on how to use the class in a game. I've also included an example game, Tic-Tac-Toe, that uses TurnBasedMatchManager.
I've not done extensive testing other than making sure it works for a simple game. If you run into problems or have suggestions for improvements please post them and I'll update this resource.
The TurnBasedMatchManager source files and Tic-Tac-Toe demo are packaged in the following zip:
GKResource.zip
For more details on the inner workings of turn-based matches, consult the documentation for these classes:
GKTurnBasedMatch
GKTurnBasedMatchmakerViewController
Setting up the engine
You will need to add the following two source files to your engine/source/platformiPhone directory:TurnBasedMatchManager.h
TurnBasedMatchManager.mm
You will also need to make the following to TGBAppDelegate.mm at the beginning of the function applicationDidFinishLaunching:
#ifdef TORQUE_ALLOW_GAMEKIT
[application registerForRemoteNotificationTypes: (UIRemoteNotificationType)(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
#endif //TORQUE_ALLOW_GAMEKITThis will allow your application to receive remote notifications, which are necessary for getting game invites, end-turn events, and end-match events.
Adding turn-based networking to your game
Once you've made the necessary engine changes, you're ready to add networking to a specific game project.
Project Settings
First of all you must setup your project to use Game Center. You do this under Project > iOS Feature Builder by checking Enable Game Center and then clicking Apply.
Game Script
The following script changes should be made in game.cs or a script called from it.
Initialize the match manager and Game Center
At the top of the file, add the following global variables:
// Match Parameters $matchMinPlayers = 2; $matchMaxPlayers = 3;
These variables will set the minimum and maximum number of players that can join a match.
At the bottom of function startGame(%level) add
// Execute the following code on iOS only
if($platform $= "iphone" || $platform $= "ipad" || $platform $= "iphone4")
{
// Initialize Game Center
initGameCenter();
// Initialize Turn Based Match Manager
initMatchManager();
// Set parameters for the match: minPlayers, maxPlayers, playerGroup, playerAttributes, playersToInvite
setMatchRequestParameters($matchMinPlayers, $matchMaxPlayers, 0, 0, "");
}Clean up at end of game
Before we forget, lets add code to clean things up at the end of the game. In your endGame() function add the following code to clean up the match manager and Game Center.
if($platform $= "iphone" || $platform $= "ipad" || $platform $= "iphone4")
{
releaseMatchManager();
closeGameCenter();
}Setting the Network Object
The match manager uses a ScriptObject to store the fields that are transmitted and stored on the server. You access this object with function getNetworkObject().
You will need to create methods to set the fields of the ScriptObject from your game state, and conversely, set your game state from the ScriptObject. The following are examples of methods that do this. Your methods could be named and implemented differently.
//---------------------------------------------------------------------------------------------
// pushNetworkData
// Defines the networkScriptObject fields to be sent over the network and sets the values of those
// fields from the engine.
//---------------------------------------------------------------------------------------------
function pushNetworkData()
{
// Get the network ScriptObject
%networkObject = getNetworkObject();
// Set the fields to be transmitted
%networkObject.myVariableName = %myVariableName;
}
//---------------------------------------------------------------------------------------------
// pullNetworkData
// Sets engine values from fields in the networkScriptObject
//---------------------------------------------------------------------------------------------
function pullNetworkData()
{
// Get the network ScriptObject
%networkObject = getNetworkObject();
// Check to make sure the dynamic fields have been set
if ( %networkObject.getDynamicFieldCount() == 0 )
{
echo("No dynamic fields have been set for network object. Setting network object to current game state.");
pushNetworkData();
return;
}
%myVariableName = %networkObject.myVariableName;
}Display the Game Center matchmaking view controller
The TurnBasedMatchManager is set up to use the GKMatchmakerViewController for setting up and joining matches. If you want to use your own custom match-making interface, you can, but you will have to modify the engine code.
To display the Game Center, you will need to call
showMatchMaker();
This could be added to a button in a start menu. Important: The local player must be authenticated with the Game Center or the matchmaker won't launch. initGameCenter() handles authetication, but the process may take a few seconds, so don't call showMatchMaker() immediately after the initialization code.
Matchmaker view controller callbacks
Implement the following callbacks from the matchmaker view controller. You can just add them to game.cs.
//---------------------------------------------------------------------------------------------
// onMatchmakerDidFail
// Sent from the engine when an when the Game Center matchmaker fails with an error.
//---------------------------------------------------------------------------------------------
function onMatchmakerDidFail()
{
echo("onMatchmakerDidFail called...");
// Add your implementation here...
}
//---------------------------------------------------------------------------------------------
// onQuitFromGameCenter
// Sent from the engine when a match is removed from the Game Center.
//---------------------------------------------------------------------------------------------
function onQuitFromGameCenter()
{
echo("onQuitFromGameCenter called...");
// Add your implementation here...
}
//---------------------------------------------------------------------------------------------
// onMatchmakerWasCancelled
// Sent from the engine when the Game Center matchmaker is cancelled without selecting a match.
//---------------------------------------------------------------------------------------------
function onMatchmakerWasCancelled()
{
echo("onMatchmakerWasCancelled called...");
// Add your implementation here...
}
//---------------------------------------------------------------------------------------------
// onInviteFromGameCenter
// Sent from the engine when an invite was received.
//---------------------------------------------------------------------------------------------
function onInviteFromGameCenter()
{
echo("onInviteFromGameCenter called...");
// Add your implementation here...
}
//---------------------------------------------------------------------------------------------
// onDidFindMatch
// Sent from the engine when a match has been found using the Game Center matchmaker.
//
// %matchDataExists - Contains "1" if the match data already exists, indicating that the game
// state should be loaded from the network object. If set to "0" the match
// data doesn't exist yet, and the client should initialize the game state
// and set the network object from that.
//---------------------------------------------------------------------------------------------
function onDidFindMatch(%matchDataExists)
{
echo("onDidFindMatch called...");
//-------------------------------------------------------
// Your code will add more implementation to this method
// but the following are some code snippets you may
// find useful in your own code.
//-------------------------------------------------------
// Get a list containing the IDs of all the players in the match
%participants = getMatchParticipants();
// Get the ID of the local participant
%localParticipant = getLocalParticipant();
// Get the ID of the participant who currently has the turn
%currentParticipant = getCurrentParticipant();
// Set the turn order (the next participant after you)
%nextParticipant = someFunctionToDetermineTheNextParticipant(); // How you set up your turn order is up to you
setNextPlayer(%nextParticipant);
// Attempt to load game state from the network object if the game already exists.
// Otherwise, initialize the game state and set the network object from the new game state.
if(%matchDataExists $= "1")
{
echo("Game data exists. Loading game state from network object.");
pullNetworkData();
}
else
{
echo("Game data does not exist yet. Initializing game state.");
pushNetworkData();
}
}The most important method here is onDidFindMatch. This is called when you select a match in the matchmaker, both when hosting a match and when joining someone else's.
Here is the breakdown of what's going on in this method:
// Get a list containing the IDs of all the players in the match %participants = getMatchParticipants(); // Get the ID of the local participant %localParticipant = getLocalParticipant(); // Get the ID of the participant who currently has the turn %currentParticipant = getCurrentParticipant();These lines are just in here for reference since they are methods you will most likely need for your game at some point.
setNextPlayer(%nextParticipant);This line sets the next player that gets the turn after the local player ends their turn. The match manager stores the next player so you only need to call this once unless your turn order changes. The nextPlayer is also used when a player quits the match, so the match knows the next player to hand the turn off to, so be sure to set it as soon as possible.
// Attempt to load game state from the network object if the game already exists.
// Otherwise, initialize the game state and set the network object from the new game state.
if(%matchDataExists $= "1")
{
echo("Game data exists. Loading game state from network object.");
pullNetworkData();
}
else
{
echo("Game data does not exist yet. Initializing game state.");
pushNetworkData();
}This last code block uses the %matchDataExists flag to check whether the match has existing network data that should be loaded. The match data should exist unless you are the first player to take a turn in the match.Match manager callbacks
We've added callback definitions for the Game Center view controller, but still need to add some more that can be fired during a match. Add them to the end of game.cs.
//---------------------------------------------------------------------------------------------
// onMatchEnded
// Sent from the engine when the match is ended by another player winning.
//---------------------------------------------------------------------------------------------
function onMatchEnded()
{
echo("onMatchEnded called...");
// Add your implementation here...
}
//---------------------------------------------------------------------------------------------
// onPlayerQuit
// Sent from the engine when a player quits the game.
//---------------------------------------------------------------------------------------------
function onPlayerQuit(%inTurn)
{
echo("onPlayerQuit called...);
// Add your implementation here...
}
//---------------------------------------------------------------------------------------------
// onTurnEvent
// Sent from the engine when another player ends their turn
// (won't be sent to the player who ended their turn).
//---------------------------------------------------------------------------------------------
function onTurnEvent()
{
echo("onTurnEvent called...");
// Add your implementation here...
}
//---------------------------------------------------------------------------------------------
// onError
// Sent from the engine to alert the script when an error occurs in certain engine functions.
//
// %methodName - String containing the name of the engine method that sent the error.
// %message - The full error description.
//---------------------------------------------------------------------------------------------
function onError(%methodName, %message)
{
echo("onError called...");
echo("methodName: " @ %methodName);
echo("message: " @ %message);
}Ending Turns
If it is currently the localPlayer's turn, you can end their turn by calling
endTurn("You message here");The string passed to endTurn() is the message that is displayed on notifications that alert a player that it is their turn when they are outside of the application. Make sure to set the networkScriptObject before ending turn so that any changes you made during your turn are passed to the server.Ending a match
In order to end a match, match outcomes must first be set for all players.
To set a match outcome for a player, use the function setMatchOutcomeForPlayer.
// Don't forget to do this for all players before calling endMatch %outcome = 2; // GKTurnBasedMatchOutcomeWon setMatchOutcomeForPlayer(%idOfPlayer, %outcome);
Each player must have a non-zero outcome set before the match can be ended. Check out GKTurnBasedMatchOutcome for possible match outcome values.
Once you've assigned each player an outcome, you can end the match by calling endMatch.
endMatch();
For the common situation where you want to end a match with one winner, and all other players losers, you can use the convenience method endMatchForWinner. This will set all the player match outcomes for you. For example, to end the match with the local player as the winner, you only need to call
endMatchForWinner( getLocalParticipant() );
Quitting from a match
In order to have your player quit from the match, you must set the player's match outcome and then call quitMatch.
// Set the match outcome for the local player %outcome = 1; //GKTurnBasedMatchOutcomeQuit setMatchOutcomeForPlayer(getLocalParticipant(), %outcome); // Quit the match for the local player quitMatch();
Notes:
-I've been inconsistent with my naming conventions and used both "player" and "participant" in my code, but they mean the same thing.
Disclaimer
This resource is not a part of the official Torque engines. There are no official plans to integrate into an engine for a release. GarageGames cannot be held liable for any errors introduced into your game when integrating this resource. However, if you like this resource and find bugs, please post. If it becomes polished and more stable, we will consider rolling it into an official release.
#2
12/16/2011 (2:25 pm)
Great stuff!
#3
12/16/2011 (10:32 pm)
This is wicked awesome! We couldn't ask for a better resource for a more awesome guy! :)
#4
12/20/2011 (5:59 pm)
Fantastic, I'm keen to try making a boardgame with iT2D and this will be really useful!
#5
01/18/2012 (3:47 am)
O.K. I've got to save up for a apple computer, now. You guys are doing so much great stuff with it.
#6
01/27/2012 (6:01 am)
Great Stuff it has a lot Gotta review to make sure how to implement. Thanks 
Ray Delia
SK Studios