Tuesday, April 28, 2009

Input Wrangling Part 2

So in the previous post we defined a Control which has a name and one or more inputs it has to poll to know it's status. The messy part here is that while Microsoft has made it easy to poll a single input to know it's status, such as a game pads left shoulder button using GamePad.GetState(players[1]).Buttons.LeftShoulder, setting it up so that which input to be polled is dynamically configured is, if not complex, rather wordy. The reason is that each controll has to be able to potentially poll each and every possibly configured input, and there are over 25 of those, not including each keyboard key, so you end up with a long switch statement or if/elseif chain that looks like this

 foreach (InputSettings.Trigger trigger in triggerList)
{
    bool status;
    switch(trigger.inputType)
    {
            ...
        case "LeftShoulder":
            status = pgState.Buttons.LeftShoulder == ButtonState.Pressed;
            break;
        case "RightShoulder":
            status = pgState.Buttons.LeftShoulder == ButtonState.Pressed;
            break;
            ...
    }
    // deal with input type status conflicts here
}

As you can see, each Controll has to loop over this massive switch statement for each of its triggers, there must be a more effecient way. What we'd like to do is store the inputs themselves in an array or list so we can to poll them directly without having to sift through a lot of the inputs we don't want first. Unfortunatly you can't store a reference to an input, and Microsoft strongly discourages storing a reference to a status instance, stating its important to get a new instance everytime you want to poll an input. What we can do is store the code that does the polling itself independantly.

One way to do this is to create small classes that hold one input poll each, such as:

    public abstract class InputPoll
    {
        public abstract float poll(GamePadState gpState);
    }
    public class LeftShoulder : InputPoll
    {
        public override float poll(GamePadState pgState)
        {
            return pgState.Buttons.LeftShoulder == ButtonState.Pressed ? 1.0f : 0.0f;
        }
    }
    public class RightShoulder : InputPoll
    {
        public override float poll(GamePadState pgState)
        {
            return pgState.Buttons.RightShoulder == ButtonState.Pressed ? 1.0f : 0.0f;
        }
    }

Then you have code that creates a list of these input triggers for each controller at instantiation time:

    List<InputPoll> inputTriggers = new List<InputPoll>();
    foreach (InputSettings.Trigger trigger in triggerList)
    {
        switch(trigger.Type)
        {
            case "LeftShoulder":
                inputTriggers.add(new RightShoulder());
                break;
            case "RightShoulder":
                inputTriggers.add(new LeftShoulder());
                break;
        }
    }

Then on each input the Controll will only have to go through its list of trigger inputs to determine its status:

    foreach(InputPoll inputTrigger in inputTriggers)
    {
        status = inputTrigger.poll(pgStatus);
        // deal with input type status conflicts here
    }

What this has really occomplished is moving the big ugly switch statement from the update code to the initialization code, which is a good thing. We can, however, do a little better.

Next:  The joys and dangers of C# delegates

No comments:

Post a Comment