Memory Game Demo
The Memory Demo is a fully working example of a simple two-player game that can be played asynchronously.
It has matchmaking, shows "saved games", who's turn it is in those games and supports push notifications for inactive opponents.
The client is available in the Unity SDK.
On the server side, the reference implementation for asynchronous games is used.
This can be activated in the Dashboard and you can get the source on GitHub.
Preparing The Client
The client is pretty much ready to run.
You only need to load MemoryDemoScene and insert your own AppId in the Editor.
Get your application's AppId from the Realtime Dashboard after free signup.
Copy your AppId from the dashboard and open the MemoryDemoScene
.
Find the "Scripts" GameObject and the MemoryGui
component to set the "App Id".
Preparing The Server
From the Realtime Dashboard, go to the management page of your application by clicking "Manage".
Then at the bottom you will find the Webhooks section where you should create a new webhooks setup using the "Create a new Webhook" button.
From the dropdown list, choose "WebHooks 1.2 Demo".
You can also choose "WebHooks 1.2" if your own web service is ready.
For the sake of this demo, only the relevant parameters are shown in the screenshot below.
For more information about Webhooks please visit this page.
A Look At The Code
To get the most out of the Memory Demo, you should definitely take a look at the code.
The Unity Inspector settings define the looks and some options but the logic is done in code.
Important classes in this demo, that you might want to peek at are MemoryGui
, MemoryBoard
, MemoryGameClient
and NamePickerGui
.
Authentication
For turnbased games the user plays an important role:
You need to keep a list of games per user and that is best accessed by some userID.
To keep things simple, this demo does not require a password and just accepts any entered name as userID.
Photon's Custom Authentication feature can be used to actually authenticate a user's account.
Read more about it in the custom authentication doc.
For your reference, a second scene "CustomAuthDemo" is in the package.
It shows you how to implement authentication on the client side.
Matchmaking
This demo uses a very simple but matchmaking workflow:
It tries to join a random room and creates a new one if that fails.
Find additional info in the reference doc for matchmaking and lobby.
Matchmaking only works as long as a room has at least one active player joined.
As a single player does not do much on his own, this usually means a room is in matchmaking only for a short time.
The gui elements call NewGameMsg()
to do matchmaking, which in turn calls OpJoinRandomRoom()
.
The response to this call, as any other operation's response, will call OnOperationResponse
.
If needed, the MemoryGameClient
calls the actual room-creating code with MemoryGameClient.CreateTurnbasedRoom()
.
Saving Your State
Any game that is not abandoned explicitly can be continued later on.
This includes cases where the client gets closed mid-game or loses connection.
Photon Realtime automatically saves buffered events and all room- and player-properties.
When a player re-joins a room, it will get the properties of the players - active and inactive - and all buffered events.
With this data you are able to reproduce the state and continue playing.
For a memory game, we need to keep track of the tiles on the board.
Cached Events
Aside from sending events only to the active players in a Room, you can deliberately cache them for joining and "returning" players.
The room will keep cached events in the order of their arrival and sends them to players who join, before those get any "live" events.
In Photon Realtime, cached events will also be saved and sent any time a player continues the game later on.
The demo does not use cached events but below you can see how the OpRaiseEvent
call could look like:
C#
public void CachedRaiseEvent(byte evCode, object content)
{
// the content can be anything Photon-serializable. Most often a Hashtable is used.
RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.AddToRoomCache };
this.OpRaiseEvent(evCode, content, true, options); // cached events should be sent reliable
}
As usual, saved events in a turnbased game are also passed to your code by calling OnEvent()
.
If you don't need a long history of turns (for replay) you should speed up loading the game by clearing the cache of unused events.
This is done (a bit confusingly) by OpRaiseEvent
again but with the CachingOption set to EventCaching.RemoveFromRoomCache
.
Properties
The memory game saves the complete game state in a set of room properties.
Properties are great for values which don't change frequently and only need to store the current value.
Best of all: You can cherry-pick some room properties and make them available in the "saved games" list.
In Photon Realtime, all properties are saved when all players become inactive.
This is why you configure "IsPersistent" to "true" in the dashboard.
The demo saves each tile in its own property with the tile id as string key.
This way, each tile could be updated individually.
However, the demo always saves the whole game state at the end of each turn.
A key element in Memory is that you can see the other's flipped tiles.
At least, if they don't match.
To implement this, the players do not hand over the turn when done.
Instead, both flipped tiles are simply saved and the logic keeps you from selecting others.
The opponent has to show the tiles and "take over" the turn when ready.
The scores and a turn counter are also part of the saved properties.
Check out SaveBoardToProperties
and LoadBoardFromProperties
in MemoryGameClient.cs.
Saved Games
Photon Realtime does not only save the state of each room but can also help you keep a game list for each user.
If your users log in with a fixed account (i.e. same UserId), the list is available even if a user switches to another device.
Get Saved Games
Clients access the saved games list by a WebRPC
call.
WebRPCs are simply operations that are executed by your own web service.
WebRPCs are easy to call from the client side:
C#
Dictionary<string, object>() jsonParameters = new Dictionary<string, object>();
this.GameClientInstance.OpWebRpc("GetGameList", jsonParameters);
Photon will use the BaseUrl
configured for Webhooks and add "GetGameList" to the end.
Of course, the server configured as "Base Path" needs to provide the games list per player under this path.
The Dictionary jsonParameters
is not really needed in this case but shows how to pass parameters to the web server if needed.
Photon will automatically provide info about the user (like the UserID) to the web service.
The returned list of saved games contains the names of rooms the player can re-join.
Per room, the server also sends the actorNumber the user had in that room.
Last but not least: If you provided a list of room properties that should be available in the lobby, those are also sent by GetGameList in our default implementation.
The MemoryGameClient turns this into a List<SaveGameInfo> SavedGames
, which is used to show the rooms list.
The demo sends just enough data per game to show if it's your turn and who you are playing in per game.
The actual room-name is not important in this context.
Note: Try to minimize the data you send to keep things lean and cheap for players.
Opening Saved Games
Players can return to saved games by "re-joining" them.
They need to know the name of the room.
This is done in MemoryGui.LoadGameMsg()
.
C#
this.GameClientInstance.OpRejoinRoom(roomName);
Things To Extend
The demo still has some rough edges.
Amongst others you might want to try sanding these ...
- Push notifications.
- The game does not detect when Webhooks are not configured.
It could show a note. - There is no visual feedback if you got disconnected.
- Show if the other player is active or not as you never know if it makes sense to wait.
- Show how many players are online.
- ...
Start playing!
Back to top