331 lines
9.0 KiB
Markdown
331 lines
9.0 KiB
Markdown
---
|
|
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<CanDestroyComponent>(a))
|
|
{
|
|
if (HasComponent<CanBeDestroyedComponent>(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<ScoreMessage>())
|
|
{
|
|
if (HasComponent<ScoreComponent>(scoreMessage.Entity))
|
|
{
|
|
ref readonly var scoreComponent = ref GetComponent<ScoreComponent>(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<DestroyMessage>())
|
|
{
|
|
if (HasComponent<SpawnBallAfterDestroyComponent>(message.Entity))
|
|
{
|
|
ref readonly var respawnComponent = ref GetComponent<SpawnBallAfterDestroyComponent>(message.Entity);
|
|
|
|
SendMessage(
|
|
new BallSpawnMessage(
|
|
new MoonTools.Structs.Position2D(640, 360),
|
|
300,
|
|
16,
|
|
16
|
|
),
|
|
respawnComponent.Seconds
|
|
);
|
|
}
|
|
|
|
if (HasComponent<IncreaseScoreAfterDestroyComponent>(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<PaddleSpawnMessage>
|
|
{
|
|
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<PlayerInputComponent>())
|
|
{
|
|
ref readonly var playerInputComponent = ref GetComponent<PlayerInputComponent>(playerInputEntity);
|
|
if (HasComponent<PlayerComponent>(playerInputEntity))
|
|
{
|
|
ref readonly var playerComponent = ref GetComponent<PlayerComponent>(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.
|