question

taptastic.studios@gmail.com avatar image
taptastic.studios@gmail.com asked

Persistent turn based games?

Hello,

I am currently working on my first multiplayer turn based game (which is also my frist game in unity), so bear with me and help me out with a bit of clarification as all this is getting confusing to me.

The multiplayer functionality that I need is like this:
- one player is starting a multiplayer game, he doesnt finds an available room, so creates one
- he plays his turn (simple moves), data is sent to the server and now the user can close the application
- when second player is starting a multiplayer game he finds the game created by the first player and joins it
- he gets first players turn data and after that he can play his turn
- first player receives a notification that is now his move... etc

For the multiplayer part I started using turnbased photon + playfab, only to discover now that I may need another server for photon webhooks (?) to keep persistent game rooms.

What is the recommended why of achieving this functionality?

Thanks

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Hamza Lazaar avatar image
Hamza Lazaar answered

Please give me your feedback about two things:
- does the pseudo code for the simple scenario described in a previous answer matches the design you're proposing?

- how do you suggest a GetGameList like handler?

I do believe your suggestion is the one that should be used. I'm trying to make a clear design and thinking ahead before starting to implement this in CloudScript and hitting several difficulties later...getting list of games could have been the first.

The problem can happen when you try "bulk read" of all "open games" of a player in order to get the list of active/ongoing games to the player with minimal information about each.
As I described before, this can cost from 2 API calls up to 102 API calls. If this burst is allowed when not so frequent then you should help us decide how often to poll games list from clients.
If this is too many then we should either show game IDs only to the player or save that minimal information to display (examples: opponent, score, turn, round, game mode, game language, game type, etc.) outside Photon room state and directly into "open games" list of the player. This has a price also in API calls number that I'm willing to discuss with you also.

I have few optimization tricks that can help reduce the number of API calls but also increase CloudScript code complexity and length (decreasing its readability and flexibility). For instance I'm explicitly constructing game IDs from client and each game ID already includes/contains the PlayFabId of its creator so this can remove the need of guessing that. Also when reading or updating SharedGroup data it's always preferable to group per "target SharedGroupId" all keys to be read together in an array or all data to be updated in a JSON object before calling GetSharedGroupData or UpdateSharedGroupData. This can make the code ugly but it will reduce the number of API calls in a single handler considerably.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

brendan avatar image
brendan answered

Bursts of 100 calls would definitely not be within the reasonable range, so yes, keeping the summary of the data needed in a single Shared Group Data for the user should be part of the design. The authoritative state of the game would still be updated only in one place - the Shared Group Data of the user who started the game session, in this design - but that should only be needed when actually playing that specific game, so that shouldn't be a problem. You'd then update the other player's Shared Group Data (again, via Cloud Script) to update the summary info. So effectively, the GetGameList call would just grab the user's own Shared Group Data, which should have the summary info for all games the player is in, and either the full details (for games he started) or the info on the other player, so that if the user chooses to play that game session, it can be loaded.

For the pseudo code you're proposing, my main question would be why use the Room TTL in this manner? Since the gameplay is entirely asynchronous, each player would update the state of the game upon completion of his turn. So there shouldn't be anything left for the room to do when its timer expires. The TTL is hugely useful in many game designs, but in this case, since the time the other player might join to do his turn could be very long, it seems like it would be a better use of the Photon Cloud service to just have a short TTL.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Hamza Lazaar avatar image
Hamza Lazaar answered

When I say EmptyRoomTTL expires, I have Photon's internal mechanisms in mind, it can be 0 and I still consider EmptyRoomTTL a necessary condition for GameClose webhook to be triggered.

By the way, EmptyRoomTTL threshold is now 5 minutes. I think developers need to make use of this when they launch their games even if the intention is just to keep the room as long as possible. Few extra minutes increases probability of rooms getting matched. So total max lifetime of rooms pending random matchmaking is 65 minutes in AsyncRandomLobbies with EmptyRoomTTL = 300000.


