9.0 KiB
title | date | weight |
---|---|---|
Tracking the Score | 2019-06-04T16:49:50-07:00 | 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:
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:
AddComponent(entity, new ScoreComponent(0));
Now let's create a collision response component.
In PongFE/Components/IncreaseScoreAfterDestroyComponent.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:
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:
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:
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:
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:
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.
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.
AddComponent(ball, new IncreaseScoreAfterDestroyComponent());
And remember to add our new ScoreEngine to the WorldBuilder in PongFEGame.cs.
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:
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:
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(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.
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.