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

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

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:

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:

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

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

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:

Now let's update ClientLoadGameSystem to send this RPC as part of the game loading process
Paste the code snippet below into ClientLoadGameSystem.cs:

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:

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

Great! We see that PlayerScore and HighestScore were created and that PlayerScore is updated to the players name
We have set up scoring
We created HighestScoreComponent
We created HighestScore prefab
We created PlayerScoreComponent
We created PlayerScoreAuthoringComponent
We created PlayerScore prefab
We created SendServerPlayerNameRpc
We updated ClientLoadGameSystem
We created SetupScoreSystem
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:

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

Great! They are updating
Now let's hit "p" to self-destruct
Checkout the PlayerScore

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:

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

We now have our scores updating based on bullet collisions
We created AdjustScoresFromBulletCollisionsSystem
We updated PlayerDestructionSystem
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:

Now hit play, host game, and shoot around at the asteroids and see your score increase
Warning! This might not work as expected if there are thin-clients, since the UI-Code might find the score entity of one of the thin clients instead of the actual player.
Hit "p" to self-destruct and start again and see the score reset to 0

Our game UI updates based on updated ghost values
We updated GameOverlayUpdater
Github branch link:‌
git clone https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/
git checkout 'Score-Keeping'‌
Last updated