1.3 Tutorials: C#/Unity3d SmartFoxTris PRO
» Introduction
In this article we'll develop a complete 3d turn-based game of multiplayer tic-tac-toe using server side
extensions and Unity3D. We will look at the server side extension code and how it communicates with the game clients.
We won't however go into the Unity specific things too much and rather focus on how to use the SmartFox API, for further reference about how to do things in Unity please see the documentation included with the engine. The code for the game will build on the previous tutorial "C#/Unity3d lobby", so we won't explain the topics covered in there, how to set up a lobby, how to handle chat, etc, either.
What are the advantages of using server side logic? In general, using a server side extensions is a more
flexibile and secure option. Even if SmartFoxServer provides
powerful tools for developing application logic on the client, this approach
can be limiting when your games start getting more complex. Also keeping sensitive
game data on the server side allows overall better security from hacking attempts,
cheating etc...
» The game
The server side extension that handles the game is going to be dynamically attached to each game room created, and it will send the game events to the interface. This way we can split the game logic, by moving it on the server, from the game view which is handled by the client.
» Server side events
Before we analyze the game code, it's a good idea to take a look at the the
events that the extension will handle:
| game start | The game begins when 2 players are available in the room | |
| game end | The game ends when all board tiles have been filled. The extension will then calculate if there's a winner or if it's a tie | |
| game stop | The game is interrupted because one of the players has left the room in the middle of a match |
if ( GUI.Button(new Rect(screenW-200, 255, 100, 24), "New game") ) {
Hashtable roomObj = new Hashtable();
roomObj.Add("name", smartFox.myUserName + "'s game");
roomObj.Add("isGame", true);
roomObj.Add("maxUsers", 2);
roomObj.Add("uCount", true);
Hashtable ext = new Hashtable();
ext.Add("name", "tris");
ext.Add("script", "sfsTris.as");
roomObj.Add("extension", ext);
smartFox.CreateRoom(roomObj);
}
This displays a button with the title "New Game", when clicked the code creates the new game room with support for 2 players and it attaches
our server side extension to it. The actionscript file is called "sfsTris.as" and it is located (as usual) in the sfsExtensions/ folder. The name that we'll use to invoke the extension is "tris".
When we call smartFox.CreateRoom we will get the OnJoinRoom call back so we handle that and load the "game" scene.
public void OnJoinRoom(Room room) {
// If we joined a game room, then we either created it (and auto joined) or manually selected a game to join
if ( room.IsGame() ) {
started = false;
Debug.Log("Joining game room " + room.GetName());
UnregisterSFSSceneCallbacks();
Application.LoadLevel("game");
}
}
In the game scene we do the usual things like registering our callbacks and such, as mentioned in the introduction we won't go into this since it was covered in the previous tutorial. Once it's all set up we wait until we have two players and start the game:
void StartGame() {
chatWindow.AddSystemMessage("Game started! May the best man win");
currentGameState = GameState.RUNNING;
}
The TrisGame object is our main game code, located in TrisGame.cs. It has all the basic variables we need set up:
private string extensionName;
private string gameStatus;
private int player1Id;
private int player2Id;
private string player1Name;
private string player2Name;
private int whoseTurn;
private Boolean gameStarted;
private int myPlayerID;
At this point the game will more or less stay idle and wait for events coming from the server. When we recieve an event we'll take care of it in TrisGame::OnExtensionResponse().
» The sfsTris.as extension
We will now inspect the code of the server side extension. The first few lines
define the main variables to handle the game state:
var whoseTurn // keep track of the current turn
var board // a 2D array containing the board game data
var numPlayers // count the number of players currently inside
var users = [] // an array of users
var gameStarted // boolean, true if the game has started
var currentRoomId // the Id of the room where the extension is running
var p1id // userId of player1
var p2id // userId of player2
var moveCount // count the number of moves
var endGameResponse // save the final result of the game
function init()
{
numPlayers = 0
gameStarted = false
}
function destroy()
{
// Nothing special to do here
}
function handleInternalEvent(evt)
{
evtName = evt.name
// Handle a user joining the room
if (evtName == "userJoin")
{
// get the id of the current room
if (currentRoomId == undefined)
currentRoomId = evt["room"].getId()
// Get the user object
u = evt["user"]
// add this user to our list of local users in this game room
// We use the userId number as the key
users[u.getUserId()] = u
// Handle player entering game
// Let's check if the player is not a spectator (playerIndex != -1)
if (u.getPlayerIndex() != -1)
{
numPlayers++
if (u.getPlayerIndex() == 1)
p1id = u.getUserId()
else
p2id = u.getUserId()
// If we have two players and the game was not started yet
// it's time to start it now!
if(numPlayers == 2 && !gameStarted)
startGame()
}
else
{
// If a spectator enters the room
// we have to update him sending the current board status
updateSpectator(u)
if (endGameResponse != null)
_server.sendResponse(endGameResponse, currentRoomId, null, [u])
}
}
// Handle a user leaving the room or a user disconnection
else if (evtName == "userExit" || evtName == "userLost")
{
// get the user id
var uId = evt["userId"]
// get the playerId of the user we have lost
var oldPid = evt["oldPlayerIndex"]
var u = users[uId]
// Let's remove the player from the list
delete users[uId]
// If the user we have lost was playing
// we stop the game and tell everyone
if (oldPid > 0)
{
numPlayers--
gameStarted = false
if(numPlayers > 0)
{
var res = {}
res._cmd = "stop"
res.n = u.getName()
_server.sendResponse(res, currentRoomId, null, users)
}
}
}
// Handle a spectator switching to a player
else if (evtName == "spectatorSwitched")
{
if (!gameStarted && evt["playerIndex"] > 0)
{
numPlayers++
// Update the playerId
this["p" + evt["playerIndex"] + "id"] = evt["user"].getUserId()
// If we now have 2 players the game should be started
if(numPlayers == 2)
startGame()
}
}
}
| doc index | next » |