This discussion is for our decision on how to simplify Input Command usage for Milestone 4. We mostly had v0.9.5 functionality in Milestone 3, but there were some changes due cleaner code and dependency injection.
Here is how it worked in v0.9.5, lets start with a InputSettings.xml file, which can be edited by the Input Settings Editor:
<?xml version="1.0" encoding="utf-8"?>
<InputSettings>
<Command Name="QuitTest">
<Trigger Button="Escape" State="Released" />
<Trigger Button="F4" State="Released">
<Modifier Button="LeftAlt" />
<Modifier Button="RightAlt" />
</Trigger>
<Trigger Button="GamePadBack" State="Released" />
</Command>
<Command Name="MakeScreenshot">
<Trigger Button="PrintScreen" State="Pressed" />
</Command>
</InputSettings>
And you can obviously also set it up in pretty much the same way in code:
Input.Commands["Action"].AddTrigger(new CommandTrigger
{
Button = InputButton.MouseLeft,
State = InputState.Released
});
And finally this is how you use commands:
Input.Commands["Action"].Add(owner, delegate
{
Application.BackgroundColor = Color.Random;
});
Input.Commands[CommandQuitGame].Add(this, delegate
{
Application.Quit();
});
The code was a bit verbose sometimes, but worked well, especially allowing many different triggers for all kinds of input (keyboard, gamepad, mouse, touch or gestures). The main issue was however that input commands were kind of optional and you could still use any input device directly, which was often required and also led to lots of duplicate code. Also all commands needed to be checked each frame, which made it important to optimize lots of input code.
In Milestone 3 a simplified system was used, which is quite similar, but mostly was used in this way:
Input.Add(Key.Space, key => { Window.BackgroundColor = Color.Random; });
Nice and easy, however there is no easy way to save this out into a InputSettings.xml file and it also led to code like here in the Breakout game. All these commands do the same thing, they modify the xPosition value via several different input devices:
inputCommands.Add(Key.CursorLeft, State.Pressed,
key => xPosition -= PaddleMovementSpeed * Time.Current.Delta);
inputCommands.Add(Key.CursorRight, State.Pressed,
key => xPosition += PaddleMovementSpeed * Time.Current.Delta);
inputCommands.Add(MouseButton.Left, State.Pressed,
mouse => xPosition += mouse.Position.X - Position.X);
inputCommands.Add(State.Pressed, touch => xPosition += touch.GetPosition(0).X - Position.X);
inputCommands.Add(GamePadButton.Left, State.Pressed,
() => xPosition -= PaddleMovementSpeed * Time.Current.Delta);
inputCommands.Add(GamePadButton.Right, State.Pressed,
() => xPosition += PaddleMovementSpeed * Time.Current.Delta);
Aside from verbose code in the setup, not being able to save out commands and set them up in the editor, and some complex code to link it all up in InputCommands, this system worked quite well, had good performance and was very extensible. Instead of calling Add one could also build his own triggers and attach them to the same command action.
However now with Milestone 4 there are major changes to the Entity System and there are no runners anymore, instead all entities are updated and drawn in parallel. This means it is time to think about how to simplify commands, load them easily and look at all the sample games we got. Here is my proposal. Like before Commands are setup and saved out via the Editor, but can also be done directly in code. Normally games should use a Commands.xml file, which allows to change input bindings easily without recompilation. This is again the Breakout example from above.
Quote:
<?xml version="1.0" encoding="utf-8"?>
<InputSettings>
<ButtonCommand Name="MovePaddleLeft">
<KeyTrigger Button="CursorLeft" />
<GamePadTrigger Button="Left" />
</ButtonCommand>
<ButtonCommand Name="MovePaddleRight">
<KeyTrigger Button="CursorRight" />
<GamePadTrigger Button="Right" />
</ButtonCommand>
<PositionCommand Name="MovePaddleDirectly">
<MousePositionTrigger Button="Left" />
<TouchPositionTrigger />
</PositionCommand>
</InputSettings>
Now in the game the code is much simpler and does not contain any input device specific code anymore, you can support any button or input device now.
Code:
new ButtonCommand("MovePaddleLeft", timeDelta => xPosition -= PaddleMovementSpeed * timeDelta);
new ButtonCommand("MovePaddleRight", timeDelta => xPosition += PaddleMovementSpeed * timeDelta);
new PositionCommand("MovePaddleDirectly", (pos, timeDelta) => xPosition += pos.X - Position.X);
Comments are welcome.
Edited by user Sunday, July 7, 2013 12:38:02 AM(UTC)
| Reason: code tags not working well, only left the bottom one