encompass-cs-docs/content/pong/polish/title.md

261 lines
7.1 KiB
Markdown
Raw Normal View History

2019-06-10 01:59:57 +00:00
---
title: "Title"
date: 2019-06-09T16:51:13-07:00
weight: 20
---
2020-07-19 22:22:18 +00:00
It would be nice to have a title screen instead of launching right into gameplay. Let's make that happen.
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
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.
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
We could introduce a concept of game state here.
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
In **Enums.cs**:
```cs
public enum GameState
{
Title,
Game
2019-06-10 01:59:57 +00:00
}
```
2020-07-19 22:22:18 +00:00
In **PongFE/Components/GameStateComponent.cs**:
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
```cs
using Encompass;
using PongFE.Enums;
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
namespace PongFE.Components
{
2021-05-01 19:07:59 +00:00
public struct GameStateComponent
2020-07-19 22:22:18 +00:00
{
public GameState GameState { get; }
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
public GameStateComponent(GameState gameState)
{
GameState = gameState;
}
}
}
```
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
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.
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
In **PongFEGame.cs**:
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
```cs
var gameStateEntity = WorldBuilder.CreateEntity();
WorldBuilder.SetComponent(gameStateEntity, new GameStateComponent(GameState.Title));
```
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
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));
}
}
}
2019-06-10 01:59:57 +00:00
}
2020-07-19 22:22:18 +00:00
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
)
2019-06-10 01:59:57 +00:00
);
2020-07-19 22:22:18 +00:00
SendMessage(
new PaddleSpawnMessage(
new MoonTools.Structs.Position2D(playAreaWidth - 45, playAreaHeight / 2 - 40),
Enums.PlayerIndex.Two,
PaddleControl.Computer,
20,
80
)
2019-06-10 01:59:57 +00:00
);
2020-07-19 22:22:18 +00:00
SendMessage(
new BallSpawnMessage(
new MoonTools.Structs.Position2D(playAreaWidth / 2, playAreaHeight / 2),
500,
16,
16
),
0.5
);
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
// top boundary
SendMessage(
new BoundarySpawnMessage(
new MoonTools.Structs.Position2D(0, -6),
playAreaWidth,
6
)
);
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
// bottom boundary
SendMessage(
new BoundarySpawnMessage(
new MoonTools.Structs.Position2D(0, playAreaHeight),
playAreaWidth,
6
)
);
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
// right boundary
SendMessage(
new GoalBoundarySpawnMessage(
Enums.PlayerIndex.One,
new MoonTools.Structs.Position2D(playAreaWidth, 0),
6,
playAreaHeight
)
);
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
// left boundary
SendMessage(
new GoalBoundarySpawnMessage(
Enums.PlayerIndex.Two,
new MoonTools.Structs.Position2D(-6, 0),
6,
playAreaHeight
)
);
}
```
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
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.
2021-05-01 19:07:59 +00:00
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.
2020-07-19 22:22:18 +00:00
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
{
2021-05-01 19:07:59 +00:00
public class TitleRenderer : Renderer
2020-07-19 22:22:18 +00:00
{
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;
}
2019-06-10 01:59:57 +00:00
2021-05-01 23:03:20 +00:00
public override void Render(double dt, double alpha)
2020-07-19 22:22:18 +00:00
{
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);
}
2019-06-10 01:59:57 +00:00
}
}
}
```
2020-07-19 22:22:18 +00:00
And let's tweak the *CenterLineRenderer.Render* method a bit.
```cs
2021-05-01 23:03:20 +00:00
public override void Render(double dt, double alpha)
2020-07-19 22:22:18 +00:00
{
ref readonly var gameStateComponent = ref ReadComponent<GameStateComponent>();
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
if (gameStateComponent.GameState == GameState.Game)
{
ref readonly var playAreaComponent = ref ReadComponent<PlayAreaComponent>();
DrawDottedLine(playAreaComponent.Width / 2, 0, playAreaComponent.Width / 2, playAreaComponent.Height, 20, 20);
}
}
```
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
Let's set everything up in **PongFEGame**.
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
```cs
...
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
DynamicSpriteFont InstructionFont { get; set; }
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
...
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
InstructionFont = DynamicSpriteFont.FromTtf(
File.ReadAllBytes(@"Content/Fonts/SquaredDisplay.ttf"),
48
);
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
...
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
WorldBuilder.AddEngine(new GameStateEngine());
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
...
2019-06-10 01:59:57 +00:00
2021-05-01 19:07:59 +00:00
WorldBuilder.AddRenderer(new TitleRenderer(SpriteBatch, ScoreFont, InstructionFont));
2019-06-10 01:59:57 +00:00
2020-07-19 22:22:18 +00:00
...
2019-06-10 01:59:57 +00:00
```
Let's try it!
2020-07-19 22:22:18 +00:00
![pong title](/images/pong_title.png)
2019-06-10 01:59:57 +00:00
Nice!