I think you're only considering apps that require a "constant/static" summary for games like opponentId, creation timestamp, mode, type, language, region, etc.
What about game lists (I'm talking about the actual in-game screen / UI view) that gets dynamically updated as soon as the games got updated without the need to join each one. The score, turn number, next player to play, domination ratio, etc. are few examples of dynamic properties that need to be refreshed.

Avoiding those "100 bursts" has a price of making extra UpdateSharedGroup calls to update as many games list as players. So the previous # of UpdateSharedGroup will be multiplied by n (the number of players), sometimes it's (n - 1) if data for Actor1 can be read from State directly. So we may end up with duplicate data [inside Photon Room State and outside directly in the SharedGroup] and other replicated data [rooms summary across all players].

A while back, after understanding PlayFab-Photon limitations and constraints I wrote these notes:

  1. Where to save data:
  2. If it’s needed ONLY when Joined to a Room then it can go in Photon’s Custom Room Properties.
  3. If it should be accessible from State argument within webhooks then it needs to be a Lobby property. In GameProperties only updated properties are a separate argument. Do we need to also save it in the SharedGroup separately ?
  4. Else it must go directly in the SharedGroup entry. When do we need to also save it in the Custom Room Properties ?
  5. When and how to save data:
  6. You can save Room state when it is sent in GameClose type=”Save” (if IsPersistent) and can be sent as argument in GameProperties and GameEvent.
  7. Other data can be saved from any webhook or webRPC call.
  8. How to notify other users about data update ?
  9. PushNotification
  10. PhotonChat
  11. Poll and Pull

In a perfect world, push notifications and a local cache of games is enough to obtain this instant update of games list data and looks.
But fallback mechanisms should be put in place in case push fails or is disabled.

Photon Chat is an example of backup strategies. It enables exchange of messages on Photon Master Server ("lobby level", "outside rooms"). It is a way of letting a connected player but not joined to the roomX to get updates about roomX.
Personally I have enough overhead and I don't want to deal with another separate product (SDK, API, AppID, life cycle, etc.). Besides, this method is not tested yet.
So I was using a polling system: periodic fetch or on-demand pull to refresh. Client sends a key-values list like this: (game id : <save game data/state indicator>). The value should inform of the state/date of the local save game. The server first "counts games" to see if the client is missing a game (multiple devices in mind, outdated cache also) then the server compares the game states/dates sent by the client with what's on the server. Only the difference should be returned to client.

Another PlayFab-Photon developer came up with another clever way of updating clients about games they're not joined to. I just hope he joins our discussion.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

brendan avatar image
brendan answered

I think we're largely on the same page - the two places where I think we primarily diverge are:

  1. I prefer storing the game state info in PlayFab, as there are situations (for many games, granted, they're corner cases, but for people shipping their first game, they'll be more common) where it may be a long time before someone joins the game.

  2. Specific to the "up to date list" concept, it's absolutely the case that we don't support all possible models of async games. If there's a need to have a list which is kept up to date at a high rate, the developer needs to be thinking of using a custom game server - which we can host for them - as the aggregation point for this info.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Hamza Lazaar avatar image
Hamza Lazaar answered

@Brendan:

I'm working on a CloudScript version that should support all kind of Photon games. It's almost finished and I'll be needing testers. 

Since SharedGroups are not meant to allow fast or concurrent access (read/write), do you confirm that we should recommend using Photon's room state to save game data while still joined (either cache events or as room properties) and write it to the SharedGroup ONLY when all players disconnect. Meaning we should not allow writing to SharedGroup when raising an event or updating room/player properties as this may be frequent and fast and may corrupt data if events occur at the same time.

This is very important, I need to know how far I can trust SharedGroups. 

There are still other questions about the overall design:

1. When to start saving? "as soon as there is something to save" or "only on demand"? meaning: save room data in "create" or "save" event? save player game entry in "join" or "leave" event?

2. edge case A: in game close webhook no currentPlayerId or args.UserId is available so we don't have a user context unless it's a "save" event and the "State" is present as argument. We can loop over the ActorList array and get the data of the player where ActorNr = 1, however:

- 2.1: What should be done in "close" event when we have already saved a game data previously? We need to save a reference to the UserId where the game data is saved globally. I'm using a SharedGroup with ID = GameID, created in RoomCreated and deleted in RoomClosed.

- 2.2: What should be done if ActorNr = 1 has left and can't be found in "State"? this is discussed in details in 3.

3. edge case B: should we always save the "State" in a SharedGroup of the "creator"/ActorNr = 1? Or it should be dynamically moved to the MasterClient's space whenever this latter changes. This concern is raised when ActorNr = 1 leaves the game for good, should we remove the game entry from his games list SharedGroup or keep it and flag it to not use it when getting the games list for that player. OR when the ActorNr=1 leaves, we should move the data to another player (e.g. the next player with with the lowest ActorNr) and we should update ALL SharedGroups of all players of the game. 

4. if 100KB is not enough, right now what can be safely removed from State as not required on Photon server: "DebugInfo" and "DEBUG_BINARY" and "CustomProperties" key/values, the following key/values are not used YET: (PublishUserId, ExpectedUsers, ExcludedActors). However, in your game you can avoid saving some RoomOptions and manually (hard code) add those each time you return a "State" in "load" events. 

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

brendan avatar image
brendan answered

Actually, Shared Group Data absolutely does allow for fast access for both read and write. The issue is that it works more like User Data - it's designed to be a store for data that can change rapidly. For that reason, it cannot be cached and replicated across multiple database shards, the way that Title Data, which isn't designed for frequent updates, can. It's just a matter of physics - if a dozen people request the a piece of data from a single source, that's not a problem. But if thousands do, that is. So the issue isn't with using Shared Group Data to save a game's state, it's with using it as a matchmaking system, trying to allow all users to query a single data point for info on games to join.

Now, that said, no game should be updating Shared Group Data (or User Data) at a high rate, as that could push them past our fair usage limits. Instead, they should update them at the end of a session or when a significant change occurs. One way to do this would indeed be based upon the webhook calls from Photon, but a client could just as easily call into Cloud Script to trigger this update. In the context you describe though, I believe that your preferred option would be to save the events on a Room Close. You could also raise a custom event (OpRaiseEvent), if there's a need to save data out-of-band. Since this calls into a Cloud Script, you could just track the PlayFab IDs in well-defined room properties, and use that to determine those IDs for cases where you no longer have the Actor list available, which I believe answers your section 2.

For 3, different games are going to have different requirements. In my original example, there's no concept of the original player no longer being part of the game, since it's a 1-vs-1 challenge. If he's no longer in the game, that means he lost, and the game is done. For more complex games, developers may want to "migrate" the game state to a new "host" player's Shared Group Data.

For 4, I would say that serializing the entire room state and requiring huge amounts of storage is never the right thing to do. The game should only be saving out the game-specific state information needed to get back to that state.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

jaaydenhalko avatar image
jaaydenhalko answered

I am working on an asynchronous turn-based game basically like chess or connect four. I feel like using photon for just the matchmaking doesnt make sense for this type of game since all I really need is a global data store for the matchmaking. The matchmaking can start off by simply matching to any open game needing 1 player or creating a new game if there are no open games. Additionally, I dont like the restriction for photon that players can only be matched in the same region. This makes sense for real-time games but not for asynchronous turn-based. Can you suggest a global datastore that would work well with playfab? @Brendan

,

For the game I am making which is basically will work like chess or connect four, asynchronous turn-based, it feels like I am going through alot of extra trouble to use photon as the matchmaker since matchmaking for me can just consist of matching to any open game needing a player or creating a new game if there are no games needing players. In addition, I dont like the restriction of photon that matchmaking only happens for players in the same region. That makes sense for real-time games but not for asynchronous. I really just need a global data store that will work well with Playfab. Any suggestions for this? @Brendan

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

brendan avatar image
brendan answered

@jaaydenhalko Unfortunately, there is no global data store which is suited to that in PlayFab currently. We do plan to add an asynchronous matchmaker in a future update, but right now, you would need to use either Photon or else your own external data store.

To be clear, we have two types of data systems right now:

1. Data which is rarely updated, and only by the developer, which is readable by all users. This includes systems like Title Data, Title News, and Publisher Data. They are cached and sharded for efficiency, which is what allows them to be read by all players, but that also means that updates to the data have to propagate between the databases, which takes a small (but non-zero) amount of time.

2. Data which is frequently updated by the player, and which can be shared with a small number of players (think 100 or less). That's User Data, Shared Group Data, etc. Each piece of data is a single row in a single data table, so if you had a lot of people try to access it at once, that would bog down the table - that's just the physics of it.

One example of a system that requires frequent updates and the ability to be accessed by all users would be our matchmaker for hosted game servers. That data lives in-memory, but it is also designed to not require 100% continuity. It is built in such a way that if that server had to be rebooted, the data is rebuilt quite quickly, based on the running game servers.

Other systems that require that all players can update the data and all players can read the data are usually custom built, based upon the requirements of the game. A generic system that allowed for that and additionally had to ensure that the data would be maintained reliably (that is, it can't be rebuilt the way a matchmaker can) would be extremely expensive to operate.

So in general, the recommendation is to use Photon or your own external custom data store for this. We do have the ability to do custom development contracts if you have a specific service design you need implemented. If that's the route you'd like to go, feel free to open a ticket using the option on this page.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

lmgualandi avatar image
lmgualandi answered
,

@Brendan do you have an ETA for the asynchronous matchmaker feature?

1 comment
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

brendan avatar image brendan commented ·

As that work hasn't been scheduled yet, I'm afraid I can't really give a date for it. As soon as it is, you can count on us making it very visible. :)

0 Likes 0 ·
glaws avatar image
glaws answered

Hi,

I'm currently developing a turn based multiplayer game, and i've been reading the forum posts today on how best to set it up, but I still have some questions that I can't seem to find the answer to.

First of all, I understand the discussion here on how to use Photon to Matchmake and to save active games into Players shared data. However, I do not understand the best way to actively play that game.

So for example, a player completes their turn, and sends a message to cloudscript(?) which in turn does some logic to avoid cheating, and then updates the state of the game. At this point, how does the second player know that it is their turn? i.e. the player could either be offline completely, using their device for something else, or inside the game doing something else. I'm aware a push message could be sent to deal with the offline, but how about if they are in the game. Is a push message relevent here also?

Or is it the case that all of this messaging is handled by Photon?

The reason i'm asking, is because of the region locking of users playing multiplayer via Photon. This is fine for random matchmaking, but I want users to be able to play against friends in their facebook list, and they may well be spread across regions, in this case, a messaging system via PlayFab would be preferable.

5 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

glaws avatar image glaws commented ·

For clarification, our turn based game will be fairly slow to play, think Trivia Crack. Players will have a few minutes to play their turn.

0 Likes 0 ·
brendan avatar image brendan commented ·

Yes, a Push Notification would still be relevant even if the player is currently in the title. However, since a player could turn off Push at the device, which provides zero feedback to us to let us know, I'd recommend having another way to be aware. Since you're talking about using Photon, the simplest thing to do would be to have the player join the room for any game he's actively playing. That way, you'll get notification of the update via the room itself (since you'll be updating the room's properties). For cross-region play, you could still have the players join the room in the other region.

0 Likes 0 ·
glaws avatar image glaws brendan commented ·

OK, yes, I realised that push could be off, I wasn't sure if that stopped it being processed completely, so we'll use Photon, I'll need to read up a bit more of how that works. I think I understand how to deal with active games, but I'm not so clear on cross-region play. Are you suggesting it's possible to join rooms from different regions?

Thanks,

Graeme.

0 Likes 0 ·
brendan avatar image brendan glaws commented ·

Yes, you can read more on how to use regions in Photon Realtime here (https://doc.photonengine.com/en-us/realtime/current/reference/regions), but in short, as long as you're not restricting the client by having region-specific versions of your game, you can join a room in another region.

0 Likes 0 ·
Show more comments

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.