Welcome to "King of the Ring" (KOR). You must Attack with power & dodge with speed! Last player alive, wins.
Related Features
• Matchmaking - Connect remote players in a room
• Multiplayer - Real-time multiplayer game functionality that enables multiple players to interact in shared game sessions
1. Download the Multiplayer KOR Sample Project 2. Open in Unity Editor (Version 2021.3 or later) 3. Open the Beamable Toolbox 4. Sign-In / Register To Beamable. See Installing Beamable for more info 5. Rebuild the Unity Addressables: Unity → Window → Asset Management → Groups, then Build → Update a Previous Build 6. Open the 1.Intro Scene 7. Play The Scene: Unity → Edit → Play 8. Click "Start Game: Human vs Bot" for an easy start. Or do a standalone build of the game and run the build. Then run the Unity Editor. In both running games, choose "Start Game: Human vs Human" to play against yourself 9. Enjoy!
Note: Sample projects are compatible with the latest supported Unity versions
These steps are already complete in the sample project. The instructions here explain the process.
Related Features
• Matchmaking - Connect remote players in a room
• Multiplayer - Real-time multiplayer game functionality that enables multiple players to interact in shared game sessions
This step includes the bulk of time and effort the project.
Step
Detail
1. Create C# game-specific logic
• Implement game logic • Handle player input • Render graphics & sounds
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.
Gotchas
Here are some common issues and solutions:
• While the name is similar, this Configuration.cs is wholly unrelated to Beamable's Configuration Manager.
The "Configuration" values are easily configurable
Optional: Game Makers may experiment with new Delay values here to allow animations to occur faster or slower.
Code
The GameSceneManager is the main entry point to the Game Scene logic.
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Threading.Tasks;usingBeamable.Examples.Features.Multiplayer.Core;usingBeamable.Samples.KOR.Behaviours;usingBeamable.Samples.KOR.Data;usingBeamable.Samples.KOR.Multiplayer;usingBeamable.Samples.KOR.Multiplayer.Events;usingBeamable.Samples.KOR.Views;usingUnityEngine;usingBeamable.Experimental.Api.Sim;usingBeamable.Samples.Core.Debugging;usingBeamable.Samples.Core.UI;usingBeamable.Samples.Core.UI.DialogSystem;usingBeamable.Samples.Core.Utilities;namespaceBeamable.Samples.KOR{/// <summary>/// Handles the main scene logic: Game/// </summary>publicclassGameSceneManager:MonoBehaviour{// Properties -----------------------------------publicGameUIViewGameUIView{get{return_gameUIView;}}publicConfigurationConfiguration{get{return_configuration;}}publicList<SpawnPointBehaviour>AvailableSpawnPoints;// Fields ---------------------------------------privateIBeamableAPI_beamableAPI=null;[SerializeField]privateConfiguration_configuration=null;[SerializeField]privateGameUIView_gameUIView=null;privateAttributes_ownAttributes=null;privateList<SpawnablePlayer>_spawnablePlayers=newList<SpawnablePlayer>();privateList<SpawnPointBehaviour>_unusedSpawnPoints=newList<SpawnPointBehaviour>();privateHashSet<long>_dbidReadyReceived=newHashSet<long>();privatebool_hasSpawned=false;// Unity Methods ------------------------------protectedvoidStart(){for(inti=0;i<6;i++)_gameUIView.AvatarUIViews[i].GetComponent<CanvasGroup>().alpha=0.0f;_gameUIView.BackButton.onClick.AddListener(BackButton_OnClicked);SetupBeamable();}// Other Methods ------------------------------privatevoidDebugLog(stringmessage){// Respects Configuration.IsDebugLog CheckboxConfiguration.Debugger.Log(message);}privateasyncvoidSetupBeamable(){_beamableAPI=awaitBeamable.API.Instance;awaitRuntimeDataStorage.Instance.CharacterManager.Initialize();_ownAttributes=awaitRuntimeDataStorage.Instance.CharacterManager.GetChosenPlayerAttributes();// Do this after calling "Beamable.API.Instance" for smoother UI_gameUIView.CanvasGroupsDoFadeIn();// Set defaults if scene was loaded directlyif(RuntimeDataStorage.Instance.TargetPlayerCount==KORConstants.UnsetValue){DebugLog(KORHelper.GetSceneLoadingMessage(gameObject.scene.name,true));RuntimeDataStorage.Instance.TargetPlayerCount=1;RuntimeDataStorage.Instance.CurrentPlayerCount=1;RuntimeDataStorage.Instance.LocalPlayerDbid=_beamableAPI.User.id;RuntimeDataStorage.Instance.MatchId=KORMatchmaking.GetRandomMatchId();}else{DebugLog(KORHelper.GetSceneLoadingMessage(gameObject.scene.name,false));}// Set the ActiveSimGameType. This happens in 2+ spots to handle direct scene loadingif(RuntimeDataStorage.Instance.IsSinglePlayerMode)RuntimeDataStorage.Instance.ActiveSimGameType=await_configuration.SimGameType01Ref.Resolve();elseRuntimeDataStorage.Instance.ActiveSimGameType=await_configuration.SimGameType02Ref.Resolve();// Initialize ECSSystemManager.StartGameSystems();// Show the player's attributes in the UI of this scene_gameUIView.AttributesPanelUI.Attributes=_ownAttributes;// Initialize NetworkingawaitNetworkController.Instance.Init();// Set Available Spawns_unusedSpawnPoints=AvailableSpawnPoints.ToList();NetworkController.Instance.Log.CreateNewConsumer(HandleNetworkUpdate);// Optional: Stuff to use later when player moves are incominglongtbdIncomingPlayerDbid=_beamableAPI.User.id;// test value;DebugLog($"MinPlayerCount = {RuntimeDataStorage.Instance.MinPlayerCount}");DebugLog($"MaxPlayerCount = {RuntimeDataStorage.Instance.MaxPlayerCount}");DebugLog($"CurrentPlayerCount = {RuntimeDataStorage.Instance.CurrentPlayerCount}");DebugLog($"LocalPlayerDbid = {RuntimeDataStorage.Instance.LocalPlayerDbid}");DebugLog($"IsLocalPlayerDbid = {RuntimeDataStorage.Instance.IsLocalPlayerDbid(tbdIncomingPlayerDbid)}");DebugLog($"IsSinglePlayerMode = {RuntimeDataStorage.Instance.IsSinglePlayerMode}");// Optional: Show queueable status text onscreenSetStatusText(KORConstants.GameUIView_Playing,TMP_BufferedText.BufferedTextMode.Immediate);// Optional: Add easily configurable delaysawaitTask.Delay(TimeSpan.FromSeconds(_configuration.DelayGameBeforeMove));// Optional: Play sound//SoundManager.Instance.PlayAudioClip(SoundConstants.Click01);// Optional: Render color and text of avatar ui_gameUIView.AvatarViews.Clear();}publicasyncvoidOnPlayerJoined(PlayerJoinedEventjoinEvent){if(_spawnablePlayers.Find(i=>i.DBID==joinEvent.PlayerDbid)!=null)return;varspawnIndex=NetworkController.Instance.rand.Next(0,_unusedSpawnPoints.Count);varspawnPoint=_unusedSpawnPoints[spawnIndex];_unusedSpawnPoints.Remove(spawnPoint);SpawnablePlayernewPlayer=newSpawnablePlayer(joinEvent.PlayerDbid,spawnPoint);_spawnablePlayers.Add(newPlayer);awaitRuntimeDataStorage.Instance.CharacterManager.Initialize();newPlayer.ChosenCharacter=awaitRuntimeDataStorage.Instance.CharacterManager.GetChosenCharacterByDBID(joinEvent.PlayerDbid);stringalias=awaitRuntimeDataStorage.Instance.CharacterManager.GetPlayerAliasByDBID(joinEvent.PlayerDbid);DebugLog($"alias from joinEvent dbid={joinEvent.PlayerDbid} alias={alias}");if(joinEvent.PlayerDbid==NetworkController.Instance.LocalDbid)NetworkController.Instance.SendNetworkMessage(newReadyEvent(_ownAttributes,alias));}privatevoidOnPlayerReady(ReadyEventreadyEvt){Configuration.Debugger.Log($"Getting ready for dbid={readyEvt.PlayerDbid}"+$" attributes move/charge={readyEvt.aggregateMovementSpeed}/{readyEvt.aggregateChargeSpeed}",DebugLogLevel.Verbose);_dbidReadyReceived.Add(readyEvt.PlayerDbid);SpawnablePlayersp=_spawnablePlayers.Find(i=>i.DBID==readyEvt.PlayerDbid);sp.Attributes=newAttributes(readyEvt.aggregateChargeSpeed,readyEvt.aggregateMovementSpeed);sp.PlayerAlias=readyEvt.playerAlias;Configuration.Debugger.Log($"alias from readyEvt dbid={readyEvt.PlayerDbid} alias={sp.PlayerAlias}");Configuration.Debugger.Log($"OnPlayerReady Players={_dbidReadyReceived.Count}/{RuntimeDataStorage.Instance.CurrentPlayerCount}",DebugLogLevel.Verbose);if(!_hasSpawned&&_dbidReadyReceived.Count==RuntimeDataStorage.Instance.CurrentPlayerCount){_hasSpawned=true;SpawnAllPlayersAtOnce();StartGameTimer();}}privatevoidStartGameTimer(){GameUIView.GameTimerBehaviour.StartMatch();GameUIView.GameTimerBehaviour.OnGameOver+=async()=>{// TODO: score the players, and end the game.Debug.Log("Game over!");varuis=FindObjectsOfType<AvatarUIView>();varvalidUis=uis.Where(ui=>ui.Player).ToList();validUis.Sort((a,b)=>a.SpawnablePlayer.DBID>b.SpawnablePlayer.DBID?1:-1);validUis.Sort((a,b)=>a.Player.HealthBehaviour.Health>b.Player.HealthBehaviour.Health?-1:1);varscores=validUis.Select(ui=>newPlayerResult{playerId=ui.SpawnablePlayer.DBID,score=ui.Player.HealthBehaviour.Health,}).ToArray();varselfRank=0;varselfScore=scores[0];for(vari=0;i<scores.Length;i++){scores[i].rank=i;if(scores[i].playerId==NetworkController.Instance.LocalDbid){selfRank=i;selfScore=scores[i];}}foreach(varmotionBehaviourinFindObjectsOfType<AvatarMotionBehaviour>()){motionBehaviour.Stop();motionBehaviour.enabled=false;}foreach(varinputBehaviourinFindObjectsOfType<PlayerInputBehaviour>()){inputBehaviour.enabled=false;}varresults=awaitNetworkController.Instance.ReportResults(scores);varisWinner=selfRank==0;varearnings=string.Join(",",results.currenciesGranted.Select(grant=>$"{grant.amount}x{grant.symbol}"));varearningsBody=string.IsNullOrWhiteSpace(earnings)?"nothing":earnings;varbody="You came in place: "+(selfRank+1)+". You earned "+earningsBody;_gameUIView.DialogSystem.ShowDialogBox<DialogUI>(// Renders this prefab. DUPLICATE this prefab and drag// into _storeUIView to change layout_gameUIView.DialogSystem.DialogUIPrefab,// Set TextisWinner?KORConstants.Dialog_GameOver_Victory:KORConstants.Dialog_GameOver_Defeat,body,// Create zero or more buttonsnewList<DialogButtonData>{newDialogButtonData(KORConstants.Dialog_Ok,()=>{KORHelper.PlayAudioForUIClickPrimary();_gameUIView.DialogSystem.HideDialogBox();// Clean up manager_spawnablePlayers.Clear();_unusedSpawnPoints.Clear();_dbidReadyReceived.Clear();_hasSpawned=false;NetworkController.Instance.Cleanup();// Destroy ECSSystemManager.DestroyGameSystems();// Change scenesStartCoroutine(KORHelper.LoadScene_Coroutine(_configuration.IntroSceneName,_configuration.DelayBeforeLoadScene));})});};}privatevoidSpawnAllPlayersAtOnce(){List<CanvasGroup>avatarUiCanvasGroups=newList<CanvasGroup>();for(intp=0;p<_spawnablePlayers.Count;p++){SpawnablePlayersp=_spawnablePlayers[p];Configuration.Debugger.Log($"DBID={sp.DBID} Spawning character={sp.ChosenCharacter.CharacterContentObject.ContentName}"+$" attributes move/charge={sp.Attributes.MovementSpeed}/{sp.Attributes.ChargeSpeed}",DebugLogLevel.Verbose);DebugLog($"playerAlias={sp.PlayerAlias}");AvatarViewavatarView=GameObject.Instantiate<AvatarView>(sp.ChosenCharacter.AvatarViewPrefab);avatarView.transform.SetPhysicsPosition(sp.SpawnPointBehaviour.transform.position);Playerplayer=avatarView.gameObject.GetComponent<Player>();player.SetAlias(sp.PlayerAlias);avatarView.SetForPlayer(sp.DBID);_gameUIView.AvatarViews.Add(avatarView);if(sp.DBID==NetworkController.Instance.LocalDbid)avatarView.gameObject.GetComponent<AvatarMotionBehaviour>().PreviewBehaviour=null;elseavatarView.gameObject.GetComponent<PlayerInputBehaviour>().enabled=false;AvatarMotionBehaviouramb=avatarView.gameObject.GetComponent<AvatarMotionBehaviour>();amb.Attributes=sp.Attributes;_gameUIView.AvatarUIViews[p].Set(player,sp);_gameUIView.AvatarUIViews[p].Render();avatarUiCanvasGroups.Add(_gameUIView.AvatarUIViews[p].GetComponent<CanvasGroup>());}TweenHelper.CanvasGroupsDoFade(avatarUiCanvasGroups,0.0f,1.0f,1.0f,0.0f,0.0f);}publicvoidHandleNetworkUpdate(TimeUpdateupdate){foreach(varevtinupdate.Events){HandleNetworkEvent(evt);}}publicvoidHandleNetworkEvent(KOREventkorEvent){switch(korEvent){caseReadyEventreadyEvt:OnPlayerReady(readyEvt);break;casePlayerJoinedEventjoinEvt:OnPlayerJoined(joinEvt);break;}}/// <summary>/// Render UI text/// </summary>/// <param name="message"></param>/// <param name="statusTextMode"></param>publicvoidSetStatusText(stringmessage,TMP_BufferedText.BufferedTextModestatusTextMode){_gameUIView.BufferedText.SetText(message,statusTextMode);}// Event Handlers -------------------------------privatevoidBackButton_OnClicked(){KORHelper.PlayAudioForUIClickBack();_gameUIView.DialogSystem.ShowDialogBox<DialogUI>(// Renders this prefab. DUPLICATE this prefab and drag// into _storeUIView to change layout_gameUIView.DialogSystem.DialogUIPrefab,// Set TextKORConstants.Dialog_AreYouSure,"This will end your game.",// Create zero or more buttonsnewList<DialogButtonData>{newDialogButtonData(KORConstants.Dialog_Ok,()=>{KORHelper.PlayAudioForUIClickPrimary();_gameUIView.DialogSystem.HideDialogBox();// Clean up manager_spawnablePlayers.Clear();_unusedSpawnPoints.Clear();_dbidReadyReceived.Clear();_hasSpawned=false;NetworkController.Instance.Cleanup();// Destroy ECSSystemManager.DestroyGameSystems();// Change scenesStartCoroutine(KORHelper.LoadScene_Coroutine(_configuration.IntroSceneName,_configuration.DelayBeforeLoadScene));}),newDialogButtonData(KORConstants.Dialog_Cancel,()=>{KORHelper.PlayAudioForUIClickSecondary();_gameUIView.DialogSystem.HideDialogBox();})});}}}
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.
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.
Here are some optional experiments game makers can complete in 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 Configuration
• Update the Configuration.asset values in the Unity Inspector Window
Note: Experiment and have fun!
Intermediate
Lobby
Add Lobby Graphics
• The lobby shows text indicating "Player 1/2 joined" • As each player joins the multiplayer matchmaking session, show the 2D asset onscreen and player's name
Intermediate
Game
Add a new character
• The game includes a character selector and several characters • Add 2D/3D assets for a new character • Update Beamable content to define the new character
Note: No 3D skills? An alternative is to duplicate an existing 3D character prefab and recolor its texture
Intermediate
Game
Add "Jump" Input
• The game includes 'tap and hold' input to move the character • Add a 'Jump' button in the bottom menu • Apply a physics force upwards on the local player
Note: Send a new multiplayer game event to all players to keep the game in sync
Advanced
Game
Add a collectible pickup
• Spawn an item in to the game world • A character collides with the item to collect the item • Collecting the item rewards the player (Shield, Speed, etc...)
Advanced
Game
Add a bomb
• Spawn a bomb in to the game world • After 3 seconds the bomb explodes and disappears • The explosion causes a physics force to push away players and items
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 matchmaking service.