--- title: "Tracking the Score" date: 2019-06-04T16:49:50-07:00 weight: 30 --- Finally, we need to track the score and update it appropriately. How do we increase the score in Pong? When a ball collides with a goal, it increases the score of a player. That sounds like a collision response to me. We will obviously need a ScoreComponent. In **PongFE/Component/ScoreComponent.cs**: ```cs using Encompass; namespace PongFE.Components { public struct ScoreComponent : IComponent { public int Score { get; } public ScoreComponent(int score) { Score = score; } } } ``` Now the question is: what do we attach this to? Well, the ball increases the score when it touches a goal boundary, right? So why not just put this component right on the goal boundary? In **GoalBoundarySpawner.cs**: ```cs AddComponent(entity, new ScoreComponent(0)); ``` Now let's create a collision response component. In **PongFE/Components/IncreaseScoreAfterDestroyComponent.cs**: ```cs using Encompass; namespace PongFE.Components { public struct IncreaseScoreAfterDestroyComponent : IComponent { } } ``` Let's take a step back for a second. **DestroyEngine** only knows the entity being destroyed. But it might be useful for it to also know the entity that is doing the destroying. Let's revise **DestroyMessage.cs**: ```cs using Encompass; namespace PongFE.Messages { public struct DestroyMessage : IMessage { public Entity Entity { get; } public Entity DestroyedBy { get; } public DestroyMessage(Entity entity, Entity destroyedBy) { Entity = entity; DestroyedBy = destroyedBy; } } } ``` And in **CollisionEngine.cs**: ```cs private void CheckDestroy(Entity a, Entity b) { if (HasComponent(a)) { if (HasComponent(b)) { SendMessage(new DestroyMessage(b, a)); } } } ``` Let's create a mechanism for updating the score. In **PongFE/Messages/ScoreMessage.cs**: ```cs using Encompass; namespace PongFE.Messages { public struct ScoreMessage : IMessage { public Entity Entity { get; } public ScoreMessage(Entity entity) { Entity = entity; } } } ``` And in **PongFE/Engines/ScoreEngine.cs**: ```cs using Encompass; using PongFE.Components; using PongFE.Messages; namespace PongFE.Engines { [Reads(typeof(ScoreComponent))] [Receives(typeof(ScoreMessage))] [Writes(typeof(ScoreComponent))] public class ScoreEngine : Engine { public override void Update(double dt) { foreach (ref readonly var scoreMessage in ReadMessages()) { if (HasComponent(scoreMessage.Entity)) { ref readonly var scoreComponent = ref GetComponent(scoreMessage.Entity); SetComponent(scoreMessage.Entity, new ScoreComponent(scoreComponent.Score + 1)); } } } } } ``` If you want to make sure this is working, you can add a debug output line directly below SetComponent: ```cs System.Console.WriteLine("score: {0}", scoreComponent.Score + 1); ``` Now the console should print every time the score increases. Now we can send a ScoreMessage from **DestroyEngine**. ```cs using Encompass; using PongFE.Components; using PongFE.Messages; namespace PongFE.Engines { [Reads( typeof(SpawnBallAfterDestroyComponent), typeof(IncreaseScoreAfterDestroyComponent) )] [Receives(typeof(DestroyMessage))] [Sends( typeof(BallSpawnMessage), typeof(ScoreMessage) )] public class DestroyEngine : Engine { public override void Update(double dt) { foreach (ref readonly var message in ReadMessages()) { if (HasComponent(message.Entity)) { ref readonly var respawnComponent = ref GetComponent(message.Entity); SendMessage( new BallSpawnMessage( new MoonTools.Structs.Position2D(640, 360), 300, 16, 16 ), respawnComponent.Seconds ); } if (HasComponent(message.Entity)) { SendMessage(new ScoreMessage(message.DestroyedBy)); } Destroy(message.Entity); } } } } ``` Let's make sure to add our response to **BoundaryGoalSpawner.cs**. ```cs AddComponent(ball, new IncreaseScoreAfterDestroyComponent()); ``` And remember to add our new ScoreEngine to the WorldBuilder in **PongFEGame.cs**. ```ts WorldBuilder.AddEngine(new ScoreEngine()); ``` Before we move on, I would like us to make a slight adjustment to how the player index is tracked on our entities. Right now, we pass a PlayerIndex to either PlayerInputComponent or ComputerControlComponent. Since this information is useful across multiple entities and components, I think it would be best for this data to have its own component. In **PongFE/Components/PlayerComponent.cs**: ```cs using Encompass; using PongFE.Enums; namespace PongFE.Components { public struct PlayerComponent : IComponent { public PlayerIndex PlayerIndex { get; } public PlayerComponent(PlayerIndex playerIndex) { PlayerIndex = playerIndex; } } } ``` Now our **PaddleSpawner** can look like this: ```cs using Encompass; using Microsoft.Xna.Framework.Graphics; using PongFE.Components; using PongFE.Enums; using PongFE.Messages; namespace PongFE.Spawners { public class PaddleSpawner : Spawner { private Texture2D WhitePixel { get; } public PaddleSpawner(Texture2D whitePixel) { WhitePixel = whitePixel; } protected override void Spawn(in PaddleSpawnMessage message) { var paddle = CreateEntity(); if (message.PaddleControl == PaddleControl.Player) { AddComponent(paddle, new PlayerInputComponent()); } else { AddComponent(paddle, new ComputerControlComponent()); } AddComponent(paddle, new PlayerComponent(message.PlayerIndex)); AddComponent(paddle, new PaddleMoveSpeedComponent(400)); AddComponent(paddle, new PositionComponent(message.Position)); AddComponent(paddle, new CollisionComponent(new MoonTools.Bonk.Rectangle(0, 0, message.Width, message.Height))); AddComponent(paddle, new CanCauseBounceComponent()); AddComponent(paddle, new Texture2DComponent(WhitePixel, 0, new System.Numerics.Vector2(message.Width, message.Height))); } } } ``` Now we make an adjustment to **InputEngine** as well. ```cs using Encompass; using Microsoft.Xna.Framework.Input; using PongFE.Components; using PongFE.Enums; using PongFE.Messages; namespace PongFE.Engines { [Reads(typeof(PlayerInputComponent), typeof(PlayerComponent))] [Sends(typeof(PaddleMoveMessage))] public class InputEngine : Engine { public override void Update(double dt) { var keyboardState = Keyboard.GetState(); foreach (ref readonly var playerInputEntity in ReadEntities()) { ref readonly var playerInputComponent = ref GetComponent(playerInputEntity); if (HasComponent(playerInputEntity)) { ref readonly var playerComponent = ref GetComponent(playerInputEntity); if (playerComponent.PlayerIndex == PlayerIndex.One) { if (keyboardState.IsKeyDown(Keys.Down)) { SendMessage( new PaddleMoveMessage( playerInputEntity, PaddleMoveDirection.Down ) ); } else if (keyboardState.IsKeyDown(Keys.Up)) { SendMessage( new PaddleMoveMessage( playerInputEntity, PaddleMoveDirection.Up ) ); } } } } } } } ``` Alright, now it would be nice to actually see the score being drawn on the screen.