
Beamable Labs
This Beamable functionality is currently in-development and subject to change. Game makers are invited and encouraged to try it out, send feedback, and help shape the future of the product.
Overview
Welcome to "Turn-based Beamable Fighters" (TBF). This downloadable sample game project showcases the Beamable Multiplayer FeatureFeature - An individual aspect of the Beamable product used to create a great user experience.
This document and the sample project allow game makers to understand and apply the benefits of Multiplayer in game development. Or watch this video.
Download
These learning resources provide a better way to build live games in Unity.
Source | Detail |
---|---|
![]() ![]() |
Note: Each sample project is compatible with Mac & Windows and includes the Beamable SDK. Each sample project requires internet connectivity. |
Rules of the Game
The game has 3 rounds. The player to win the most rounds, wins the game.
In each round, each player makes one attack move. After each round the moves are evaluated and a winner is declared.
The attack moves are evaluated with criteria similar to Rock, Paper, Scissors. See Wikipedia's Rock, Paper, Scissors for more info.
In the sample game, there are 3 possible attack moves with the following results;
- High beats Mid
- Mid beats Low
- Low beats High
If both players choose the same attack move, there is a tie with no consequences — the round simply repeats.
Screenshots
The player navigates from the Intro Scene to the Game Scene, where all the action takes place.
Intro Scene | Lobby Scene |
Game Scene | Project Window |
Player Experience Flowchart
Here is the high level execution flow of user input and system interactions.
Game Maker User Experience
During development, the game maker's user experience is as follows. There are several major parts to this game creation process.
Steps
Follow these steps to get started.
These steps are already complete in the sample project. The instructions here explain the process.
Related Guides
A common use case for the feature is covered in the guides. It is recommended to read the guide before continuing with the sample steps below. See Multiplayer for more info.
Step 1. Setup Project
Here are instructions to setup the Beamable SDK and "GameType" content.
Step | Detail |
---|---|
| • See Step 1 - Getting Started for more info. |
| • Unity → Window → Beamable → Open Content Manager |
| • Click the |
| ![]() ![]() • Click the |
| • Unity → Window → General→ Project |
| • Search by the name given in step #3 |
| ![]() ![]() • Populate the Note: The other fields are optional and may be needed for advanced use cases |
| • Unity → File → Save Project Best Practice: If you are working on a team, commit to version control in this step |
| • Press the "Publish" button in the Content Manager Window |
Step 2. Plan the Game Design
Multiplayer game development offers additional challenges beyond those of a typical single player game.
When designing a game with Beamable’s Multiplayer FeatureFeature - An individual aspect of the Beamable product used to create a great user experience, it is important to plan which user interactions will be sent to the servers.
Step | Detail |
---|---|
| • What are player motivations? Note: If you are new to Multiplayer game design, this is a good place to start. Think about the game as if it was indeed a single-player experience. Then iterate on the design in the following steps |
| • What is each user input gesture? |
| • What else impacts the user experience? |
| • How many event objects are needed? Note: A solid, purposeful design in this step is important to create a pleasing multiplayer game experience. See The Balancing Act below for more info |
These user interactions are relayed as event objects to all connected clients. Each client will deterministically simulate those events to ensure a consistent user experience for all players.
The Balancing Act
Deciding which user interactions require events and how to design the event payloads, striking a balance is recommended.
- Too Few Events: Each game requires a certain amount of events to faithfully and deterministically keep all players in sync. Sending too few will cause the game to fall out of sync or lack the polish expected by the game community. The same is true if each event contain too little data to represent the needs of the game design.
- Too Many Events: Each event requires reasonable overhead for serializing, sending, receiving, and processing data. Sending too many can cause latency and the players will notice lag in the user experience. The same is true if the events contain unnecessarily heavy data within each event.
Expert Advice
Here is more info from Beamable's development team.
"Determinism of the game simulation is important due to the nature of our server implementation. Because Beamable's relay servers are game state agnostic, any sort of state checkpointing or CPU architectural differences are not corrected on the server side so in order to keep the game state in sync on all clients, it is imperative that the game be made deterministic. "
• See Ruoyu Sun's Game Networking Article for more info
Step 3. Create the Game Code
This step includes the bulk of time and effort the project.
Step | Detail |
---|---|
| • Implement game logic Note: This represents the bulk of the development effort. The details depend on the specifics of the game project. |
Inspector
Here is the GameSceneManager.cs
main entry point for the Game Scene interactivity.


