» Tris (Tic-Tac-Toe)

» Overview

The Tris example shows how to create a 3D mutiplayer version of the famous "Tic-Tac-Toe" game using Unity and SmartFoxServer 2X. It is fully compatible with the Tris clients from the other APIs, which means users can play together even if they are on different devices.

The following game flow is implemented.

  1. Users log in and join a Room called "The Lobby" by default; it's a sort of lobby where users can meet and chat before they start or join a game.
  2. A user creates a Room where he can play the tic-tac-toe game against another user; as soon as the game is created, he/she will enter the new Room and wait for the second player.
  3. When the second player joins the game (by selecting it in the list), the game starts.
  4. The application alternatively sets the player's turn and after every move it checks if there's a winner.
  5. The game ends when no more moves are available or one of the player makes a row of three; at the end of the game the user can either start a new game or go back in the main chat Room.

This example shows how to create a SmartFoxServer Room of type "game". Game Rooms are specifically aimed at representing games (or matches) as they can contain two different types of users: players and spectators. A Game Room also provides player indexes: each user joining the room is automatically assigned a unique player index which facilitates the tasks of starting and stopping the game (spectators are not indexed instead).
In this example spectators are not supported.

The Tris example also features a server-side Extension which is in charge of handling the game logic, as opposed to keeping the logic on the client-side only. In general, using a server-side Extension 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 server-side Extension that handles the game is dynamically attached to the Game Room representing the match started by one of the players. It sends the game events to the Unity interface. This way we can split the game logic from the game view, which is handled by the Unity client.

>> DOWNLOAD the source files <<

» Installation

» Running the example

In order to run the application follow these steps:

  1. make sure your SmartFoxServer 2X installation contains the BasicExamples Zone definition;
  2. start SmartFoxServer 2X;
  3. make sure the client runs on the same machine as the server (the IP address can otherwise be configured as explained in this document via the source files);
  4. open the /deploy/client/Tris.html file in a browser.

» Source Code Setup

The complete project is contained in a Unity package. To access and build it please follow the following steps:

  1. create a empty new Unity project;
  2. in the Unity project tab, right click and select Import Package -> Custom Package...;
  3. browse to the package for this example in the /source/client folder;
  4. open the _Scenes/login scene.

All relevant C# code is in the Scripts folder and the SmartFoxServer 2X client API DLL is contained in the Plugins folder.

The game features a server-side Extension, as described in the overview. In order to access its code, create and setup a new project in your Java IDE of choice as described in the Writing a basic Extension document. Copy the content of the /source/server/src folder to your project's source folder.

» Client code highlights

The client code is structured similar to the Lobby example. In the lobby there is now a list of games instead of a list of chat rooms that a client can join. Additionally there is a "New game" button. This button requests SmartFoxServer to create a Game Room attaching the Tris game Extension to it.

if (GUILayout.Button("New game") ) {
	RoomSettings settings = new RoomSettings(smartFox.MySelf.Name + "'s game");
	settings.GroupId = "games";
	settings.IsGame = true;
	settings.MaxUsers = 2;
	settings.MaxSpectators = 0;
	settings.Extension = new RoomExtension(extensionId, extensionClass);
	smartFox.Send(new CreateRoomRequest(settings, true, smartFox.LastJoinedRoom));
}

If either we joined an existing Room or created a new game, the client will receive na OnJoinRoom callback to unregister lobby callbacks from the SmartFoxServer API and load the game scene to actually play the Tris game.

void OnJoinRoom(BaseEvent evt) {
	Room room = (Room)evt.Params["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.Name);
		UnregisterSFSSceneCallbacks();
		Application.LoadLevel("game");
	}
}

» Playing the game

The game client communicates with the server side game Extension via Extension-messaging. Once a client has joined the game scene and is ready to play, this is communicated to the Extension.

public void InitGame(SmartFox smartFox) {
	
	....
	
	// Tell extension I'm ready to play
	sfs.Send(new ExtensionRequest("ready", new SFSObject(), sfs.LastJoinedRoom));
}

The Extension communicates with the clients using the OnExtensionResponse callback to tell the clients about the following game states:

These Extension responses are handled in the following client code.

public void OnExtensionResponse(BaseEvent evt) {
	string cmd = (string)evt.Params["cmd"];
	SFSObject dataObject = (SFSObject)evt.Params["params"];
		
	switch ( cmd ) {
		case "start":
			StartGame(dataObject.GetInt("t"),
				dataObject.GetInt("p1i"),
				dataObject.GetInt("p2i"),
				dataObject.GetUtfString("p1n"),
				dataObject.GetUtfString("p2n")
				);
			break;

		case "stop":
			UserLeft();
			break;

		case "move":
			MoveReceived(dataObject.GetInt("t"), dataObject.GetInt("x"), dataObject.GetInt("y"));
			break;

		case "win":
			ShowWinner(cmd, (int)dataObject.GetInt("w"));
			break;
					
		case "tie":
			ShowWinner(cmd, -1);
			break;
	}
}

» Sending moves

When the given client has turn and clicks on a non-occupied tile on the board, the following code sends the tile coordinates to the server extension. The server will update the state and check victory conditions.

public void PlayerMoveMade(int tileX, int tileY) {
	EnableBoard(false);

	SFSObject obj = new SFSObject();
	obj.PutInt("x", tileX);
	obj.PutInt("y", tileY);
		
	sfs.Send(new ExtensionRequest("move", obj, sfs.LastJoinedRoom));
}

» Receiving moves

As before shown, the client will receive moves via the OnExtensionResponse callback. This updates the local board with the server state.

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);
}

» Server code highlights

Please read the corresponding Flash tutorial's Server code highlights paragraph.

» More resources

You can learn more about the described features by consulting the following resources: