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

4.7 KiB

title date weight
Input Handling 2019-05-23T13:38:42-07:00 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

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

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:

    ...

    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.

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 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:

    ...

    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.