1.3 Tutorials: C#/Unity3d SmartFoxTris PRO (p.2)
» Sending moves
The game logic is now running completely on the server
side while the client will only take care of the visualization. The central
part of the client code is the extension response handler in TrisGame.cs:
public void OnExtensionResponse(object data, string type) {
// We only use XML based messages in this tutorial, so ignore string and json types
if ( type == SmartFoxClient.XTMSG_TYPE_XML ) {
// For XML based communication the data object is a SFSObject
SFSObject dataObject = (SFSObject)data;
switch ( dataObject.GetString("_cmd") ) {
case "start":
StartGame( (int)dataObject.GetNumber("t"),
(int)dataObject.GetNumber("p1i"),
(int)dataObject.GetNumber("p2i"),
dataObject.GetString("p1n"),
dataObject.GetString("p2n"));
break;
case "stop":
UserLeft();
break;
case "move":
MoveReceived((int)dataObject.GetNumber("t"), (int)dataObject.GetNumber("x"), (int)dataObject.GetNumber("y"));
break;
case "win":
ShowWinner(dataObject.GetString("_cmd"), (int)dataObject.GetNumber("w"));
break;
case "tie":
ShowWinner(dataObject.GetString("_cmd"), -1);
break;
}
// Restarts are send as cmd and not _cmd - possible bug in SFS extension
if (dataObject.GetString("cmd") == "restart") {
sfs.SendXtMessage(extensionName, "ready", null);
}
}
}
/**
* Start the game
*/
private void StartGame(int whoseTurn, int p1Id, int p2Id, string p1Name, string p2Name) {
this.whoseTurn = whoseTurn;
player1Id = p1Id;
player2Id = p2Id;
player1Name = p1Name;
player2Name = p2Name;
ResetGameBoard();
SetTurn();
EnableBoard(true);
gameStarted = true;
// Reset GUI if this is a restart
GameGUI gui = (GameGUI)GameObject.Find("Game GUI").GetComponent("GameGUI");
gui.SetStartGame();
}
/**
* On board click, send move to other players
*/
public void PlayerMoveMade(int tileX, int tileY) {
EnableBoard(false);
Hashtable obj = new Hashtable();
obj.Add("x", tileX);
obj.Add("y", tileY);
sfs.SendXtMessage(extensionName, "move", obj);
}
The PlayerMoveMade(...) function is called from the Unity callback OnMouseDown() in the script TileController.cs that's attached to each tile object. The tiles are all named tileXY, where X and Y are the coordinate in the grid they posses so all we do is extract the 5th and 6th characters from the name and send that as the coordinate to the server.
This is how the extension handles the "move" action:
function handleMove(prms, u)
{
if (gameStarted)
{
if (whoseTurn == u.getPlayerIndex())
{
var px = prms.x
var py = prms.y
if (board[py][px] == ".")
{
board[py][px] = String(u.getPlayerIndex())
whoseTurn = (whoseTurn == 1) ? 2 : 1
var o = {}
o._cmd = "move"
o.x = px
o.y = py
o.t = u.getPlayerIndex()
_server.sendResponse(o, currentRoomId, null, users)
moveCount++
checkBoard()
}
}
}
}
We have added some extra validations to avoid cheating: first
we check that the game is really started, if not we'll refuse the request.
Then we verify if the player who sent the move was allowed to do so, in
other words if it's his turn. Once these two checks are passed we can finally
store the move in the board array, using the playerId as value.
In the next lines we set the turn for the other player and send the move data
and turn id to both clients. Also we keep track of the number of moves by incrementing
the moveCount variable and we call the checkBoard() method
to see if there's a winner or a tie.
function checkBoard()
{
var solution = []
// All Rows
for (var i = 1; i < 4; i++)
{
solution.push(board[i][1] + board[i][2] + board[i][3])
}
// All Columns
for (var i = 1; i < 4; i++)
{
solution.push(board[1][i] + board[2][i] + board[3][i])
}
// Diagonals
solution.push(board[1][1] + board[2][2] + board[3][3])
solution.push(board[1][3] + board[2][2] + board[3][1])
var winner = null
while(solution.length > 0)
{
var st = solution.pop()
if (st == "111")
{
winner = 1
break
}
else if (st == "222")
{
winner = 2
break
}
}
var response = {}
// TIE !!!
if (winner == null && moveCount == 9)
{
gameStarted = false
response._cmd = "tie"
_server.sendResponse(response, currentRoomId, null, users)
endGameResponse = response
}
else if (winner != null)
{
// There is a winner !
gameStarted = false
response._cmd = "win"
response.w = winner
_server.sendResponse(response, currentRoomId, null, users)
endGameResponse = response
}
}
Even if there is a lot of code the function works in a very simple way: it creates an empty array called "solutions" and fills it with all possible rows and columns where you can put three items in a row.
The available solutions are 8 in total: 3 columns + 3 rows + 2 diagonals.
When the array is populated we loop through it and if one combinantion of three is found then we have a winner! Also we check if there's no more moves available. In that case we'll have a tie.
» Receiving moves and client updates
As we've already seen the moves are received in the onExtensionResponse(). The method that handles the move update is called MoveReceived()
/**
* Handle the opponent move
*/
private void MoveReceived(int movingPlayer, int x, int y) {
whoseTurn = ( movingPlayer == 1 ) ? 2 : 1;
if ( movingPlayer != myPlayerID ) {
GameObject tile = GameObject.Find("tile" + x + y);
TileController ctrl = (TileController)tile.GetComponent("TileController");
ctrl.SetEnemyMove();
}
SetTurn();
EnableBoard(true);
}
We dynamically
create a string with the name of the tile and find the GameObject with that name. This will be our tile with the attached TileController script. Next we get the TileController component and call SetEnemyMove().
TileController::SetEnemyMove() simply set the tiles state to the opponents playing piece (TileState.RING or TileState.CROSS).
» Conclusions
In this tutorial we've learned how to program a game with the game logic separated from the game view by using a server side extension as the "game controller". There are many advantages to writing your game this way rather than having the logic on the client(s); better game code organization, better game security, better integration with external data etc...
We suggest to analyze both client and server code to fully understand how they
work and experiment with your own variations and ideas. Good luck!
| « previous | doc index |