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.
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.
Copy your AppId from the dashboard and open the
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
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.
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
The response to this call, as any other operation's response, will call
If needed, the
MemoryGameClient calls the actual room-creating code with
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.
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:
As usual, saved events in a turnbased game are also passed to your code by calling
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
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.
LoadBoardFromProperties in MemoryGameClient.cs.
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
WebRPCs are simply operations that are executed by your own web service.
WebRPCs are easy to call from the client side:
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.
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 to open and the player number they had in the room.
This is done in
The roomName defines which room we plan to open in 'OpJoinRoom'. With the actorNumber, you take over a specific player's spot in the player list.
We found the excellent PushWoosh service for notifications. As example, our SDK includes their client library for Android.
The general idea is that Remote API (to send the actual push) gets implemented int he Web Service. The clients only need some way to trigger the push when needed and to tell the server who to push. Clients set 2 "Tags" currently, which allow us to target each user individually: UID2 and AppId. Together, these identify a user in one app.
To trigger the push, we decided to use SetProperties with a "web forward" on demand (when a turn will end). This forwards the new room properties to the Web Service, which looks up the "turn". The "turn" contains the ID of the player who should get the push. The Web Service finds the related userID and sends a directed push, using Tag "UID2" and the AppId.
An alternative to using Tags would be to store a "PushWoosh Device ID" per user in the WebService. Or you could send that deviceId to each room (as player property).
Or you do it in yet another way.
Things To Extend
The demo still has some rough edges. Amongst others you might want to try sanding these ...
- 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.