Quote:Original post by Shinkage
How about using lambda expressions?
This. When you're hard coding
and duplicating the terminology of the programming language (and, or, etc), that's a big indicator to me that you can simplify things by just using the language itself.
For example, we could set up a system that would look like:
input.CreateNamedInputEvent( "Blackscreen", s => { return ( s.IsDown(Keys.G) && s.IsDown(Keys.F) ) || ( s.IsPressed(MouseButtons.LeftButton) ) || ( s.IsDown(Keys.W) && s.IsReleased(MouseButtons.RightButton) ) ;});
I personally use an even simpler system -- I have a list of 'trigger's, each with a list of XInput buttons and keyboard buttons. If any are pressed, the trigger's member, .Value, is set to true. If it's a once off action (like spawning an asteroid in debug mode), it's reset to false by the game logic. If it's a continuous action like firing, it just stays set.
If
all the relevant keys and buttons are released, then the .Value is also set to false.
Using the system is pretty simple:
class AsteroidsInputBindings : InputBindings { public readonly InputAxis Thrust = new InputAxis("Thrust") { Positive = new InputTrigger("Forward" ) { Keys = { Keys.Up , Keys.W } } , Negative = new InputTrigger("Backward") { Keys = { Keys.Down, Keys.S } } , XInput = pad => pad.GetNormalizedLeftThumbY() }, Rotation = new InputAxis("Rotation") { Positive = new InputTrigger("Left" ) { Keys = { Keys.Left , Keys.A } } , Negative = new InputTrigger("Right") { Keys = { Keys.Right, Keys.D } } , XInput = pad => -pad.GetNormalizedLeftThumbX() }, Strafe = new InputAxis("Strafe") { Negative = new InputTrigger("Strafe Left" ) { Keys = { Keys.Q } } , Positive = new InputTrigger("Strafe Right") { Keys = { Keys.E } } , XInput = pad => ((int)pad.RightTrigger-(int)pad.LeftTrigger)/255f }; public readonly InputTrigger Fire = new InputTrigger("Fire" ) { Keys = { Keys.Z, Keys.ControlKey }, Buttons = GamepadButtonFlags.A }, Teleport = new InputTrigger("Teleport") { Keys = { Keys.X, Keys.Space }, Buttons = GamepadButtonFlags.B }, Suicide = new InputTrigger("Suicide" ) { Keys = { Keys.C, Keys.Escape }, Buttons = GamepadButtonFlags.Start }, Respawn = new InputTrigger("Respawn" ) { Keys = { Keys.Z, Keys.ControlKey }, Buttons = GamepadButtonFlags.Start|GamepadButtonFlags.A }, DebugSpawnAsteroid = new InputTrigger("Debug: Spawn Asteroid") { Keys = { Keys.F1 } }, DebugReset = new InputTrigger("Debug: Reset Worst") { Keys = { Keys.F2 } }; }
Of note, I don't actually use the trigger string names at all right now -- I just directly use myinputsystem.Fire.Value, for example, which has the wonderful effect of compile-time checking for typos. InputBindings abuses reflection to make things just work (tm).
Full source (sorry for the terse and poorly commented code). Feel free to steal/abuse/ignore as you please:
using System;using System.Collections.Generic;using System.Linq;using System.Reflection;using System.Runtime.InteropServices;using System.Windows.Forms;using SlimDX.XInput;using Key = System.Windows.Forms.Keys; // Used exclusively to initialize KeyOverridesnamespace Asteroids { class InputTrigger { public InputTrigger( string name ) {} public readonly List<Keys> Keys = new List<Keys>(); public GamepadButtonFlags Buttons { get; set; } public bool Value { get; set; } static readonly Dictionary<Keys,string> KeyOverrides = new Dictionary<Keys,string>() { { Key.NumPad0, "Numpad 0" } , { Key.NumPad1, "Numpad 1" }, { Key.NumPad2, "Numpad 2" }, { Key.NumPad3, "Numpad 3" } , { Key.NumPad4, "Numpad 4" }, { Key.NumPad5, "Numpad 5" }, { Key.NumPad6, "Numpad 6" } , { Key.NumPad7, "Numpad 7" }, { Key.NumPad8, "Numpad 8" }, { Key.NumPad9, "Numpad 9" } , { Key.ControlKey , "Control" }, { Key.ShiftKey , "Shift" }, { Key.Menu , "Alt" } , { Key.LControlKey, "Left Control" }, { Key.LShiftKey, "Left Shift" }, { Key.LMenu, "Left Alt" } , { Key.RControlKey, "Right Control" }, { Key.RShiftKey, "Right Shift" }, { Key.RMenu, "Right Alt" } }; public string BindingDescription { get { var keys = Keys.Select(k=>KeyOverrides.ContainsKey(k)?KeyOverrides[k]:Enum.GetName(typeof(Keys),k)); var buttons = Enum.GetValues(typeof(GamepadButtonFlags)).Cast<GamepadButtonFlags>().Where(b=>(b&Buttons)!=GamepadButtonFlags.None).Select(b=>"("+Enum.GetName(typeof(GamepadButtonFlags),b)+")"); var joined = keys.Concat(buttons).ToArray(); switch ( joined.Length ) { case 0: return "N/A"; case 1: return joined[0]; case 2: return joined[0] + " or " + joined[1]; default: return string.Join(", ",joined.Take(joined.Length-1).ToArray()) + ", or " + joined.Last(); } }} } class InputAxis { public InputAxis( string name ) {} public InputTrigger Negative { get; set; } public InputTrigger Positive { get; set; } public Func<Gamepad,float> XInput { get; set; } public float Value { get; set; } } class InputBindings { private readonly Dictionary<Keys,bool> HeldKeys = new Dictionary<Keys,bool>(); Gamepad PreviousGamepad; private static readonly BindingFlags BindingFlags = BindingFlags.FlattenHierarchy|BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance|BindingFlags.Static|BindingFlags; public IEnumerable<InputAxis> Axises { get { var fields = this .GetType() .GetFields(BindingFlags) .Where(p=>typeof(InputAxis).IsAssignableFrom(p.FieldType)) .Select(p=>(InputAxis)p.GetValue(this)) ; var properties = this .GetType() .GetProperties(BindingFlags) .Where(p=>typeof(InputAxis).IsAssignableFrom(p.PropertyType)) .Select(p=>(InputAxis)p.GetValue(this,null)) ; return fields.Concat(properties).Distinct(); }} public IEnumerable<InputTrigger> SimpleTriggers { get { var fields = this .GetType() .GetFields(BindingFlags) .Where(p=>typeof(InputTrigger).IsAssignableFrom(p.FieldType)) .Select(p=>(InputTrigger)p.GetValue(this)) ; var properties = this .GetType() .GetProperties(BindingFlags) .Where(p=>typeof(InputTrigger).IsAssignableFrom(p.PropertyType)) .Select(p=>(InputTrigger)p.GetValue(this,null)) ; return fields.Concat(properties).Distinct(); }} public IEnumerable<InputTrigger> AllTriggers { get { return SimpleTriggers .Concat(Axises.Select(a=>a.Positive)) .Concat(Axises.Select(a=>a.Negative)) .Distinct() ; }} void UpdateAllTriggersAndAxises() { // release trigger if all buttons released: foreach ( var t in AllTriggers ) if ( t.Value && ((t.Buttons&PreviousGamepad.Buttons) == GamepadButtonFlags.None) && !t.Keys.Any(k=>HeldKeys.ContainsKey(k)&&HeldKeys[k]) ) t.Value = false; foreach ( var a in Axises ) { a.Value = a.XInput!=null ? a.XInput(PreviousGamepad) : 0f; // Use trigger values (keys, buttons) for axis value if XInput controller is deadzoned or unavailable: if ( a.Value == 0f ) a.Value = (a.Positive.Value == a.Negative.Value) ? 0f : a.Negative.Value ? -1f : +1f; } } // DO NOT USE: // [DllImport("user32.dll")] private static extern short GetAsyncKeyState(Keys key); // "Although the least significant bit of the return value indicates whether the key has been pressed since the // last query, due to the pre-emptive multitasking nature of Windows, another application can call GetAsyncKeyState // and receive the "recently pressed" bit instead of your application." // Source: http://msdn.microsoft.com/en-us/library/ms646293(VS.85).aspx /// <summary> /// Read the 256 key states /// </summary> /// <param name="lpKeyState">An array of at least 256 entries</param> [DllImport("user32.dll", SetLastError=true)] static extern bool GetKeyboardState(byte [] lpKeyState); // TODO: Wrap in an precondition enforcer? static readonly Keys[] SpecialKeys = new[] { Keys.ControlKey, Keys.LControlKey, Keys.RControlKey, Keys.ShiftKey, Keys.LShiftKey, Keys.RShiftKey, Keys.Menu, Keys.LMenu, Keys.RMenu }; public void UpdateKeyboard( Keys key, bool down ) { switch ( key ) { case Keys.ControlKey: case Keys.ShiftKey: case Keys.Menu: // Alt // This is a work around the fact that multiple physical keys have this enumeration, causing // the sequence of down (key 1), down (key 2), up (either) to occur while still having one button held. // We do this by using GetKeyboardState instead of assuming up means all instances are up. // // The alternative of counting ups and downs is not recommended due to focus change issues. var keys = new byte[256]; if (!GetKeyboardState(keys)) throw new Exception( "Some sort of GetKeyboardState failure not reported by GetLastError" ); foreach ( var sk in SpecialKeys ) UpdateKeyboardRaw( sk, (keys[(int)sk]&0x80)!=0 ); break; default: UpdateKeyboardRaw(key,down); break; } } void UpdateKeyboardRaw( Keys key, bool down ) { if (!HeldKeys.ContainsKey(key)) HeldKeys.Add(key,false); bool previous = HeldKeys[key]; HeldKeys[key] = down; if ( down && previous ) return; // ignore duplicate keypresses if ( down ) foreach ( var t in AllTriggers ) if ( t.Keys.Contains(key) ) t.Value = true; UpdateAllTriggersAndAxises(); } public void ClearGamepad() { PreviousGamepad = default(Gamepad); UpdateAllTriggersAndAxises(); } public void UpdateGamepad( Gamepad gamepad ) { var newly_pressed = gamepad.Buttons & ~PreviousGamepad.Buttons; PreviousGamepad = gamepad; foreach ( var t in AllTriggers ) if ( (t.Buttons&newly_pressed) != GamepadButtonFlags.None ) t.Value = true; UpdateAllTriggersAndAxises(); } } class AsteroidsInputBindings : InputBindings { public readonly InputAxis Thrust = new InputAxis("Thrust") { Positive = new InputTrigger("Forward" ) { Keys = { Keys.Up , Keys.W } } , Negative = new InputTrigger("Backward") { Keys = { Keys.Down, Keys.S } } , XInput = pad => pad.GetNormalizedLeftThumbY() }, Rotation = new InputAxis("Rotation") { Positive = new InputTrigger("Left" ) { Keys = { Keys.Left , Keys.A } } , Negative = new InputTrigger("Right") { Keys = { Keys.Right, Keys.D } } , XInput = pad => -pad.GetNormalizedLeftThumbX() }, Strafe = new InputAxis("Strafe") { Negative = new InputTrigger("Strafe Left" ) { Keys = { Keys.Q } } , Positive = new InputTrigger("Strafe Right") { Keys = { Keys.E } } , XInput = pad => ((int)pad.RightTrigger-(int)pad.LeftTrigger)/255f }; public readonly InputTrigger Fire = new InputTrigger("Fire" ) { Keys = { Keys.Z, Keys.ControlKey }, Buttons = GamepadButtonFlags.A }, Teleport = new InputTrigger("Teleport") { Keys = { Keys.X, Keys.Space }, Buttons = GamepadButtonFlags.B }, Suicide = new InputTrigger("Suicide" ) { Keys = { Keys.C, Keys.Escape }, Buttons = GamepadButtonFlags.Start }, Respawn = new InputTrigger("Respawn" ) { Keys = { Keys.Z, Keys.ControlKey }, Buttons = GamepadButtonFlags.Start|GamepadButtonFlags.A }, DebugSpawnAsteroid = new InputTrigger("Debug: Spawn Asteroid") { Keys = { Keys.F1 } }, DebugReset = new InputTrigger("Debug: Reset Worst") { Keys = { Keys.F2 } }; }}
[Edited by - MaulingMonkey on July 10, 2010 1:16:09 AM]