261 lines
7.1 KiB
Markdown
261 lines
7.1 KiB
Markdown
---
|
|
title: "Title"
|
|
date: 2019-06-09T16:51:13-07:00
|
|
weight: 20
|
|
---
|
|
|
|
It would be nice to have a title screen instead of launching right into gameplay. Let's make that happen.
|
|
|
|
You might be tempted to create a separate World for the title screen or different game modes. While this is possible, I don't really recommend it, because it is difficult to share information between different Worlds by design.
|
|
|
|
We could introduce a concept of game state here.
|
|
|
|
In **Enums.cs**:
|
|
|
|
```cs
|
|
public enum GameState
|
|
{
|
|
Title,
|
|
Game
|
|
}
|
|
```
|
|
|
|
In **PongFE/Components/GameStateComponent.cs**:
|
|
|
|
```cs
|
|
using Encompass;
|
|
using PongFE.Enums;
|
|
|
|
namespace PongFE.Components
|
|
{
|
|
public struct GameStateComponent
|
|
{
|
|
public GameState GameState { get; }
|
|
|
|
public GameStateComponent(GameState gameState)
|
|
{
|
|
GameState = gameState;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
This component will be something we call a _singleton component_. Just one of them exists in the world to conveniently track some kind of global state.
|
|
|
|
In **PongFEGame.cs**:
|
|
|
|
```cs
|
|
var gameStateEntity = WorldBuilder.CreateEntity();
|
|
WorldBuilder.SetComponent(gameStateEntity, new GameStateComponent(GameState.Title));
|
|
```
|
|
|
|
Let's make a new Engine.
|
|
|
|
In **PongFE/Engines/GameStateEngine.cs**:
|
|
|
|
```cs
|
|
using Encompass;
|
|
using Microsoft.Xna.Framework.Input;
|
|
using PongFE.Components;
|
|
using PongFE.Enums;
|
|
using PongFE.Messages;
|
|
|
|
namespace PongFE.Engines
|
|
{
|
|
public class GameStateEngine : Engine
|
|
{
|
|
public override void Update(double dt)
|
|
{
|
|
ref readonly var gameStateEntity = ref ReadEntity<GameStateComponent>();
|
|
ref readonly var gameStateComponent = ref GetComponent<GameStateComponent>(gameStateEntity);
|
|
|
|
if (gameStateComponent.GameState == GameState.Title)
|
|
{
|
|
if (Keyboard.GetState().IsKeyDown(Keys.Enter))
|
|
{
|
|
StartGame();
|
|
SetComponent(gameStateEntity, new GameStateComponent(GameState.Game));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void StartGame()
|
|
{
|
|
ref readonly var playAreaComponent = ref ReadComponent<PlayAreaComponent>();
|
|
var playAreaWidth = playAreaComponent.Width;
|
|
var playAreaHeight = playAreaComponent.Height;
|
|
|
|
SendMessage(
|
|
new PaddleSpawnMessage(
|
|
new MoonTools.Structs.Position2D(20, playAreaHeight / 2 - 40),
|
|
Enums.PlayerIndex.One,
|
|
PaddleControl.Player,
|
|
20,
|
|
80
|
|
)
|
|
);
|
|
|
|
SendMessage(
|
|
new PaddleSpawnMessage(
|
|
new MoonTools.Structs.Position2D(playAreaWidth - 45, playAreaHeight / 2 - 40),
|
|
Enums.PlayerIndex.Two,
|
|
PaddleControl.Computer,
|
|
20,
|
|
80
|
|
)
|
|
);
|
|
|
|
SendMessage(
|
|
new BallSpawnMessage(
|
|
new MoonTools.Structs.Position2D(playAreaWidth / 2, playAreaHeight / 2),
|
|
500,
|
|
16,
|
|
16
|
|
),
|
|
0.5
|
|
);
|
|
|
|
// top boundary
|
|
SendMessage(
|
|
new BoundarySpawnMessage(
|
|
new MoonTools.Structs.Position2D(0, -6),
|
|
playAreaWidth,
|
|
6
|
|
)
|
|
);
|
|
|
|
// bottom boundary
|
|
SendMessage(
|
|
new BoundarySpawnMessage(
|
|
new MoonTools.Structs.Position2D(0, playAreaHeight),
|
|
playAreaWidth,
|
|
6
|
|
)
|
|
);
|
|
|
|
// right boundary
|
|
SendMessage(
|
|
new GoalBoundarySpawnMessage(
|
|
Enums.PlayerIndex.One,
|
|
new MoonTools.Structs.Position2D(playAreaWidth, 0),
|
|
6,
|
|
playAreaHeight
|
|
)
|
|
);
|
|
|
|
// left boundary
|
|
SendMessage(
|
|
new GoalBoundarySpawnMessage(
|
|
Enums.PlayerIndex.Two,
|
|
new MoonTools.Structs.Position2D(-6, 0),
|
|
6,
|
|
playAreaHeight
|
|
)
|
|
);
|
|
}
|
|
```
|
|
|
|
Notice how we took most of the *StartGame* stuff from the *PongFEGame.LoadContent* method. Make sure to take those message sends out of that method.
|
|
|
|
Now we need to draw the title screen. There's many different approaches. Generic UI text rendering elements would probably be a good idea. But I'm lazy, so let's just set up a very basic renderer.
|
|
|
|
In **PongFE/Renderers/TitleRenderer.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 TitleRenderer : Renderer
|
|
{
|
|
private SpriteBatch SpriteBatch { get; }
|
|
private DynamicSpriteFont TitleFont { get; }
|
|
private DynamicSpriteFont InstructionFont { get; }
|
|
|
|
public TitleRenderer(SpriteBatch spriteBatch, DynamicSpriteFont titleFont, DynamicSpriteFont instructionFont)
|
|
{
|
|
SpriteBatch = spriteBatch;
|
|
TitleFont = titleFont;
|
|
InstructionFont = instructionFont;
|
|
}
|
|
|
|
public override void Render(double dt, double alpha)
|
|
{
|
|
ref readonly var gameStateComponent = ref ReadComponent<GameStateComponent>();
|
|
ref readonly var playAreaComponent = ref ReadComponent<PlayAreaComponent>();
|
|
|
|
if (gameStateComponent.GameState == GameState.Title)
|
|
{
|
|
var titleDimensions = TitleFont.MeasureString("PongFE");
|
|
var titlePosition = new Vector2(
|
|
(playAreaComponent.Width - titleDimensions.X) / 2,
|
|
(playAreaComponent.Height - titleDimensions.Y) / 4
|
|
);
|
|
|
|
var instructionDimensions = InstructionFont.MeasureString("Press Enter to begin");
|
|
var instructionPosition = new Vector2(
|
|
(playAreaComponent.Width - instructionDimensions.X) / 2,
|
|
playAreaComponent.Height * 2 / 3
|
|
);
|
|
|
|
SpriteBatch.DrawString(TitleFont, "PongFE", titlePosition, Color.White);
|
|
SpriteBatch.DrawString(InstructionFont, "Press Enter to begin", instructionPosition, Color.White);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
And let's tweak the *CenterLineRenderer.Render* method a bit.
|
|
|
|
```cs
|
|
public override void Render(double dt, double alpha)
|
|
{
|
|
ref readonly var gameStateComponent = ref ReadComponent<GameStateComponent>();
|
|
|
|
if (gameStateComponent.GameState == GameState.Game)
|
|
{
|
|
ref readonly var playAreaComponent = ref ReadComponent<PlayAreaComponent>();
|
|
|
|
DrawDottedLine(playAreaComponent.Width / 2, 0, playAreaComponent.Width / 2, playAreaComponent.Height, 20, 20);
|
|
}
|
|
}
|
|
```
|
|
|
|
Let's set everything up in **PongFEGame**.
|
|
|
|
```cs
|
|
...
|
|
|
|
DynamicSpriteFont InstructionFont { get; set; }
|
|
|
|
...
|
|
|
|
InstructionFont = DynamicSpriteFont.FromTtf(
|
|
File.ReadAllBytes(@"Content/Fonts/SquaredDisplay.ttf"),
|
|
48
|
|
);
|
|
|
|
...
|
|
|
|
WorldBuilder.AddEngine(new GameStateEngine());
|
|
|
|
...
|
|
|
|
WorldBuilder.AddRenderer(new TitleRenderer(SpriteBatch, ScoreFont, InstructionFont));
|
|
|
|
...
|
|
```
|
|
|
|
Let's try it!
|
|
|
|
![pong title](/images/pong_title.png)
|
|
|
|
Nice!
|