Keep Score and Update Game UI

Code and workflows to keep score between players in a multiplayer game on a LAN

What you'll develop on this page

Server keeping score and sending the score to clients to update their UI

The server will set and adjust player scores based on bullet collisions. It will also track the highest score.

Github branch link: https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/tree/Score-Keeping

How we'll be keeping score

Scoring

1 point for shooting asteroids (as many as you can get through before disappear)

10 points for shooting a player

How the server will keep score

Server will be keeping score by updating ghosted HighScore ghosts and a single HighestScore ghost. When players join, a new object is created.

HighestScore is pre-spawned in ConvertedScene empty (does it have to be prefab?)

PlayerScores are created when a new player joins.

Recycling HighScore ghosts

NCEs re-use network ids, so we will need to keep track of this.

If a player joins the session and it is re-using a network id, we clear that player score

Server creating scores

We are going to start by setting up our HighestScore in ConvertedSubScene.

  • First, let's create the component we will be referencing and updating to keep track of the server's highest score

  • Create HighestScoreComponent in the Mixed/Components folder

  • Paste the code snippet below into HighestScoreComponent.cs:

  • Navigate to ConvertedSubScene and create an empty GameObject called HighestScore

  • Add a GhostAuthoringComponent (by clicking Add Component button in Inspector when HighestScore is selected in Hierarchy)

    • Name = HighestScore

    • Importance = 500

    • Supported Ghost Mode = Interpolated

    • Optimization Mode = Static

  • Next add HighestScoreComponent

  • When done with those steps, drag HighestScore GameObject from Hierarchy into Scripts and Prefabs

Creating HighScore GameObject and prefab
  • Even though we will not make another HighestScore, NetCode requires that the ghosted GameObject be a prefab

  • Now let's create the PlayerScore ghosts that we will be using to keep track of individual player scores

  • Create PlayerScoreAuthoringComponent in Server/Components

  • Paste the code snippet below into PlayerScoreAuthoringComponent.cs:

Creating PlayerScoreAuthoringComponent
  • Next let's create the component that will be storing the actual player data named PlayerScoreComponent in the Mixed/Components folder

  • Paste the code snippet below into PlayerScoreComponent.cs:

Creating PlayerScoreComponent
  • Create an empty GameObject called PlayerScore in the Hierarchy (doesn't matter which one)

  • Add PlayerScoreComponent to PlayerScore

  • Add GhostAuthoringComponent

    • Name = PlayerScore

    • Importance = 500

    • Supported Ghost Modes = Interpolated

    • Optimization Mode = Static

  • Drag it into Scripts and Prefabs

  • Delete it from the hierarchy

Creating PlayerScore prefab
  • Navigate to ConvertedSubScene and add PlayerScoreAuthoringComponent to the PrefabCollection GameObject

  • Drag the PlayerScore prefab from the Scripts and Prefabs folder into the "Prefab" field in the Player Score Authoring Component in Inspector when PrefabCollection is selected in Hierarchy

Updating PrefabCollection with PlayerScore
  • Now we have our ghosts ready

    • HighestScore is part of the ConvertedSubScene

    • PlayerScore is referenced as part of PrefabCollection

We are going to kick off the process of setting up scores by adding a new RPC to be sent in ClientLoadGameSystem.

We could add this flow to existing flows that are kicked off by ClientLoadGameSystem (like SendServerGameLoadedRpc), but we are going to keep it separate to keep this flow concerned only with score setup.

  • Let's create SendServerPlayerNameRpc in the Mixed/Commands folder

  • Paste the code snippet below into SendServerPlayerNameRpc.cs:

Creating SendServerPlayerNameRpc
  • Now let's update ClientLoadGameSystem to send this RPC as part of the game loading process

  • Paste the code snippet below into ClientLoadGameSystem.cs:

Updating ClientLoadLevelSystem
  • Although there will be only one HighestScore entity, there will be a PlayerScore created for each unique NCE network id value

    • Remember that NetCode recycles network id's once players have disconnected

    • So a player may have its score tracked during gameplay, then leave the game, and then a new player may join with that same network id (i.e. it's been 'recycled')

    • So we have to set up a process that checks our existing PlayerScores network id values to see if they match the new SendServerPlayerNameRpc's NCE Network id

      • If they do match, we will reset that PlayerScore to 0 and update it with the sent RPCs provided name

  • Create SetupScoreSystem in the Server/Systems folder

  • Paste the code snippet below into SetupScoreSystem.cs:

Creating SetupScoreSystem
  • Let's get back to NavigationScene, hit Play, click Host a Game, and then go to DOTS Windows

  • Check out the ClientWorld (by selecting the drop down menu in the top left that currently has "Default World" selected, then choose ClientWorld)

    • Find our PlayerScore and HighestScore components

Checking out Entity Debugger to see PlayerScore and HighestScore appearing on the client
  • Great! We see that PlayerScore and HighestScore were created and that PlayerScore is updated to the players name

Getting the Server to update scores

We are going to use a similar approach for updating scores as the one we used in ChangeMaterialAndDestroySystem when we added DestroyTags to entities bullets that had collisions.

When we have an OnEnter collision we will do analysis of who the bullet owner is and what it collided with. Based on the results we will update the bullet owner's PlayerScore and possibly the HighestScore.

  • Let's create AdjustScoresFromBulletCollisionsSystem in the Server/Systems folder

  • Paste the code snippet below into AdjustScoresFromBulletCollisionsSystem.cs:

Creating AdjustScoresFromBulletCollisionsSystem
  • Okay once saved, let's hit play, host game, shoot around to destroy a couple asteroids, then head back to DOTS Windows to check out ClientWorld again to make sure that our PlayerScore and HighestScore are updating

Shooting around and PlayerScore updating with PlayerName and Current/High score
  • Great! They are updating

  • Now let's hit "p" to self-destruct

  • Checkout the PlayerScore

Self-destructing does not cause the current score to go to 0
  • Uh oh, the CurrentScore didn't go to 0 even though we self-destructed as a player

  • Let's update PlayerDestructionSystem to also reset the players score to 0

  • Paste the code snippet below into PlayerDestructionSystem.cs:

Updating PlayerDestructionSystem
  • Once that's updated, hit Play, host game, shoot around again, destroy some asteroids, hit "p" to self-destruct and checkout our PlayerScoreComponent

Update Game UI With UI Toolkit Data Binding

Now that we have our ghosted PlayerScores and HighestScore our client can access them to update their Game UI.

The server calls the shots so the logic we use will be simple. If your UI values do not equal ghost values, change your UI values to the ghost values.

Let's update GameOverlayUpdater to including querying for the PlayerScores and HighestScore.

  • Paste the code snippet below into GameOverlayUpdater.cs:

Updating GameOverlayUpdater
  • Now hit play, host game, and shoot around at the asteroids and see your score increase

  • Hit "p" to self-destruct and start again and see the score reset to 0

Game UI updating when player shoots asteroids

Github branch link:‌

git clone https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/ git checkout 'Score-Keeping'‌

Last updated