More Api Calls
We've set up the additional fields in our backend and Api reference.
Now we shall write a couple of functions to properly update our battle table with the information it needs.
Update CreateBattle with seed
First off, we need to make a change to our CreateBattle()
function our GameData.cs
script.
Since we've added a seed to our battles
table, we need to set it to a random integer when we create a battle.
Let's open the GameData.cs
file in Assets/_Game/Scripts/Data/GameData.cs
In the function public async Task<Battles> CreateBattle()
Replace
createBattle.SetArgs(new{objects = new{shooter_id = 1}});
with
int seeds = Random.Range(0, 100000);createBattle.SetArgs(new{objects = new{shooter_id = 1, seed = seeds}});
The whole functions looks like
public async Task<Battles> CreateBattle(){GraphApi.Query createBattle = shooterApi.GetQueryByName("CreateBattle", GraphApi.Query.Type.Mutation);int seeds = Random.Range(0, 100000);createBattle.SetArgs(new{objects = new{shooter_id = 1, seed = seeds}});UnityWebRequest request = await shooterApi.Post(createBattle);string result = request.downloadHandler.text;return JsonConvert.DeserializeObject<Battles>(RemoveData(result,createBattle.queryString));}
As you can see, a random integer is selected between 0 and 1000000 and the seed
column of our battles
row in our backend is set to that value.
This seed
column is used by both the Shooter
and Defender
to randomly spawn targets at the start of the game. Since they'll both have the same seed, the same targets will be spawned for both of them.
Add new Api Call functions
Now we need a couple of functions to update our battles
row when things happen in the game like the Shooter
missing or hitting a target.
Remember the flow we described in Realtime Gameplay Overview
Copy and past the code below into your GameData.cs
script and I'll explain each function briefly
public async void UpdateHitTargets(bool complete){GraphApi.Query updateBattle = shooterApi.GetQueryByName("UpdateOnlineBattle", GraphApi.Query.Type.Mutation);updateBattle.SetArgs(new{where = new{id = new{_eq = battle.id}},_set = new{timeTaken = time, attackComplete = complete, hitTargets = ListToString(hitTargets)}});await shooterApi.Post(updateBattle);}public async void UpdateDefendedTargets(){GraphApi.Query updateBattle = shooterApi.GetQueryByName("UpdateOnlineBattle", GraphApi.Query.Type.Mutation);updateBattle.SetArgs(new{where = new{id = new{_eq = battle.id}}, _set = new{defendedTargets = ListToString(defendedTargets)}});await shooterApi.Post(updateBattle);}public async Task<string> UpdateDefendedTargets(bool complete){gameManager.hud.SetActive(false);GraphApi.Query updateBattle = shooterApi.GetQueryByName("UpdateOnlineBattle", GraphApi.Query.Type.Mutation);updateBattle.SetArgs(new{where = new{id = new{_eq = battle.id}},_set = new{gameComplete = complete, defendedTargets = ListToString(defendedTargets)}});UnityWebRequest request = await shooterApi.Post(updateBattle);return request.downloadHandler.text;}public async void UpdateMiss(){GraphApi.Query updateBattle = shooterApi.GetQueryByName("UpdateOnlineBattle", GraphApi.Query.Type.Mutation);if (user.role == User.Role.SHOOTER){updateBattle.SetArgs(new{where = new{id = new{_eq = battle.id}}, _set = new{shooter_lastseen = "now()"},_inc = new{shooterMisses = 1}});}else{updateBattle.SetArgs(new{where = new{id = new{_eq = battle.id}}, _set = new{defender_lastseen = "now()"},_inc = new{defenderMisses = 1}});}await shooterApi.Post(updateBattle);}private async void ProcessBattle(){ProcessShots();if (battle.gameComplete){if (gameComplete)return;gameComplete = true;shouldCount = false;Debug.Log("Game complete");shooterApi.CancelSubscription(battleCws, "battle");if ((battle.hitTargets.Count == battle.defendedTargets.Count && user.role == User.Role.DEFENDER) ||(battle.hitTargets.Count > battle.defendedTargets.Count && user.role == User.Role.SHOOTER)){await DeleteBattles();}OnGameComplete complete = new OnGameComplete();complete.FireEvent();return;}if (battle.attackComplete){if (attackComplete)return;attackComplete = true;time = battle.timeTaken + 5;Debug.Log("Starting Fight");OnDefenderTurn defenderTurn = new OnDefenderTurn();defenderTurn.FireEvent();}}void ProcessShots(){Color color;if (user.role == User.Role.SHOOTER){color = Color.green;if (battle.defenderMisses > misses){for (int i = 0; i < battle.defenderMisses - misses; i++){sfxPlayer.PlayOneShot(sounds.zap);Game.Bullet bullet = Instantiate(bulletPrefab, opponentPosition, Quaternion.identity).GetComponent<Game.Bullet>();bullet.Move(color);PlayDelayedSound(sounds.disappointments, 0.2f);}misses = battle.defenderMisses;}if (battle.defendedTargets == null)return;if (battle.defendedTargets.Count > defendedTargets.Count){int lastIndex = battle.defendedTargets.Count - 1;for (int i = 0; i < battle.defendedTargets.Count - defendedTargets.Count; i++){sfxPlayer.PlayOneShot(sounds.zap);PlayDelayedSound(sounds.activation, 0.2f);PlayDelayedSound(sounds.claps, 0.2f);PlayDelayedSound(sounds.activation, 0.1f);int index = battle.defendedTargets[lastIndex - i];Debug.Log("Shoot the targets");Game.Bullet bullet = Instantiate(bulletPrefab, opponentPosition, Quaternion.identity).GetComponent<Game.Bullet>();Target target = gameManager.targetSpawner.targets.Find((target1 => target1.index == index));bullet.Move(target.transform.position, color);target.HighlightTarget(color);}defendedTargets = new List<int>(battle.defendedTargets);}}else{color = Color.red;if (battle.shooterMisses > misses){for (int i = 0; i < battle.shooterMisses - misses; i++){sfxPlayer.PlayOneShot(sounds.zap);PlayDelayedSound(sounds.disappointments, 0.2f);Game.Bullet bullet = Instantiate(bulletPrefab, opponentPosition, Quaternion.identity).GetComponent<Game.Bullet>();bullet.Move(color);}misses = battle.shooterMisses;}if (battle.hitTargets == null)return;if (battle.hitTargets.Count > hitTargets.Count){int lastIndex = battle.hitTargets.Count - 1;for (int i = 0; i < battle.hitTargets.Count - hitTargets.Count; i++){sfxPlayer.PlayOneShot(sounds.zap);PlayDelayedSound(sounds.activation, 0.2f);PlayDelayedSound(sounds.claps, 0.2f);int index = battle.hitTargets[lastIndex - i];Debug.Log("Shoot the targets");Game.Bullet bullet = Instantiate(bulletPrefab, opponentPosition, Quaternion.identity).GetComponent<Game.Bullet>();Target target = gameManager.targetSpawner.targets.Find((target1 => target1.index == index));bullet.Move(target.transform.position, color);target.HighlightTarget(color);}hitTargets = new List<int>(battle.hitTargets);}}}
I know that seems like a lot of code but a lot of it is just to do game stuff.
Let's break them down.
void UpdateHitTargets
This function updates the hitTargets
column of our battle in the backend. It takes in a bool complete
to signify if the Shooter
's turn is complete.
If bool complete
is true, it also sets the attackComplete
column of our battle to true. Else, it sets it as fault.
We shall call this function every time the Shooter
hits a target.
UpdateDefendedTargets
This function updates the defendedTargets
column of our battle in the backend. It takes in a bool complete
to signify if the Defender
's turn is complete hereby signifying the end of the game.
If bool complete
is true, it also sets the gameComplete
column of our battle to true.
We shall call this function every time the Defender
deactivates a target.
void UpdateMiss
This function is used to increase the value in column shooterMisses
or defenderMisses
when either the Shooter
or Defender
misses a target.
void ProcessBattle
This function does a couple of things depending on the data we get from our subscription. It shall be called every time we get data from our subscription.
If you recall, every time a change is made to our battle, data is sent to our subscription containing the new values of the battle.
This data is automatically deserialized into our battle
object in the function ReceiveBattleData()
.
The ProcessBattle()
function checks the state of our battle
object and does a couple of things.
- if
battle.attackComplete
istrue
, it means theShooter
's turn is over and it fires theOnDefenderTurn()
event which triggers theDefender
's turn. It also sets theDefender
's time tobattle.timeTaken
+ 5. - if
battle.gameComplete
istrue
, it means theDefender
's turn is over and the game is over. It deletes the battle, unsubscribes from the subscription and fires theOnGameComplete()
event which triggers the game over screen. - it calls the
ProcessShots()
function which shall be explained below.
void ProcessShots
This is the function that simulates the opponent's turn. Depending on the value of shooterMisses
, defenderMisses
, hitTargets
and defendedTargets
, it simulates shots fired by the opponent when it's their turn.
Add ProcessBattle to ReceiveBattleData
Go to the ReceiveBattleData()
function and add ProcessBattle()
at the start.
It looks like this afterwards
private void ReceiveBattleData(OnSubscriptionDataReceived subscriptionDataReceived){Debug.Log(subscriptionDataReceived.data);battle = JsonConvert.DeserializeObject<Battles>(RemoveSubscriptionData(subscriptionDataReceived.data,shooterApi.GetQueryByName("SubscribeToBattle", GraphApi.Query.Type.Subscription).queryString));if (battle == null){shooterApi.CancelSubscription(battleCws, "battle");return;}if (!battle.shooterReady && battle.defenderReady){string opponentName =user.role == User.Role.SHOOTER ? battle.defender.username : battle.shooter.username;menuManager.SetWaitingText($"Found an opponent. {opponentName}");waitingTime = 10;if (user.role == User.Role.SHOOTER)UpdateShooterReady();}if (battle.shooterReady && battle.defenderReady && SceneManager.GetActiveScene().buildIndex == 0){string opponentName =user.role == User.Role.SHOOTER ? battle.defender.username : battle.shooter.username;menuManager.SetWaitingText($"Entering battle against. {opponentName}");if (!inBattle){inBattle = true;}menuManager.BattleAnimation();}ProcessBattle();}
And there we go! We've created functions that'll help us update our battle as the player plays.
We've also created a function to properly update the game depending on what data we get from our subscription.
We just need to call these functions at the right times and that's it!
- Build apps and APIs 10x faster
- Built-in authorization and caching
- 8x more performant than hand-rolled APIs