Input Class Structure

Whether you're a newbie or an experienced programmer, any questions, help, or just talk of any language will be welcomed here.

Moderator: Coders of Rage

Post Reply
User avatar
EccentricDuck
Chaos Rift Junior
Chaos Rift Junior
Posts: 305
Joined: Sun Feb 21, 2010 11:18 pm
Current Project: Isometric "2.5D" Airship Game
Favorite Gaming Platforms: PS2, SNES, GBA, PC
Programming Language of Choice: C#, Python, JScript
Location: Edmonton, Alberta

Input Class Structure

Post by EccentricDuck »

I was just wondering how people would normally structure an input class/classes. I'm currently working on a 3D project in XNA. My initial thought was to just create a basic class with various methods that have return types appropriate to their function. These would include methods like CameraPosition, CameraTarget (the first two that I implemented for testing purposes), movement, etc. I'm not sure if this is the most effective route to go though. Things like CameraPosition and CameraTarget are often directly linked to movement of the character model so that means I end up creating extra code where I don't need it (mapping the same key multiple times, eg. the up arrow would move the character forward, camera position forward, and camera target forward). I suppose I could handle camera with a separate class and implement Inheritance/Polymorphism...

Here's how the code for one of the methods currently looks:

Code: Select all

        public Vector3 CameraTarget(Vector3 cameraTarget)
        {
            #if !XBOX
            keyboardState = Keyboard.GetState();

            if (keyboardState.IsKeyDown(Keys.PageDown))
            {
                cameraTarget -= new Vector3(0.0f, 5.0f, 0.0f);
            }

... (and so on for the different keys)

            #endif
            return cameraTarget;
I currently just instantiate the Input class and call each method separately. The #if !XBOX command is just there since this is intended for running on the 360, though I suppose it doesn't need keyboard functionality removed from the console. I remember playing FFXI on the 360 back in the day and I played it on a keyboard - I should see if it supports that through XNA. Also, I plan on implementing a previousKeyboardState field to allow for single button presses, but I'm still trying to figure out the best place to put it.

I think I may have figured out how I'll do this just by typing it out :lol: I'd still greatly appreciate any input though (haha, get it, input... )
User avatar
EccentricDuck
Chaos Rift Junior
Chaos Rift Junior
Posts: 305
Joined: Sun Feb 21, 2010 11:18 pm
Current Project: Isometric "2.5D" Airship Game
Favorite Gaming Platforms: PS2, SNES, GBA, PC
Programming Language of Choice: C#, Python, JScript
Location: Edmonton, Alberta

Re: Input Class Structure

Post by EccentricDuck »

I decided to use a nested class in my input class that contains the inputState and previousInputState fields. The problem I was having was that, being structs, the inputStates were not able to be changed directly at the beginning and end of each update loop without using return values - and for the sake of simplicity in my main class I wanted the input states to remain solely in the Input class.

The problem with structs is that they are simply data types as opposed to reference types so they couldn't be updated persistently in methods. By referencing a field of a struct (eg. keyboardInputState which is in the Input class) in one of the methods in that class (eg. CameraTarget, which might be used for rotating where the camera is "looking") and updating the field (eg. with a GetState() call which gathers the current state of the keyboard), that value is essentially copied as opposed to referencing the original value and updating it. This is bad for two reasons:
1) A previousInputState variable (for both keyboard and gamepad) needs to be updated at the end of each update loop for detecting things like single button presses (compares the inputState with previousInputState from the last update loop). This doesn't work unless the inputState is persistent so that it can be referenced at the end of the update loop.
2) The GetState() will be called 60 times per second for both the keyboard and gamepad so it seems like a waste of resources to create a new updated copy of the inputState for both of those on every call that'll go to the stack. It's only a single 32-byte data type for each of them so I suppose it's not that much for a PC or 360 - assuming the garbage collector does a good job of getting rid of that. Seems like a bad idea anyway though since garbage collection takes some resources and it's a bad habit that could cause a memory leak if not caught in an unmanaged code environment.

Here's the code for the Input class so far:

Code: Select all

    class Input 
    {
        protected class InputStates
        {
            public GamePadState gamePadState;
            public KeyboardState keyboardState;

            public GamePadState previousGamePadState;
            public KeyboardState previousKeyboardState;
        }

        protected InputStates inputStates;

        // Constructor
        public Input()
        {
            inputStates = new InputStates();
        }

        /// <summary>
        /// Run at the beginning of input calls
        /// in the Update() loop.
        /// </summary>
        public void GetState()
        {
            inputStates.gamePadState = GamePad.GetState(PlayerIndex.One);
            inputStates.keyboardState = Keyboard.GetState();
        }

        /// <summary>
        /// Run at the end of input calls
        /// in the Update() loop.
        /// </summary>
        public void PreviousInputState()
        {
            inputStates.previousGamePadState = inputStates.gamePadState;
            inputStates.previousKeyboardState = inputStates.keyboardState;
        }

        public bool SinglePress()
        {
            #region Keyboard

            if (inputStates.keyboardState.IsKeyDown(Keys.Space) &&
                inputStates.previousKeyboardState.IsKeyUp(Keys.Space))
            {
                return true;
            }

            else
            {
                return false;
            }

            #endregion
        }

        public Vector3 ObjectRotation()
        {
            Vector3 objectRotation = new Vector3();

            #region GamePad

            objectRotation.Y -=
                inputStates.gamePadState.ThumbSticks.Left.X * 0.1f;

            objectRotation.X -=
                inputStates.gamePadState.ThumbSticks.Left.Y * 0.1f;

            #endregion

            #region Keyboard

            if (inputStates.keyboardState.IsKeyDown(Keys.Left))
            {
                objectRotation.Y += 0.05f;
            }
            if (inputStates.keyboardState.IsKeyDown(Keys.Right))
            {
                objectRotation.Y -= 0.05f;
            }
            if (inputStates.keyboardState.IsKeyDown(Keys.Up))
            {
                objectRotation.X += 0.05f;
            }
            if (inputStates.keyboardState.IsKeyDown(Keys.Down))
            {
                objectRotation.X -= 0.05f;
            }

            #endregion

            return objectRotation;
        }

        public Vector3 CameraPosition()
        {
            Vector3 cameraPosition = new Vector3();

            #region Keyboard

            if (inputStates.keyboardState.IsKeyDown(Keys.PageDown))
            {
                cameraPosition -= new Vector3(0.0f, 5.0f, 0.0f);
            }

            if (inputStates.keyboardState.IsKeyDown(Keys.PageUp))
            {
                cameraPosition += new Vector3(0.0f, 5.0f, 0.0f);
            }

            if (inputStates.keyboardState.IsKeyDown(Keys.Left))
            {
                cameraPosition -= new Vector3(20.0f, 0.0f, 0.0f);
            }

            if (inputStates.keyboardState.IsKeyDown(Keys.Right))
            {
                cameraPosition += new Vector3(20.0f, 0.0f, 0.0f);
            }

            if (inputStates.keyboardState.IsKeyDown(Keys.Up))
            {
                cameraPosition -= new Vector3(0.0f, 0.0f, 20.0f);
            }

            if (inputStates.keyboardState.IsKeyDown(Keys.Down))
            {
                cameraPosition += new Vector3(0.0f, 0.0f, 20.0f);
            }
            #endregion

            return cameraPosition;
        }

        public Vector3 CameraTarget()
        {
            Vector3 cameraTarget = new Vector3();

            #region Keyboard

            if (inputStates.keyboardState.IsKeyDown(Keys.PageDown))
            {
                cameraTarget -= new Vector3(0.0f, 5.0f, 0.0f);
            }

            if (inputStates.keyboardState.IsKeyDown(Keys.PageUp))
            {
                cameraTarget += new Vector3(0.0f, 5.0f, 0.0f);
            }

            if (inputStates.keyboardState.IsKeyDown(Keys.Left))
            {
                cameraTarget -= new Vector3(20.0f, 0.0f, 0.0f);
            }

            if (inputStates.keyboardState.IsKeyDown(Keys.Right))
            {
                cameraTarget += new Vector3(20.0f, 0.0f, 0.0f);
            }

            if (inputStates.keyboardState.IsKeyDown(Keys.Up))
            {
                cameraTarget -= new Vector3(0.0f, 0.0f, 20.0f);
            }

            if (inputStates.keyboardState.IsKeyDown(Keys.Down))
            {
                cameraTarget += new Vector3(0.0f, 0.0f, 20.0f);
            }
            #endregion

            return cameraTarget;
        }
    }
I still need to update the playerindex for gamepads so that it grabs whatever controller the player may be using and assigns it to PlayerIndex.One. Also, I realized that what I said about a new copy of a data type being "a bad habit that could cause a memory leak" is rather ironic, given I do this a large number of times with Vector3s (as a matter of fact it's the primary use of this class so far aside from detection of the keyboard/gamepad states).
Post Reply