226 lines
6.9 KiB
Markdown
226 lines
6.9 KiB
Markdown
---
|
|
title: "Drawing the Score"
|
|
date: 2019-06-04T17:21:52-07:00
|
|
weight: 40
|
|
---
|
|
|
|
Remember Renderers? Haven't thought about those in a while!
|
|
|
|
All we need to draw new elements to the screen are Renderers. Since displaying the score is a UI element, I think it would be best to do this with a GeneralRenderer.
|
|
|
|
But first, we're gonna need a font. I liked [this font](https://www.dafont.com/squared-display.font). But you can pick any font you like. It's your world and you can do whatever you like in it.
|
|
|
|
Place the font of your heart's desire into the directory **PongFE/Content/Fonts**.
|
|
|
|
Now we need to get our font in the game. There are a few different font rendering tools available. SpriteFontPlus has worked pretty well for me so far.
|
|
|
|
In your terminal, do the following commands:
|
|
|
|
```sh
|
|
git submodule add https://github.com/rds1983/SpriteFontPlus.git
|
|
git submodule update --init --recursive
|
|
```
|
|
|
|
In **PongFE.Framework.csproj**:
|
|
|
|
```xml
|
|
<ItemGroup>
|
|
<ProjectReference Include="..\FNA\FNA.csproj"/>
|
|
<ProjectReference Include="..\encompass-cs\encompass-cs\encompass-cs.csproj"/>
|
|
<ProjectReference Include="..\SpriteFontPlus\src\SpriteFontPlus.FNA.csproj" />
|
|
</ItemGroup>
|
|
```
|
|
|
|
And in **PongFE.Core.csproj**:
|
|
|
|
```xml
|
|
<ItemGroup>
|
|
<ProjectReference Include="..\FNA\FNA.Core.csproj"/>
|
|
<ProjectReference Include="..\encompass-cs\encompass-cs\encompass-cs.csproj" />
|
|
<ProjectReference Include="..\SpriteFontPlus\src\SpriteFontPlus.FNA.Core.csproj" />
|
|
</ItemGroup>
|
|
```
|
|
|
|
Now we can use SpriteFontPlus.
|
|
|
|
First, we need to be able to track which player's score is which. Why don't we add our new PlayerComponent to GoalBoundarySpawner?
|
|
|
|
In **GoalBoundarySpawnMessage.cs**:
|
|
|
|
```cs
|
|
using Encompass;
|
|
using MoonTools.Structs;
|
|
using PongFE.Enums;
|
|
|
|
namespace PongFE.Messages
|
|
{
|
|
public struct GoalBoundarySpawnMessage : IMessage
|
|
{
|
|
public PlayerIndex PlayerIndex { get; }
|
|
public Position2D Position { get; }
|
|
public int Width { get; }
|
|
public int Height { get; }
|
|
|
|
public GoalBoundarySpawnMessage(PlayerIndex playerIndex, Position2D position, int width, int height)
|
|
{
|
|
PlayerIndex = playerIndex;
|
|
Position = position;
|
|
Width = width;
|
|
Height = height;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**GoalBoundarySpawner.cs**:
|
|
|
|
```cs
|
|
using Encompass;
|
|
using PongFE.Components;
|
|
using PongFE.Messages;
|
|
|
|
namespace PongFE.Spawners
|
|
{
|
|
public class GoalBoundarySpawner : Spawner<GoalBoundarySpawnMessage>
|
|
{
|
|
protected override void Spawn(in GoalBoundarySpawnMessage message)
|
|
{
|
|
var entity = CreateEntity();
|
|
|
|
AddComponent(entity, new PositionComponent(message.Position));
|
|
AddComponent(entity, new CollisionComponent(new MoonTools.Bonk.Rectangle(0, 0, message.Width, message.Height)));
|
|
AddComponent(entity, new CanDestroyComponent());
|
|
AddComponent(entity, new ScoreComponent(0));
|
|
AddComponent(entity, new PlayerComponent(message.PlayerIndex));
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Now we can adjust the spawn messages in **PongFEGame.cs**:
|
|
|
|
```cs
|
|
// right boundary
|
|
WorldBuilder.SendMessage(
|
|
new GoalBoundarySpawnMessage(
|
|
Enums.PlayerIndex.One,
|
|
new MoonTools.Structs.Position2D(1280, 0),
|
|
6,
|
|
720
|
|
)
|
|
);
|
|
|
|
// left boundary
|
|
WorldBuilder.SendMessage(
|
|
new GoalBoundarySpawnMessage(
|
|
Enums.PlayerIndex.Two,
|
|
new MoonTools.Structs.Position2D(-6, 0),
|
|
6,
|
|
720
|
|
)
|
|
);
|
|
```
|
|
|
|
Scoring on the right goal increases the score of player one, and scoring on the left goal increases the score of player two.
|
|
|
|
Now let's write our ScoreRenderer.
|
|
|
|
In **PongFE/Renderers/ScoreRenderer.cs**:
|
|
|
|
```cs
|
|
using Encompass;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using PongFE.Components;
|
|
using PongFE.Enums;
|
|
using SpriteFontPlus;
|
|
|
|
namespace PongFE.Renderers
|
|
{
|
|
public class ScoreRenderer : GeneralRenderer
|
|
{
|
|
public SpriteBatch SpriteBatch { get; }
|
|
public DynamicSpriteFont Font { get; }
|
|
public int SpacingFromCenter { get; } = 240;
|
|
public int SpacingFromTop { get; } = 20;
|
|
|
|
public ScoreRenderer(SpriteBatch spriteBatch, DynamicSpriteFont font)
|
|
{
|
|
SpriteBatch = spriteBatch;
|
|
Font = font;
|
|
}
|
|
|
|
public override void Render()
|
|
{
|
|
int? playerOneScore = null;
|
|
int? playerTwoScore = null;
|
|
|
|
foreach (ref readonly var entity in ReadEntities<ScoreComponent>())
|
|
{
|
|
ref readonly var scoreComponent = ref GetComponent<ScoreComponent>(entity);
|
|
ref readonly var playerComponent = ref GetComponent<PlayerComponent>(entity);
|
|
|
|
if (playerComponent.PlayerIndex == Enums.PlayerIndex.One)
|
|
{
|
|
playerOneScore = scoreComponent.Score;
|
|
}
|
|
else if (playerComponent.PlayerIndex == Enums.PlayerIndex.Two)
|
|
{
|
|
playerTwoScore = scoreComponent.Score;
|
|
}
|
|
}
|
|
|
|
if (playerOneScore.HasValue)
|
|
{
|
|
SpriteBatch.DrawString(
|
|
Font,
|
|
playerOneScore.Value.ToString(),
|
|
new Vector2(640 - SpacingFromCenter, SpacingFromTop),
|
|
Color.White
|
|
);
|
|
}
|
|
|
|
if (playerTwoScore.HasValue)
|
|
{
|
|
SpriteBatch.DrawString(
|
|
Font,
|
|
playerTwoScore.Value.ToString(),
|
|
new Vector2(640 + SpacingFromCenter - (Font.Size / 2), SpacingFromTop),
|
|
Color.White
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
What's the question mark on those variables? This creates a *Nullable* value. That means the variable can either contain an int, or be _null_. We don't necessarily have a guarantee that the score values will exist in the world, so we provide a way for the renderer to fail gracefully if the search for the score value fails.
|
|
|
|
Basically, we find each entity with a score component and figure out which player the score belongs to. Then we draw the score component's value to the screen as a string.
|
|
|
|
Let's add our ScoreRenderer to the WorldBuilder. First we need to set up the font.
|
|
|
|
```cs
|
|
...
|
|
|
|
DynamicSpriteFont ScoreFont { get; set; }
|
|
|
|
...
|
|
|
|
protected override void LoadContent()
|
|
{
|
|
...
|
|
|
|
ScoreFont = DynamicSpriteFont.FromTtf(File.ReadAllBytes(@"Content/Fonts/SquaredDisplay.ttf"), 128);
|
|
|
|
...
|
|
|
|
WorldBuilder.AddGeneralRenderer(new ScoreRenderer(SpriteBatch, ScoreFont), 0);
|
|
|
|
...
|
|
```
|
|
|
|
Now you should be able to see the game score. This is starting to look and feel like a more complete game now.
|
|
|
|
Hang on a sec though - what's that magic value *640* in our ScoreRenderer? So far we've been assuming throughout our code that our UI and our play area are locked to 1280x720. What if that isn't true?
|