Appendix C - In-Game Communication
Multiplayer Architecture
We should use one of the below methods to handle multiplayer functionality. Corresponding GameArchType
should be returned in the GetGameArchType
overridden function.
Generic
We go for Generic
architecture if the game has its own multiplayer network (like photon) to handle multiplayer in the game or if the game is a single player game. Here GetGameArchType
overridden function should return GameArchType.Generic
RemoteEventBased
If the game requires simple event transfer between remote players, event loss during disconnection is acceptable and doesn’t need master client(it is a client like normal client along with some extra functionality and decision making capability), then the following steps need to be done for event transfer (PUN2 is used for events transfer)
In the file created in section 2 above
- Override
public virtual GameArchType GetGameArchType()
to returnGameArchType.RemoteEventBased
- Override
* Override `public virtual void onGamePlayEvent(object data) to listen to the event data fired by each player in section 4.1.c.ii.`
API Calls
- API present in
IRemoteEventBasedGameManager
should be called from appropriate places in the game. An Object implementingIRemoteEventBasedGameManager
can be obtained by callingJoyrideGame.instance.getRemoteEventBasedGameManager().
API present in
IRemoteEventBasedGameManager
are:bool isConnectedToServerWithProperState();
//it returns true only if startGamePlay Command received and current user is connected to the game room on servervoid sendGamePlayDataToRemotePlayers(object data, bool sendToMeAlso = true);
//It sends data to remote players via game server. I also sends data to the local player ifsendToMeAlso
istrue
GenericRemote
If the game requires events transfer along with the master client(it is a client like a normal client along with some extra functionality and decision making capability), then following steps need to be done to use our framework for event transfer (PUN2 is used for events transfer. The framework handles master client switching, disconnection, automatic event generation during disconnection, event generation for GameAI etc)
- In the file created in section 2 above,
- Override
public virtual GameArchType GetGameArchType()
to returnGameArchType.GenericRemote
- Override
public virtual INonGameSpecificCommandHandler createGenericGameManager(GameConfig gameConfig)
to create an instance ofGenericGameManager (GenericGameManager
class implementsIGenericGameManager
andINonGameSpecificCommandHandler
) and store reference ofIGenericGameManager
.
- Override
- Constructor of
GenericGameManager
class takes following 3 argumentIGameEventListener<S, PS, A> gameEventListener
IGameStateCalculator<S, PS, A> gameSpecificStateCalculator
A dummyGameSpecificAction
- Create a class/classes implementing each interface in above three points. While implementing these interfaces, create different classes implementing
IGameState, IGameAction, IGSPlayerState
.
IGameEventListener<S, PS, A> where A:IGameAction where S:IGameState where PS: IGSPlayerState
interface needs to be implemented so as to update the game/player’s UI depending on remote event/state. Description of non-trivial Api ofIGameEventListener
are:void onReceivedGameState(GenericGameState<S, PS> gameState);
//update the game UI with this state and enable/disable interaction if required. It is called to synchronize the state.void onSynchronizingGameState();
//disable the UI/interaction and show appropriate messages. It is called after reconnection to the game server.void onDisconnected();
//disable the UI/interaction and show appropriate messages. It is called when I get disconnected from the game server.void onConnected();
//enable the UI/interaction and show/remove messages. It is called when I get re-connected to the game server.void onPlayerLeft(string userId);
//It is called when a player (including local player) is marked left. A player is marked left when disconnectionTimeout(30 seconds) happens after disconnection.void onAppPauseState(bool paused, double pauseSec = 0);
//It is called when app pauses/resumes. When the app resumes, pauseSec represents pauseDuration.void onGameAction(GenericGameAction<A> action);
//It is called when a remote player/game’ action comes to the client. Process the action appropriately to update the UI.While processing the action,
updateGameStateFromLocalGame, updatePlayerStateFromLocalGame, updateCurrentUserIdFromLocalGame
(Section 4.2.d.iii,Section 4.2.d.iv,Section 4.2.d.v respectively) ofIGenericGameManager
should be called.After the action is processed successfully, do call
onRemoteActionProcessed
(Section 4.2.d.ii) ofIGenericGameManager
, so that the framework can notify the next remote action viaonGameAction(GenericGameAction<A> action)
.
IGameStateCalculator<S, PS, A> where S:IGameState where A: IGameAction where PS: IGSPlayerState
interface needs to be implemented so that the framework can fire/notify appropriate events/actions for all players at desired time. Description of non-trivial Api ofIGameStateCalculator
are:
* `S getInitialGameState(JoyrideGame.JRGameInfo JRGameInfo, MultiPlayer.IRemotePlayer[] remotePlayers);` //should return game specific state data which will be same for all players. It is used to create an initial instance of `GenericGameState<S, PS> where S: IGameState where PS:IGSPlayerState` which is used by the framework to store the game’s state.
* `GenericGameAction<A> getActionToStartGameWithStartingState(GenericGameState<S, PS> gameState, string firstUserId, int serverTime);` //It is called by master client only to send start event to each player including itself, so that game can start with same state/action at almost same time in each client. Eg- For a chain-reaction game, it should return action corresponding to the start turn for the first player. For a Uno game, it should return action corresponding to the start turn and initially dealt random cards. For chain-reaction game, its implementation is:
* `GenericGameAction<BaseCRActionData> action = new` `GenericGameAction<BaseCRActionData>("", serverTime, false);` `action.gameSpecificActionData = new ChainRNextTurnData(firstUserId);`
return action;
* `GenericGameAction<A> getNextActionIfTimeout(GenericGameState<S,PS> gameState, int serverTime);` //called by master client only to fire next action on behalf of a GameAI player and a player who got disconnected.
* `bool isValidActionToUpdateState(GenericGameAction<A> action, GenericGameState<S,PS> gameState);` //return as if remote action is valid or not for current game specific state of the game.
* `PS getInitialPlayerState(string userId, int serverTime, int playersCount);` //it should return the game specific player state for the requested userId. It is used to create an initial instance of `GenericGameState<S, PS> where S: IGameState where PS:IGSPlayerState` which is used by the framework to store the game’s state. For chain-reaction game, its implementation is:
return new CRInGamePlayerState(serverTime, playersCount, false, false);
API present in
IGenericGameManager<S,PS,A> where A:IGameAction where S:IGameState where PS:IGSPlayerState
should be called from appropriate places in the game-core codebase. Its non-trivial API are:GenericGameAction<A> onLocalUserAction(A gameSpecificActionData, bool isTimeout);
//call this method to notify action performed by the local user to remote players.void onRemoteActionProcessed();
//This method must be called after a remote action/event received in Section 4.2.b.vii is processed completely by the local client.void updateGameStateFromLocalGame(S gameSpecificStateFromLocalGame);
//This method may be called during processing of remote action/event received in Section 4.2.b.viivoid updatePlayerStateFromLocalGame(Dictionary<string,PS> gameSpecificPlayerState);
//This method may be called during processing of remote action/event received in Section 4.2.b.viivoid updateCurrentUserIdFromLocalGame(string userId);
//This method may be called during processing of remote action/event received in Section 4.2.b.viibool isGameStarted();
int currentServerTime();
//It returns current server time which can even be negative. Use it to get time difference or populate playerAction/game’s serverTime only.bool isPlayerLeft(string userId);
//It returns true if player is marked Left(player is marked Left when disconnectionTimeout(30 seconds) happens after a player gets disconnected)bool isConnectedToServerWithProperState();
//returns true if game is started, local client is connected with recent game state.PS getGameSpecificPlayerState(string userId);
Don’t modify the property of
GenericGameState
andGenericGameAction
from anywhere.IGameAction
interface is meant to represent any action in the game, which generally corresponds to a player. Description of non-trivial Api ofIGameAction
are:Byte getUniqueCode();
//return unique value from 2 to 199 for each action.bool shouldSendStateAlso();
//return true for one or moreIGameAction
. If true, we append local gameState to the action data while sending over the server. Before processing this action on a remote client, we first update the gameState so that all clients are in sync
IGSPlayerState
interface is meant to represent the game specific state of a player. Description of non-trivial Api ofIGSPlayerState
are:int startTime();
should return the start time of the player’s current state. It generally is the timeStamp of the last action taken by the player
IGameState
interface is meant to represent the game state like the state of board, which is not part ofIGSPlayerState
Class
JRGameInfo
Class | Data Type |
---|---|
myInfo | JRUserInfo |
opponentArray | JRUserInfo |
userCountryCode | string |
serverRegion | string |
roomName | string |
roundCount | int |
duration | int |
difficultyLevel | int |
isFue | bool |
gameVariantId | string |
igameSpecificConfigJson | string |
musicVolume | float |
activeMultiplier | string |
sfxVolume | float |
multiplierRewardDuration | int |
boosterType | string |
forcedGameAIRequired | bool |
JRUserInfo
Class | Data Type |
---|---|
userId | string |
userName | string |
profileUrl | string |
position | int |
activeMultiplier | string |
gameUserId | string |
gameLevel | int |
boosterType | string |
abandonGameLevel | int |