I got a lot of good feedback on my other question. So I will take another swing at it, post my revisions, and hopefully get some more.
First, the configuration hasn't changed. Here's the JSON file:
{
"DisplaySettings": {
"IsFullScreen": false,
"PreferredBackBufferWidth": 800,
"PreferredBackBufferHeight": 600
},
"GamePadSettings": {
"Back": "Exit",
"DPadDown": "MoveDown",
"DPadLeft": "MoveLeft",
"DPadRight": "MoveRight",
"DPadUp": "MoveUp"
},
"KeyboardSettings": {
"Escape": "Exit",
"S": "MoveDown",
"A": "MoveLeft",
"D": "MoveRight",
"W": "MoveUp"
}
}
This is parsed into a Settings object:
public class Settings
{
public DisplaySettings DisplaySettings = new DisplaySettings();
public GamePadSettings GamePadSettings = new GamePadSettings();
public KeyboardSettings KeyboardSettings = new KeyboardSettings();
}
public class DisplaySettings
{
public bool IsFullScreen = false;
public int PreferredBackBufferWidth = 800;
public int PreferredBackBufferHeight = 600;
}
public class GamePadSettings
{
public string Back = "Exit";
public string DPadDown = "MoveDown";
public string DPadLeft = "MoveLeft";
public string DPadRight = "MoveRight";
public string DPadUp = "MoveUp";
}
public class KeyboardSettings
{
public string Escape = "Exit";
public string S = "MoveDown";
public string A = "MoveLeft";
public string D = "MoveRight";
public string W = "MoveUp";
}
I might still change these to properties, but public fields are simple and work well with the JSON parser. Main method of Program is largely the same, except now I pass level
and player
to the Input class:
public static class Program
{
private static void Main( string[] args )
{
IFileHandler fileHandler = new JsonHandler();
var settings = fileHandler.Read<Settings>( "settings.json" );
var level = fileHandler.Read<Level>( "level.json" );
var player = fileHandler.Read<Entity>( "player.json" );
using ( var game = new MyGame( new Graphics( settings.DisplaySettings, level, player ),
new Input( settings.GamePadSettings, settings.KeyboardSettings, level, player ) ) )
{
game.Run();
fileHandler.Write( "player.json", player );
fileHandler.Write( "level.json", level );
}
}
}
The reason for this change is MyGame doesn't handle the player actions directly anymore:
public class MyGame : Game
{
private readonly IGraphics graphics;
private readonly IInput input;
public MyGame( IGraphics graphics, IInput input )
{
if ( graphics == null || input == null )
{
throw new ArgumentNullException();
}
this.graphics = graphics;
this.input = input;
graphics.Setup( this );
input.Setup( this );
}
protected override void Update( GameTime gameTime )
{
input.Process();
base.Update( gameTime );
}
protected override void Draw( GameTime gameTime )
{
graphics.Draw();
base.Draw( gameTime );
}
}
Input handles them instead:
public class Input : IInput
{
private readonly GamePadSettings gamePadSettings;
private readonly KeyboardSettings keyboardSettings;
private readonly Level level;
private readonly Entity player;
private Game game;
private IDictionary<Buttons, string> gamePadBindings;
private IDictionary<Keys, string> keyboardBindings;
private GamePadState gamePadState;
private KeyboardState keyboardState;
public Input( GamePadSettings gamePadSettings, KeyboardSettings keyboardSettings, Level level, Entity player )
{
if ( gamePadSettings == null || keyboardSettings == null || level == null || player == null )
{
throw new ArgumentNullException();
}
this.gamePadSettings = gamePadSettings;
this.keyboardSettings = keyboardSettings;
this.level = level;
this.player = player;
}
public void Setup( Game game )
{
if ( game == null )
{
throw new ArgumentNullException();
}
this.game = game;
SetupGamePad();
SetupKeyboard();
}
public void Process()
{
ProcessGamePad();
ProcessKeyboard();
}
private void SetupGamePad()
{
gamePadBindings = new Dictionary<Buttons, string>
{
{ Buttons.Back, gamePadSettings.Back },
{ Buttons.DPadDown, gamePadSettings.DPadDown },
{ Buttons.DPadLeft, gamePadSettings.DPadLeft },
{ Buttons.DPadRight, gamePadSettings.DPadRight },
{ Buttons.DPadUp, gamePadSettings.DPadUp }
};
}
private void SetupKeyboard()
{
keyboardBindings = new Dictionary<Keys, string>
{
{ Keys.Escape, keyboardSettings.Escape },
{ Keys.S, keyboardSettings.S },
{ Keys.A, keyboardSettings.A },
{ Keys.D, keyboardSettings.D },
{ Keys.W, keyboardSettings.W }
};
}
private void ProcessGamePad()
{
gamePadState = GamePad.GetState( PlayerIndex.One );
if ( !gamePadState.IsConnected )
{
return;
}
foreach ( var binding in gamePadBindings )
{
if ( gamePadState.IsButtonDown( binding.Key ) )
{
DoAction( binding.Value );
}
}
}
private void ProcessKeyboard()
{
keyboardState = Keyboard.GetState( PlayerIndex.One );
foreach ( var binding in keyboardBindings )
{
if ( keyboardState.IsKeyDown( binding.Key ) )
{
DoAction( binding.Value );
}
}
}
private void DoAction( string action )
{
if ( action.Equals( Actions.Exit, StringComparison.OrdinalIgnoreCase ) )
{
game.Exit();
return;
}
if ( action.Equals( Actions.MoveDown, StringComparison.OrdinalIgnoreCase ) )
{
Scroll( Directions.Down );
return;
}
if ( action.Equals( Actions.MoveLeft, StringComparison.OrdinalIgnoreCase ) )
{
Scroll( Directions.Left );
return;
}
if ( action.Equals( Actions.MoveRight, StringComparison.OrdinalIgnoreCase ) )
{
Scroll( Directions.Right );
return;
}
if ( action.Equals( Actions.MoveUp, StringComparison.OrdinalIgnoreCase ) )
{
Scroll( Directions.Up );
return;
}
}
private void Scroll( Vector2 direction )
{
// TODO: Collision detection
player.Turn( direction );
player.Animate();
level.Move( direction * -1 );
}
}
Before, I was using reflection to create a dictionary of bindings. This seemed pretty elegant, but had several problems. What I lose in elegance though I more than gain (in my humble opinion) in simplicity. i.e., the DoAction method is very easy to understand. In addition, I no longer have to worry about accidentally exposing a method the user can access via configuration, I don't have a bunch of methods that ReSharper can't find usages of, and performance should be improved.
So what do you think? Is this better than my previous attempt? What else can be improved?