Blazor GameDev – part 8: keyboard input
Blazor GameDev – part 8: keyboard input

Blazor GameDev – part 8: keyboard input

2020, Aug 09    

Hi everyone! Welcome back to part 8 of our Blazor 2d Gamedev series. Today we’re going to refactor our last example, detecting keyboard input to control character animations.

Last time we saw how to load spritesheets and introduced the AnimatedSpriteRenderComponent component. Let’s now see how we can use the keyboard to switch between animations. The results will look more or less like this:

You can check it out in your browser here. Use left/right arrows to control the player.

So let’s start. The first step, as we did in Example 6, is to update our index.html and add the listeners for keyboard events down/up:

window.game.canvas.onkeydown = (e) => {
    game.instance.invokeMethodAsync('OnKeyDown', e.keyCode);
};
window.game.canvas.onkeyup = (e) => {
    game.instance.invokeMethodAsync('OnKeyUp', e.keyCode);
};

The callbacks will forward the payload to C# methods in our Index.razor:

[JSInvokable]
public async ValueTask OnKeyDown(int keyCode) => InputSystem.Instance.SetKeyState((Keys)keyCode,ButtonState.States.Down);

[JSInvokable]
public async ValueTask OnKeyUp(int keyCode) => InputSystem.Instance.SetKeyState((Keys)keyCode,ButtonState.States.Up);

Then we have to update the InputSystem to handle keyboard input as well, same way we did for mouse:

public class InputSystem
{
    public void SetKeyState(Keys key, ButtonState.States state)
    {
            var oldState = _keyboardStates[key];
            _keyboardStates[key] = new ButtonState(state, oldState.State == ButtonState.States.Down);
    }

    public ButtonState GetKeyState(Keys key) => _keyboardStates[key];
}

We’re almost done. This time we’re going to give our character a “bigger” brain:

public class CharacterBrain : BaseComponent
{
    private readonly Transform _transform;
    private readonly AnimatedSpriteRenderComponent _animationComponent;
    private readonly AnimationsSet _animationsSet;

    public override async ValueTask Update(GameContext game)
    {
            var right = InputSystem.Instance.GetKeyState(Keys.Right);
            var left = InputSystem.Instance.GetKeyState(Keys.Left);

            if (right.State == ButtonState.States.Down)
            {
                _transform.Direction = Vector2.UnitX;
                _animationComponent.Animation = _animationsSet.GetAnimation("Run");
            }
            else if (left.State == ButtonState.States.Down)
            {
                _transform.Direction = -Vector2.UnitX;
                _animationComponent.Animation = _animationsSet.GetAnimation("Run");
            }
            else if (right.State == ButtonState.States.Up)
                _animationComponent.Animation = _animationsSet.GetAnimation("Idle");
            else if (left.State == ButtonState.States.Up)
                _animationComponent.Animation = _animationsSet.GetAnimation("Idle");
    }
}

Let’s see what’s happening here. At every update we get the current input state and if the button is pressed, we switch to the “Run” animation. Otherwise we go back to “Idle”. If we’re going left, we also reverse the direction.

The last step is to update our AnimatedSpriteRenderComponent to be able to switch between animations:

public class AnimatedSpriteRenderComponent : BaseComponent
{
	private int _currFrameIndex = 0;
	private int _currFramePosX = 0;
	private float _lastUpdate = 0f;
	
	private readonly Transform _transform;
	private AnimationsSet.Animation _animation;
	
	public async ValueTask Render(GameContext game, Canvas2DContext context)
	{
		if (null == Animation)
			return;
		
		if (game.GameTime.TotalTime - _lastUpdate > 1000f / Animation.Fps)
		{
			++_currFrameIndex;
			_lastUpdate = game.GameTime.TotalTime;
			_currFramePosX = (_currFrameIndex % Animation.FramesCount) * Animation.FrameSize.Width;
		}

		var dx = -(_transform.Direction.X-1f) * Animation.FrameSize.Width / 2f;
		await context.SetTransformAsync(_transform.Direction.X, 0, 0, 1, dx, 0);

		await context.DrawImageAsync(Animation.ImageRef, _currFramePosX, 0,
			Animation.FrameSize.Width, Animation.FrameSize.Height,
			_transform.Position.X, _transform.Position.Y,
			Animation.FrameSize.Width, Animation.FrameSize.Height);
	}

	public AnimationsSet.Animation Animation
	{
		get => _animation;
		set
		{
			if (_animation == value)
				return;
			_currFrameIndex = 0;
			_animation = value;
		}
	}
}

Not very different from the last time, just a few things to note. The call to SetTransformAsync() will create and set a transformation matrix. We will use it for now to handle the character flip left/right. Also, we also have to make sure the current frame index is brought back to 0 when we set a new Animation.

That’s it for now! Next time we’ll see how to refactor this code to a more clean structure. Bye!

Did you like this post? Then