encompass-cs-docs/content/pong/move_paddle/input_handling.md

142 lines
4.7 KiB
Markdown

---
title: "Input Handling"
date: 2019-05-23T13:38:42-07:00
weight: 10
---
In Pong, the paddles move when the player moves the joystick on their controller up or down.
We currently have a MotionEngine that reads MotionMessages and moves the PositionComponents they reference.
So... it makes sense that we could have an InputEngine that sends MotionMessages, yeah?
Create a file: **PongFE/Engines/InputEngine.cs**
```cs
using Encompass;
using Microsoft.Xna.Framework.Input;
using PongFE.Messages;
namespace PongFE.Engines
{
public class InputEngine : Engine
{
public override void Update(double dt)
{
var keyboardState = Keyboard.GetState();
if (keyboardState.IsKeyDown(Keys.Down))
{
SendMessage(new MotionMessage(
}
}
}
}
```
*record scratch*
Uh oh. **SendMessage** emits a message, as the name suggests, and we can use it to send out a MotionMessage. But it needs a reference to our paddle entity.
At this point, you might be tempted to attach our paddle entity as a property on InputEngine. This would be a *terrible mistake*. We *absolutely never* want to have our game state directly attached to the state of an Engine.
What we want instead is to have a component that the InputEngine can use to look up the appropriate entity. Essentially, we use a Component to designate that an Entity is a certain kind of object that we want to be able to reference.
Create a file: **PongFE/Components/PlayerInputComponent.cs**
```cs
using Encompass;
namespace PongFE.Components
{
public enum PlayerIndex
{
One,
Two
}
public struct PlayerInputComponent : IComponent
{
public PlayerIndex PlayerIndex { get; }
public PlayerInputComponent(PlayerIndex playerIndex)
{
PlayerIndex = playerIndex;
}
}
}
```
Why an *enum* instead of just an integer or something? When we write programs it is very easy to shoot ourselves in the foot. What if someone accidentally typed -1 in as a value or something? Enums structure our data to make it harder for us to make silly mistakes like this.
Let's add this component to our paddle entity.
In **PongFEGame.cs**:
```cs
...
var paddle = WorldBuilder.CreateEntity();
WorldBuilder.SetComponent(paddle, new PlayerInputComponent(PongFE.Components.PlayerIndex.One));
WorldBuilder.SetComponent(paddle, new PositionComponent(new MoonTools.Structs.Position2D(5, 5)));
WorldBuilder.SetComponent(paddle, new Texture2DComponent(PaddleTexture, 0));
...
```
Now we can go back to our InputEngine.
```cs
using Encompass;
using Microsoft.Xna.Framework.Input;
using PongFE.Components;
using PongFE.Messages;
namespace PongFE.Engines
{
[Reads(typeof(PlayerInputComponent))]
[Sends(typeof(MotionMessage))]
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 (playerInputComponent.PlayerIndex == PlayerIndex.One)
{
if (keyboardState.IsKeyDown(Keys.Down))
{
SendMessage(new MotionMessage(playerInputEntity, new System.Numerics.Vector2(0, 10)));
}
}
}
}
}
}
```
Engines have total freedom to read anything in the game state that they desire. This gives Engines a lot of flexibility to do what they need to do. In this case, **ReadEntities<PlayerInputComponent>** lets us get a reference to each entity that has a PlayerInputComponent attached to it. From there, we can get the specific PlayerInputComponent for that Entity, check which PlayerIndex it contains, and then send a message to its entity if the Down key is pressed.
Also, remember when we had to declare **Reads** and **Receives** and **Writes** on our MotionEngine? Well, similarly, we have to declare **Sends** when our engine emits a certain kind of Message. Otherwise Encompass will get mad at us and crash the game for our own safety.
Let's add our PlayerInputEngine to the WorldBuilder.
In **PongFEGame.cs**:
```cs
...
WorldBuilder.AddEngine(new InputEngine());
WorldBuilder.AddEngine(new MotionEngine());
WorldBuilder.AddOrderedRenderer(new Texture2DRenderer(SpriteBatch));
...
```
It doesn't matter which order they go in, because remember, Encompass figures it out automatically. I just prefer this order for some reason. Once we have a lot of Engines it stops mattering pretty quickly anyway.