The "Configuration" and "GameUIView" are passed as references
Here is the Configuration.cs
holding high-level, easily-configurable values used by various areas on the game code. Several game classes reference this data.
Optional: Game Makers may experiment with new values here to allow the players' turns to occur faster or slower.


The "Configuration" values are easily configurable
Code
The GameSceneManager
is the main entry point to the Game Scene logic.
Here is a partial code snippet. Download the project to see the complete code.
namespace Beamable.Samples.TBF
{
public enum GameMoveType
{
Null,
High, // Like "Rock"
Medium, // Like "Paper"
Low // Like "Scissors"
}
public class GameSceneManager : MonoBehaviour
{
// Properties -----------------------------------
public GameUIView GameUIView { get { return _gameUIView; } }
public Configuration Configuration { get { return _configuration; } }
public TBFMultiplayerSession MultiplayerSession { get { return _multiplayerSession; } }
public RemotePlayerAI RemotePlayerAI { get { return _remotePlayerAI; } set { _remotePlayerAI = value; } }
public GameProgressData GameProgressData { get { return _gameProgressData; } set { _gameProgressData = value; } }
// Unity Methods ------------------------------
protected void Start()
{
_gameUIView.BackButton.onClick.AddListener(BackButton_OnClicked);
_gameUIView.MoveButton_01.onClick.AddListener(MoveButton_01_OnClicked);
_gameUIView.MoveButton_02.onClick.AddListener(MoveButton_02_OnClicked);
_gameUIView.MoveButton_03.onClick.AddListener(MoveButton_03_OnClicked);
_gameUIView.AvatarUIViews[TBFConstants.PlayerIndexLocal].HealthBarView.Health = 100;
_gameUIView.AvatarUIViews[TBFConstants.PlayerIndexRemote].HealthBarView.Health = 100;
//
_gameStateHandler = new GameStateHandler(this);
SetupBeamable();
}
protected void Update()
{
_multiplayerSession?.Update();
}
// Other Methods -----------------------------
private async void SetupBeamable()
{
await _gameStateHandler.SetGameState(GameState.Loading);
await Beamable.API.Instance.Then(async de =>
{
await _gameStateHandler.SetGameState (GameState.Loaded);
try
{
_beamableAPI = de;
_multiplayerSession = new TBFMultiplayerSession(
RuntimeDataStorage.Instance.LocalPlayerDbid,
RuntimeDataStorage.Instance.TargetPlayerCount,
RuntimeDataStorage.Instance.RoomId) ;
await _gameStateHandler.SetGameState(GameState.Initializing);
_multiplayerSession.OnInit += MultiplayerSession_OnInit;
_multiplayerSession.OnConnect += MultiplayerSession_OnConnect;
_multiplayerSession.OnDisconnect += MultiplayerSession_OnDisconnect;
_multiplayerSession.Initialize();
}
catch (Exception)
{
SetStatusText(TBFHelper.InternetOfflineInstructionsText, BufferedTextMode.Immediate);
}
});
}
public void SetStatusText(string message, BufferedTextMode statusTextMode)
{
_gameUIView.BufferedText.SetText(message, statusTextMode);
}
private void BindPlayerDbidToEvents(long playerDbid, bool isBinding)
{
if (isBinding)
{
string origin = playerDbid.ToString();
_multiplayerSession.On<GameStartEvent>(origin, MultiplayerSession_OnGameStartEvent);
_multiplayerSession.On<GameMoveEvent>(origin, MultiplayerSession_OnGameMoveEvent);
}
else
{
_multiplayerSession.Remove<GameStartEvent>(MultiplayerSession_OnGameStartEvent);
_multiplayerSession.Remove<GameMoveEvent>(MultiplayerSession_OnGameMoveEvent);
}
}
private void SendGameMoveEventSave(GameMoveType gameMoveType)
{
if (_gameStateHandler.GameState == GameState.RoundPlayerMoving)
{
_gameUIView.MoveButtonsCanvasGroup.interactable = false;
SoundManager.Instance.PlayAudioClip(SoundConstants.Click02);
_multiplayerSession.SendEvent<GameMoveEvent>(
new GameMoveEvent(gameMoveType));
}
}
// Event Handlers -------------------------------
private void BackButton_OnClicked()
{
//Change scenes
StartCoroutine(TBFHelper.LoadScene_Coroutine(_configuration.IntroSceneName,
_configuration.DelayBeforeLoadScene));
}
private void MoveButton_01_OnClicked()
{
SendGameMoveEventSave(GameMoveType.High);
}
private void MoveButton_02_OnClicked()
{
SendGameMoveEventSave(GameMoveType.Medium);
}
private void MoveButton_03_OnClicked()
{
SendGameMoveEventSave(GameMoveType.Low);
}
private async void MultiplayerSession_OnInit(System.Random random)
{
await _gameStateHandler.SetGameState(GameState.Initialized);
}
private async void MultiplayerSession_OnConnect(long playerDbid)
{
BindPlayerDbidToEvents(playerDbid, true);
if (_multiplayerSession.PlayerDbidsCount < _multiplayerSession.TargetPlayerCount)
{
await _gameStateHandler.SetGameState (GameState.Connecting);
}
else
{
await _gameStateHandler.SetGameState (GameState.Connected);
_multiplayerSession.SendEvent<GameStartEvent>(new GameStartEvent());
}
}
private void MultiplayerSession_OnDisconnect(long playerDbid)
{
BindPlayerDbidToEvents(playerDbid, false);
SetStatusText(string.Format(TBFConstants.StatusText_Multiplayer_OnDisconnect,
_multiplayerSession.PlayerDbidsCount.ToString(),
_multiplayerSession.TargetPlayerCount), BufferedTextMode.Immediate);
}
private async void MultiplayerSession_OnGameStartEvent(GameStartEvent gameStartEvent)
{
//TODO: check if I got X responses. Don't check the following...
if (_multiplayerSession.PlayerDbidsCount == _multiplayerSession.TargetPlayerCount)
{
await _gameStateHandler.SetGameState (GameState.GameStarted);
}
}
private async void MultiplayerSession_OnGameMoveEvent(GameMoveEvent gameMoveEvent)
{
if (_gameStateHandler.GameState == GameState.RoundPlayerMoving)
{
//Add each player event to a list
_gameProgressData.GameMoveEventsThisRoundByPlayerDbid[gameMoveEvent.PlayerDbid] = gameMoveEvent;
await _gameStateHandler.SetGameState(GameState.RoundPlayerMoved);
}
}
}
}
The following could indeed have been placed within the GameStateManager.cs
class above. However, to simplify the game's architecture and to improve readability, it is self-contained.
The GameStateHandler.cs
class structures the game into approximately 15 individual GameState
values, each responsible for a unique moment in the game flow.
Here is a partial code snippet. Download the project to see the complete code.
namespace Beamable.Samples.TBF
{
// The are arguable more states than necessary.
// The current setup provides very clear visibility
// into the subtleties of the user's flow through
// the game.
public enum GameState
{
// The game loads within here
Null,
Loading,
Loaded,
Initializing,
Initialized,
Connecting,
Connected,
GameStarting,
GameStarted,
// The game repeats within here
RoundStarting,
RoundStarted,
RoundPlayerMoving,
RoundPlayerMoved,
RoundEvaluating,
RoundEvaluated,
// The game ends here
GameEnding,
}
public class GameStateHandler
{
// Properties -----------------------------------
public GameState GameState { get { return _gameState; } }
// Fields ---------------------------------------
private GameState _gameState = GameState.Null;
private GameSceneManager _gameSceneManager;
// Other Methods -----------------------------
public GameStateHandler(GameSceneManager gameSceneManager)
{
_gameSceneManager = gameSceneManager;
}
public async Task SetGameState(GameState gameState)
{
//NOTE: Do not set "_gameState" directly anywhere, except here.
_gameState = gameState;
// SetGameState() is async...
// Pros: We can use operations like "Task.Delay" to slow down execution
// Cons: Error handling is tricky.
// Workaround: AsyncUtility helps with its try/catch.
await AsyncUtility.AsyncSafe(async () =>
{
switch (_gameState)
{
case GameState.Null:
break;
case GameState.Loading:
// **************************************
// Render the scene before any latency
// of multiplayer begins
// **************************************
_gameSceneManager.SetStatusText("", BufferedTextMode.Immediate);
_gameSceneManager.GameUIView.AvatarViews[TBFConstants.PlayerIndexLocal].PlayAnimationIdle();
_gameSceneManager.GameUIView.AvatarViews[TBFConstants.PlayerIndexRemote].PlayAnimationIdle();
_gameSceneManager.GameProgressData = new GameProgressData(_gameSceneManager.Configuration);
_gameSceneManager.GameUIView.MoveButtonsCanvasGroup.interactable = false;
_gameSceneManager.SetStatusText(TBFConstants.StatusText_GameState_Loading, BufferedTextMode.Queue);
break;
case GameState.Loaded:
// **************************************
// Update UI
//
// **************************************
_gameSceneManager.SetStatusText(TBFConstants.StatusText_GameState_Loaded, BufferedTextMode.Queue);
break;
case GameState.Initializing:
// **************************************
// Update UI
//
// **************************************
_gameSceneManager.SetStatusText(TBFConstants.StatusText_GameState_Initializing, BufferedTextMode.Queue);
break;
case GameState.Initialized:
// **************************************
// Update UI
//
// **************************************
_gameSceneManager.SetStatusText(TBFConstants.StatusText_GameState_Initialized, BufferedTextMode.Queue);
break;
case GameState.Connecting:
// **************************************
// Update UI
//
// **************************************
_gameSceneManager.SetStatusText(string.Format(TBFConstants.StatusText_GameState_Connecting,
_gameSceneManager.MultiplayerSession.PlayerDbidsCount.ToString(),
_gameSceneManager.MultiplayerSession.TargetPlayerCount), BufferedTextMode.Queue);
break;
case GameState.Connected:
// **************************************
// Advanced the state
//
// **************************************
await SetGameState(GameState.GameStarting);
break;
case GameState.GameStarting:
// **************************************
// Reset the game-specific data
//
// **************************************
_gameSceneManager.GameProgressData.GameRoundCurrent = 0;
break;
case GameState.GameStarted:
// **************************************
// Now that all players have connected,
// setup AI
//
// **************************************
// RemotePlayerAI is always created, but enabled only sometimes
bool isEnabledRemotePlayerAI = _gameSceneManager.MultiplayerSession.IsHumanVsBotMode;
System.Random random = _gameSceneManager.MultiplayerSession.Random;
_gameSceneManager.RemotePlayerAI = new RemotePlayerAI(random, isEnabledRemotePlayerAI);
await SetGameState(GameState.RoundStarting);
break;
case GameState.RoundStarting:
// **************************************
// Reste the round-specific data.
// Advance the state.
// This happens before EACH round during a game
// **************************************
_gameSceneManager.GameProgressData.GameRoundCurrent++;
_gameSceneManager.GameProgressData.GameMoveEventsThisRoundByPlayerDbid.Clear();
await SetGameState(GameState.RoundStarted);
break;
case GameState.RoundStarted:
// **************************************
// Advance the state
//
// **************************************
while (_gameSceneManager.GameUIView.BufferedText.HasRemainingQueueText)
{
// Wait for old messages to pass before allowing button clicks
await Await.NextUpdate();
}
_gameSceneManager.GameUIView.MoveButtonsCanvasGroup.interactable = true;
await SetGameState(GameState.RoundPlayerMoving);
break;
case GameState.RoundPlayerMoving:
// **************************************
// Update UI
//
// **************************************
_gameSceneManager.SetStatusText(string.Format(TBFConstants.StatusText_GameState_PlayerMoving,
_gameSceneManager.GameProgressData.GameRoundCurrent), BufferedTextMode.Queue);
break;
case GameState.RoundPlayerMoved:
// **************************************
// Render the audio and animations
// Advance the state
// **************************************
long localPlayerDbid = _gameSceneManager.MultiplayerSession.GetPlayerDbidForIndex(TBFConstants.PlayerIndexLocal);
GameMoveEvent localGameMoveEvent;
_gameSceneManager.GameProgressData.GameMoveEventsThisRoundByPlayerDbid.TryGetValue(localPlayerDbid, out localGameMoveEvent);
GameMoveType localGameMoveType = localGameMoveEvent.GameMoveType;
GameMoveType remoteGameMoveType = GameMoveType.Null;
if (_gameSceneManager.RemotePlayerAI.IsEnabled)
{
remoteGameMoveType = _gameSceneManager.RemotePlayerAI.GetNextGameMoveType();
}
else
{
long remotePlayerDbid = _gameSceneManager.MultiplayerSession.GetPlayerDbidForIndex(TBFConstants.PlayerIndexRemote);
GameMoveEvent remoteGameEvent;
_gameSceneManager.GameProgressData.GameMoveEventsThisRoundByPlayerDbid.TryGetValue(remotePlayerDbid, out remoteGameEvent);
remoteGameMoveType = remoteGameEvent.GameMoveType;
}
// LOCAL
await RenderPlayerMove(TBFConstants.PlayerIndexLocal, localGameMoveType);
// REMOTE
await RenderPlayerMove(TBFConstants.PlayerIndexRemote, remoteGameMoveType);
// All players have moved
_gameSceneManager.SetStatusText(string.Format(TBFConstants.StatusText_GameState_PlayersAllMoved,
_gameSceneManager.GameProgressData.GameRoundCurrent), BufferedTextMode.Queue);
if (_gameSceneManager.GameProgressData.GameMoveEventsThisRoundByPlayerDbid.Count ==
_gameSceneManager.MultiplayerSession.TargetPlayerCount)
{
// ALL players moved? See who won the round
await SetGameState(GameState.RoundEvaluating);
}
else
{
// Only SOME players moved? Wait for others...
await SetGameState(GameState.RoundPlayerMoving);
}
break;
case GameState.RoundEvaluating:
// **************************************
// Advance the state
//
// **************************************
_gameSceneManager.GameProgressData.EvaluateGameMoveEventsThisRound();
await SetGameState(GameState.RoundEvaluated);
break;
case GameState.RoundEvaluated:
// **************************************
// Evaluate the complete round results,
// and present the ROUND winner
//
// **************************************
long roundWinnerDbid = _gameSceneManager.GameProgressData.GetRoundWinnerPlayerDbid();
string roundWinnerName;
if (_gameSceneManager.MultiplayerSession.IsLocalPlayerDbid(roundWinnerDbid))
{
roundWinnerName = GetPlayerName(TBFConstants.PlayerIndexLocal);
}
else
{
roundWinnerName = GetPlayerName(TBFConstants.PlayerIndexRemote);
}
_gameSceneManager.SetStatusText(string.Format(TBFConstants.StatusText_GameState_Evaluated,
_gameSceneManager.GameProgressData.GameRoundCurrent, roundWinnerName), BufferedTextMode.Queue);
while (_gameSceneManager.GameUIView.BufferedText.HasRemainingQueueText)
{
// Wait for old messages to pass before allowing button clicks
await Await.NextUpdate();
}
if (_gameSceneManager.MultiplayerSession.IsLocalPlayerDbid(roundWinnerDbid))
{
_gameSceneManager.GameUIView.AvatarUIViews[TBFConstants.PlayerIndexRemote].HealthBarView.Health -= 34;
}
else
{
_gameSceneManager.GameUIView.AvatarUIViews[TBFConstants.PlayerIndexLocal].HealthBarView.Health -= 34;
}
//Wait for animations to finish
await AsyncUtility.TaskDelaySeconds(_gameSceneManager.Configuration.DelayGameBeforeGameOver);
if (_gameSceneManager.GameProgressData.GameHasWinner())
{
await SetGameState(GameState.GameEnding);
}
else
{
await SetGameState(GameState.RoundStarting);
}
break;
case GameState.GameEnding:
// **************************************
// Evaluate the complete round results,
// and present the GAME winner
//
// **************************************
long gameWinnerDbid = _gameSceneManager.GameProgressData.GetGameWinnerPlayerDbid();
string gameWinnerName;
if (_gameSceneManager.MultiplayerSession.IsLocalPlayerDbid(gameWinnerDbid))
{
gameWinnerName = GetPlayerName(TBFConstants.PlayerIndexLocal);
//Local winner
SoundManager.Instance.PlayAudioClip(SoundConstants.GameOverWin);
_gameSceneManager.GameUIView.AvatarViews[TBFConstants.PlayerIndexLocal].PlayAnimationWin();
_gameSceneManager.GameUIView.AvatarViews[TBFConstants.PlayerIndexRemote].PlayAnimationLoss();
}
else
{
gameWinnerName = GetPlayerName(TBFConstants.PlayerIndexRemote);
//Remote winner
SoundManager.Instance.PlayAudioClip(SoundConstants.GameOverLoss);
_gameSceneManager.GameUIView.AvatarViews[TBFConstants.PlayerIndexLocal].PlayAnimationLoss();
_gameSceneManager.GameUIView.AvatarViews[TBFConstants.PlayerIndexRemote].PlayAnimationWin();
}
_gameSceneManager.SetStatusText(string.Format(TBFConstants.StatusText_GameState_Ending,
_gameSceneManager.GameProgressData.GameRoundCurrent, gameWinnerName), BufferedTextMode.Queue);
break;
default:
SwitchDefaultException.Throw(_gameState);
break;
}
}, new System.Diagnostics.StackTrace(true));
}
}
}
Step 4. Create the Multiplayer Code
Now that the core game logic is setup, use Beamable to connect 2 (or more) players together. Create the Multiplayer event objects, send outgoing events, and handle incoming events.
Step | Detail |
---|---|
| • Create event objects Note: Its likely that game makers will add multiplayer functionality throughout development including during step #3. For sake of clarity, it is described here as a separate, final step #4. |
| • Unity → Edit → Play |
| • Can you beat the enemy? |
| • Unity → Edit → Stop |
Code
Here are a few highlights from the project's major calls to Beamable's Multiplayer FeatureFeature - An individual aspect of the Beamable product used to create a great user experience.
Here is a partial code snippet. Download the project to see the complete code.
Create Connection
Here the TBFMultiplayerSession.cs
class creates a new connection with Beamable's Multiplayer back-end.
public void Initialize()
{
// Create Multiplayer Session
_simClient = new SimClient(new SimNetworkEventStream(_roomId),
FramesPerSecond, TargetNetworkLead);
// Handle Common Events
_simClient.OnInit(SimClient_OnInit);
_simClient.OnConnect(SimClient_OnConnect);
_simClient.OnDisconnect(SimClient_OnDisconnect);
_simClient.OnTick(SimClient_OnTick);
}
Send Event Object
Here the GameSceneManager.cs
class sends a multiplayer event object to Beamable's Multiplayer back-end.
private void SendGameMoveEventSave(GameMoveType gameMoveType)
{
if (_gameStateHandler.GameState == GameState.RoundPlayerMoving)
{
_gameUIView.MoveButtonsCanvasGroup.interactable = false;
SoundManager.Instance.PlayAudioClip(SoundConstants.Click02);
_multiplayerSession.SendEvent<GameMoveEvent>(
new GameMoveEvent(gameMoveType));
}
}
Receive Event Object
Here the GameSceneManager.cs
class receives a multiplayer event object from Beamable's Multiplayer back-end.
private async void MultiplayerSession_OnGameMoveEvent(GameMoveEvent gameMoveEvent)
{
if (_gameStateHandler.GameState == GameState.RoundPlayerMoving)
{
//Add each player event to a list
_gameProgressData.GameMoveEventsThisRoundByPlayerDbid[gameMoveEvent.PlayerDbid] = gameMoveEvent;
await _gameStateHandler.SetGameState(GameState.RoundPlayerMoved);
}
}
Additional Experiments
Here some optional experiments game makers can perform with the sample project.
Did you complete all the experiments with success? We'd love to hear about it. Contact us.
Difficulty | Scene | Name | Detail |
---|---|---|---|
Beginner | Game | Tweak | • Select the Note: Most changes can be done while the game is running. However, if a change does not appear to work, stop and play Unity. |
Intermediate | Game | Add State Design Pattern | • Create a new Note: For ease of understandability and readability, the sample project uses a very light version of the State Pattern. See State Design Pattern for more info. |
Advanced | Game | Add a "Dodge" move type | • Offer this as a 4th button to the user during game play Note: Use this as inspiration, or create your own new move type with other results. Have fun! |
Advanced
Here are a few advanced configuration options and workflows.
What about matchmaking?
In multiplayer gaming, matchmaking is the process of choosing a Room based on criteria. E.g. "Give me a room to play in with 2 total players of any skill level". Beamable supports matchmaking through its `MatchmakingService'. See Multiplayer: What about Matchmaking? for more info.
Here is a custom, game-specific implementation which matches any 2 players without filtering. Depending on game design needs, the service can be extended to filter and match players with similar attributes; e.g. game skill level, network latency, spoken language, or geographic location.
Here is a partial code snippet. Download the project to see the complete code.
Create Connection
Here the MyMatchmaking.cs
class creates a new matchmaking session with Beamable's Multiplayer back-end.
namespace Beamable.Examples.Features.Multiplayer
{
public class MyMatchmaking
{
private MyMatchmakingResult _myMatchmakingResult;
private MatchmakingService _matchmakingService;
private SimGameType _simGameType;
public MyMatchmaking(MatchmakingService matchmakingService,
SimGameType simGameType, long LocalPlayerDbid)
{
_matchmakingService = matchmakingService;
_simGameType = simGameType;
_myMatchmakingResult = new MyMatchmakingResult(LocalPlayerDbid,
_simGameType.numberOfPlayers);
}
public async Task<MyMatchmakingResult> Start()
{
await _matchmakingService.Match(_simGameType.Id);
//TODO: Handle match results
//...
}
}
}
Playing "Against Yourself"
One of the challenges of developing a multiplayer game is testing it frequently as an individual developer. Finding a friend to play against you every time you run the game in development is prohibitive.
Here are 2 strategies to help the development process.
1. Add a Bot
The sample game includes an optional bot opponent. This is an AI that plays against you. Simply click "Start Game: Human vs Bot" from the Menu Scene to activate it. Architecturally this is similar to the full game and it fully uses the Beamable Multiplayer event objects for each player move. However, it requires only one human player.
2. Play Against Yourself
The sample game allows a workflow to test the full 2 player experience with relative ease. Build the Unity sample project as a standalone game for either Mac or PC. Then run the game in the Unity Editor also. Simply click "Start Game: Human vs Human" from the Menu Scene in both game clients to activate it.
Doing a build takes a few minutes. Depending on the specifics of your game, it may not be required to rebuild the standalone after every code change. See Multiplayer: Game Maker User Experience for more info.
Randomization and Determinism
In single-player game design, game makers may freely use randomization libraries to add variety to game play. The environment may be randomized, the enemy amount and variety can change every game session. This adds richness and replayability to the game.
In a multiplayer game its important that all players in a given game session have the SAME "random" experience. But how?
Randomization is supported by Beamable's Multiplayer with the following advice. During initialization the Beamable SimClient
will pass along the same seed value to feed the random operations on each client. By using the same seed, each game client will be in-sync.
Here is a partial code snippet. Download the project to see the complete code.
private void SimClient_OnInit(string sessionSeed)
{
int sessionSeedInt = int.Parse(sessionSeed);
// Store this as a private variable and use it for
// all randomization calls during game play
System.Random random = new System.Random(sessionSeedInt);
}
What about game security?
To be more secure against any malicious hackers in your multiplayer game’s community, consider never sending any game object state (player’s heath, power of weapon, etc) as part of the messages to be synchronized over the network.
Any enterprising hacker can “easily” hack a game client to send inflated values which will only make the game worse for everyone. If all a player can do is send an “action”. i.e. “What I did” (“I fired my equipped gun” rather than “I hit player 2 for 100 damage”), it becomes quite a bit harder for someone to cheat.
A hacker may also attempt to replay actions AGAIN for a particular turn or to send malformed game event object messages that can lead to a worst experience for other players.
Learning Resources
These learning resources provide a better way to build live games in Unity.
Source | Detail |
---|---|
![]() ![]() |
Note: Each sample project is compatible with Mac & Windows and includes the Beamable SDK. Each sample project requires internet connectivity. |
Updated 20 days ago