1. Download the Microservices BBB 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. Complete the Docker setup. See Microservices for more info 6. Click to "Start" the server. See Microservices for more info 7. Publish the Beamable Content to your realm. See Content Manager for more info 8. Open the 1.Intro Scene 9. Play The Scene: Unity → Edit → Play
Note: Sample projects are compatible with the latest supported Unity versions
The player experience flowchart shows the game flow:
The player battles the boss and the workload is appropriately divided between the C# game client code and the C# Beamable Microservice code. Goals of using server-authoritative programming include to increase the game's security (against any malicious hackers in the game's community) and to improve live ops workflows.
StartTheBattle () - Public Microservice method to reset the BossHealth Stat and randomize HeroWeaponIndex Stat
AttackTheBoss () - Public Microservice method to reduce the BossHealth Stat based on Weapon Content
namespaceBeamable.Samples.BBB{publicclassBBBGameManagerSA:MonoBehaviour{// Fields ---------------------------------------[SerializeField]privateGameUI_gameUI=null;[SerializeField]privateHeroView_heroView=null;[SerializeField]privateBossView_bossView=null;[SerializeField]privatePointsView_pointsViewPrefab=null;[SerializeField]privateConfiguration_configuration=null;[Header("Content")][SerializeField]privateBossContentRef_bossContentRef=null;[SerializeField]privateList<WeaponContentRef>_weaponContentRefs=null;privatePointsView_pointsViewInstance=null;privateBBBGameMicroserviceClient_bbbGameMicroserviceClient=null;// Unity Methods ------------------------------protectedvoidStart(){_gameUI.AttackButton.onClick.AddListener(AttackButton_OnClicked);// Block user interaction_gameUI.CanvasGroup.DOFade(0,0);_gameUI.AttackButton.interactable=false;_bbbGameMicroserviceClient=newBBBGameMicroserviceClient();StartTheBattle();}// Other Methods --------------------------------privatevoidStartTheBattle(){intheroWeaponIndexMax=_weaponContentRefs.Count;// ----------------------------// Call Microservice Method #1// ----------------------------_bbbGameMicroserviceClient.StartTheBattle(_bossContentRef,heroWeaponIndexMax).Then((StartTheBattleResultsresults)=>{_gameUI.BossHealthBarView.Health=results.BossHealthRemaining;_gameUI.AttackButtonText.text=BBBHelper.GetAttackButtonText(results.HeroWeaponIndex);// Find the Weapon data from the Weapon content_weaponContentRefs[results.HeroWeaponIndex].Resolve().Then(content=>{//TODO; Fix fade in of models (Both scenes)BBBHelper.RenderersDoFade(_bossView.Renderers,0,1,0,3);BBBHelper.RenderersDoFade(_heroView.Renderers,0,1,1,3);// Allow user interaction_gameUI.AttackButton.interactable=true;_gameUI.CanvasGroup.DOFade(1,1).SetDelay(0.50f);}).Error((Exceptionexception)=>{System.Console.WriteLine("_bossContentRef.Resove() error: "+exception.Message);});}).Error((Exceptionexception)=>{UnityEngine.Debug.Log($"StartTheBattle() error:{exception.Message}");});}privateIEnumeratorAttack(){_gameUI.AttackButton.interactable=false;// Wait - ClickyieldreturnnewWaitForSeconds(_configuration.Delay1BeforeAttack);SoundManager.Instance.PlayAudioClip(SoundConstants.Click02);// Wait - BackswingyieldreturnnewWaitForSeconds(_configuration.Delay2BeforeBackswing);SoundManager.Instance.PlayAudioClip(SoundConstants.Unsheath01);_heroView.PrepareAttack();boolisDone=false;// ----------------------------// Call Microservice Method #2// ----------------------------AttackTheBossResultsattackTheBossResults=null;_bbbGameMicroserviceClient.AttackTheBoss(_weaponContentRefs).Then((AttackTheBossResultsresults)=>{isDone=true;attackTheBossResults=results;}).Error((Exceptionexception)=>{UnityEngine.Debug.Log($"AttackTheBoss() error:{exception.Message}");});while(!isDone){yieldreturnnewWaitForEndOfFrame();}// Wait - SwingyieldreturnnewWaitForSeconds(_configuration.Delay3BeforeForeswing);SoundManager.Instance.PlayAudioClip(SoundConstants.Swing01);_heroView.Attack();// Show floating text, "-35" or "Missed!"if(_pointsViewInstance!=null){Destroy(_pointsViewInstance.gameObject);}_pointsViewInstance=Instantiate<PointsView>(_pointsViewPrefab);_pointsViewInstance.transform.position=_bossView.PointsViewTarget.transform.position;// Evaluate damageif(attackTheBossResults.DamageAmount>0){// Wait - DamageyieldreturnnewWaitForSeconds(_configuration.Delay4BeforeTakeDamage);SoundManager.Instance.PlayAudioClip(SoundConstants.TakeDamage01);BBBHelper.RenderersDoColorFlicker(_bossView.Renderers,Color.red,0.1f);_bossView.TakeDamage();// Wait - PointsyieldreturnnewWaitForSeconds(_configuration.Delay5BeforePointsView);SoundManager.Instance.PlayAudioClip(SoundConstants.Coin01);_pointsViewInstance.Points=-attackTheBossResults.DamageAmount;}else{// Wait - PointsyieldreturnnewWaitForSeconds(_configuration.Delay5BeforePointsView);SoundManager.Instance.PlayAudioClip(SoundConstants.Coin01);_pointsViewInstance.Text=BBBHelper.GetAttackMissedText();}if(attackTheBossResults.BossHealthRemaining<=0){_bossView.Die();SoundManager.Instance.PlayAudioClip(SoundConstants.GameOverWin);}else{_gameUI.AttackButton.interactable=true;}_gameUI.BossHealthBarView.Health=attackTheBossResults.BossHealthRemaining;}// Event Handlers -------------------------------publicvoidAttackButton_OnClicked(){StartCoroutine(Attack());}}}
Alternative API
Beamable Microservices supports a Promise-based workflow. The availability of the result of the Promise may be handled using .Then() within synchronous scopes as shown above or handled with .IsCompleted within Coroutines as shown here.
Create the Microservice and the project-specific C# code to meet the game's needs.
Name
Detail
1. Open the "Microservices Manager" Window
• Unity → Window → Beamable → Open Microservices Manager
2. Create a new Microservice
• Unity → Window → Beamable → Create New Microservice
• Populate all form fields
3. Implement the Microservice method
See the BBBGameMicroservice.cs code snippet below the table
4. Build the Microservice
• See Beamable Microservice Manager Window
5. Run the Microservice
• See Beamable Microservice Manager Window
6. Play the Scene
• Unity → Edit → Play
Note: Verify that the code properly functions. This varies depending on the specifics of the game logic
7. Stop the Scene
• Unity → Edit → Stop
API
Read these method diagrams along with the following code snippet for a full understanding of the client-server data exchange.
Call Microservice Method #1
The Boss data is passed along via ContentRef to set the initial BossHealth Stat value (e.g. 100). The heroWeaponIndexMax (e.g. 2) is passed and used as a random value is rolled (e.g. 1) for which weapon the Hero will use for the duration of the battle. This is stored in the HeroWeaponIndex Stat for subsequent use.
Note: The use of randomization for the HeroWeaponIndex is a simplified solution fit for this sample project. However, its likely a production game would feature deeper game play and allow the player to select the Hero's weapon, instead of using a random.
Call Microservice Method #2
A list of Weapon data is passed along via ContentRef. The Microservice uses only one index in the list (via HeroWeaponIndex Stat), calculates the damage done to the Boss and returns data to the client used for rendering of animations and UI text.
Code
Here is the code for the steps above.
Beamable auto-generates this original version of the BBBGameMicroservice as the starting point.
1 2 3 4 5 6 7 8 91011121314
usingBeamable.Server;namespaceBeamable.Server.BBBGameMicroservice{[Microservice("BBBGameMicroservice")]publicclassBBBGameMicroservice:Microservice{[ClientCallable]publicvoidServerCall(){// This code executes on the server.}}}
The game maker updates the code to meet the needs of the game project.
namespaceBeamable.Server.BBBGameMicroservice{[Microservice("BBBGameMicroservice")]publicclassBBBGameMicroservice:Microservice{[ClientCallable]publicasyncTask<StartTheBattleResults>StartTheBattle(BossContentRefbossContentRef,intheroWeaponIndexMax){// Find the Boss data from the Boss contentvarboss=awaitbossContentRef.Resolve();// Set boss health to 100awaitBBBHelper.SetProtectedPlayerStat(Services,Context.UserId,BBBConstants.StatKeyBossHealth,boss.MaxHealth.ToString());// Set hero weapon index to random (0,1)intheroWeaponIndex=newSystem.Random().Next(heroWeaponIndexMax);awaitBBBHelper.SetProtectedPlayerStat(Services,Context.UserId,BBBConstants.StatKeyHeroWeaponIndex,heroWeaponIndex.ToString());returnnewStartTheBattleResults{BossHealthRemaining=boss.MaxHealth,HeroWeaponIndex=heroWeaponIndex};}[ClientCallable]publicasyncTask<AttackTheBossResults>AttackTheBoss(List<WeaponContentRef>weaponContentRefs){// Get the weapon indexstringheroWeaponIndexString=awaitBBBHelper.GetProtectedPlayerStat(Services,Context.UserId,BBBConstants.StatKeyHeroWeaponIndex);// Get the weaponintheroWeaponIndex=int.Parse(heroWeaponIndexString);// Find the weapon data from the Weapon contentvarweapon=awaitweaponContentRefs[heroWeaponIndex].Resolve();// Calculate the damageRandomrandom=newRandom();intdamageAmount=0;doublehitRandom=random.NextDouble();//Console.WriteLine($"weaponData.hitChance={weapon.HitChance}.");//Console.WriteLine($"hitRandom={hitRandom}.");if(hitRandom<=weapon.HitChance){damageAmount=random.Next(weapon.MinDamage,weapon.MaxDamage);}// Get the boss healthstringbossHealthString=awaitBBBHelper.GetProtectedPlayerStat(Services,Context.UserId,BBBConstants.StatKeyBossHealth);// Apply the damageintbossHealthRemaining=int.Parse(bossHealthString)-damageAmount;awaitBBBHelper.SetProtectedPlayerStat(Services,Context.UserId,BBBConstants.StatKeyBossHealth,bossHealthRemaining.ToString());returnnewAttackTheBossResults{DamageAmount=damageAmount,BossHealthRemaining=bossHealthRemaining};}}}