Input System

Let’s make use of player input in the Player Input system.

In this section, we’ll:

  • Learn a bit about ECS design
  • Add code to the empty Player Input system

System Design

The Murder engine makes use of the Bang framework, which revolves around Entities, Components, and Systems.

  • Entity - An object that has a function in your game.
  • Component - An object that contains data and can be added to an Entity
  • System - A set of code that can act on the data stores within multiple Components and multiple Entities.

ECS simplifies code management by encouraging the creation of small, discrete components. For more about the Bang framework’s philosophy on ECS, visit the Bang wiki.

Hello Murder leaves the Player Input system empty as a foundation to add our own custom functionality. We’ll use it to poll FNA’s Input system to see if any movement from the player is detected. If movement is detected, it will add an Agent Impulse component to the entity.

In case you’re wondering what happens with AgentImpulse:

  • The Agent Mover system will then replace the new Agent Impulse component with a Velocity component.
  • Then the Fast Physics system will use the Velocity component to move the Entity around.

In Bang, Systems use a Filter to determine which entities need to be acted upon. The Player Input system filters out all entities except those that contain both a PlayerComponent and an AgentComponent.

The system implements the IFixedUpdateSystem interface, so Murder will execute its FixedUpdate method sixty times a second.

The system implements the IUpdateSystem, so Murder will execute its Update method as often as it can.

Physics systems operate during the FixedUpdate cycle, so we will want to carry out any changes to the player’s Entity at that time. But we’ll want to make sure we update the player input as often as we can – so we will need to store the player input into a private field during the Update cycle.

Adding a Vector2 field

  1. Open src/HelloMurder/Systems/Player/PlayerInputSystem.cs and add a private Vector2 field:

    [Filter(kind: ContextAccessorKind.Read, typeof(PlayerComponent), typeof(AgentComponent))]
    public class PlayerInputSystem : IUpdateSystem, IFixedUpdateSystem
    {
    
        private Vector2 _cachedInputAxis = Vector2.Zero;
    
        /// <summary>
        ///     Called every fixed update.
        ///     We can apply input values to fixed updating components such as physics components.
        ///     For example the <see cref="AgentComponent"/>
        /// </summary>
        /// <param name="context"></param>
    
  2. If you’re using an IDE, it might be a bit confused here because there’s two possible matches for “Vector2” – System.Numerics, and Microsoft.Xna.Framework. We want to use System.Numerics, so add that to the “usings” section.

    using Bang.Contexts;
    using Bang.Entities;
    using Bang.Systems;
    using Murder.Components;
    using Murder;
    using HelloMurder.Components;
    using System.Numerics;
    
    namespace HelloMurder.Systems
    

Caching input

  1. Back in HelloMurderGame, we registered our movement axes into InputAxis.Movement. We’ll read their current state by using Game.Input.GetAxis and store that state in our private Vector2 field.

    First, find the Update method. Add this line of code:

    public void Update(Context context)
    {
        // Read from Game.Input
        _cachedInputAxis = Game.Input.GetAxis(InputAxis.Movement).Value;
    }
    
  2. The Game.Input.GetAxis method was referred to in HelloMurder.Core.Input, so let’s add a reference to that in the using section.

    using Murder;
    using HelloMurder.Components;
    using HelloMurder.Core.Input;
    using System.Numerics;
    
    namespace HelloMurder.Systems
    

Applying input

  1. First, we’ll need to check and see if the Vector2 field has a value. If it doesn’t, we don’t need to do anything else.

    Then, we need to create a Direction from the axis.

    We’ll then add a new AgentImpulse component using this Direction. We’ll do this by using one of the Bang ECS’s “syntactic sugar” helpers, SetAgentImpulse.

    Bang ECS automatically generates helper functions, like SetAgentImpulse, for every Component you add to the game. All you have to do is create a public readonly struct that implements the IComponent class.

    We specified that we’re only interested in entities with a Player component and an Agent component, back in the system class’s Filter attribute. These components are just “flags” that are used to limit the scope of the Player Input system, so that it’s not just adding AgentImpulse components to every single entity.

    Add the following code to the FixedUpdate method:

        public void FixedUpdate(Context context)
        {
            foreach (Entity entity in context.Entities)
            {
                // Send entity messages or use entity extensions to update relevant entities
                bool moved = _cachedInputAxis.HasValue();
    
                if (moved)
                {
                    Direction direction = DirectionHelper.FromVector(_cachedInputAxis);
    
                    entity.SetAgentImpulse(_cachedInputAxis, direction);
                }
            }
        }
    
  2. Those two methods came from Murder.Helpers, Murder.Utilities, so let’s add those.

    using Murder.Components;
    using Murder.Helpers;
    using Murder.Utilities;
    using Murder;
    using HelloMurder.Components;
    using HelloMurder.Core.Input;
    using System.Numerics;
    
    namespace HelloMurder.Systems
    
  3. Now, we’re done with code edits! Save the file and move on.

Next steps

We’ve created the Player Input system – next we’ll be plugging it into the game as a Feature.