C++ Controller Input Class

Something I’ve always enjoyed programming is input via a controller, I’d dabbled with Microsoft’s XINPUT classes for the Xbox 360 controller before and I found it really interesting so I decided to put together a collection of classes which would allow detection of common input scenarios for video games.

As it currently stands, the specific detections I’ve created are:

  • QTE from Heavy Rain

    QTE from Heavy Rain

    Thumb Stick Rotation. As commonly found in Quick Time Events in games, this is used to detect if a certain thumb stick is moved from a certain position to another in a circular motion. This is achieved by imagining the thumb stick as a circle and splitting it up into slices and detecting which is the thumb stick is currently situated in. You can set how many slices you want the circle to be separated into for enhanced accuracy. The class supports rotation from any point 360 degrees back to the start or any position in between.

  • Button Mash. This one is fairly simple, when a specified button is mashed fast enough for long enough. I’ve achieved this by having a value which starts at zero, then is increased x amount each time the button is pressed. Once the value reaches 1.0, then the button has been mashed sufficiently. Of course, the value is always decreasing by a small amount each update so you have to be quick to reach 1.0!
  • Button Combo: Exactly the kind of thing you’d find in every fighting game.

Function Pointers:

Something I hadn’t used in C++ before that I became extremely familiar with by the end of this project was function pointers, I knew they existed in C++ and the idea behind them seemed really powerful to me so I decided to design all the classes in this project around passing function pointers.

The most difficult part of this came when creating the button combo class where I planned to pass in a list of function pointers to the specific buttons that must be pressed in order. This got slightly confusing when having to deal with pointers to pointers and arrays at the same time, but through using typedef’s and reading + understanding compile time errors I was able to overcome these problems.

typedef float (XInputHandler::*XInputFctnPtr) ();
typedef float (XInputHandler::*XInputFctnPtrArray[MAX_BUTTON_COMBO_SEQUENCE]) ();

 

Extra Stuff:

In order to possibly make all input types interchangeable, every method of input on the controller is evaluated by returning a float so that the A button for example can be swapped out with the right trigger easily.

 

Code:

XInputHandler:

#include "XInputHandler.h"


XInputHandler::XInputHandler(PlayerIndex playerIndex)
{
	this->playerNum = playerIndex;
}


XInputHandler::~XInputHandler()
{

}

bool XInputHandler::isControllerConnected()
{
	DWORD error = XInputGetState(playerNum, &xInputControllerState);
	if (error == ERROR_DEVICE_NOT_CONNECTED) return false;
	else return true;
}

bool XInputHandler::Update(float fDT)
{
	//Return false if no longer connected.
	DWORD error = XInputGetState(playerNum, &xInputControllerState);
	if (error == ERROR_DEVICE_NOT_CONNECTED) return false;
	else return true;
}

float XInputHandler::getLeftTrigger()
{
	return xInputControllerState.Gamepad.bLeftTrigger / MAX_BYTE_SIZE;
}

float XInputHandler::getRightTrigger()
{
	return xInputControllerState.Gamepad.bRightTrigger / MAX_BYTE_SIZE;
}

float XInputHandler::getLeftStickX()
{
	if (xInputControllerState.Gamepad.sThumbLX == 32767) return 1.0f;
	return (float)xInputControllerState.Gamepad.sThumbLX / MAX_INT_SIZE;
}

float XInputHandler::getLeftStickY()
{
	if (xInputControllerState.Gamepad.sThumbLY == 32767) return 1.0f;
	return (float)xInputControllerState.Gamepad.sThumbLY / MAX_INT_SIZE;
}

float XInputHandler::getRightStickX()
{
	if (xInputControllerState.Gamepad.sThumbRX == 32767) return 1.0f;
	return (float)xInputControllerState.Gamepad.sThumbRX / MAX_INT_SIZE;
}

float XInputHandler::getRightStickY()
{
	if (xInputControllerState.Gamepad.sThumbRY == 32767) return 1.0f;
	return (float) xInputControllerState.Gamepad.sThumbRY / MAX_INT_SIZE;
}

Vector2 XInputHandler::getLeftStickVector()
{
	return Vector2(getLeftStickX(), getLeftStickY());
}

Vector2 XInputHandler::getRightStickVector()
{
	return Vector2(getRightStickX(), getRightStickY());

}

float XInputHandler::getButtonDownA()
{
	if (xInputControllerState.Gamepad.wButtons & XINPUT_GAMEPAD_A) return 1.0f;
	else return 0.0f;
}
float XInputHandler::getButtonDownB()
{
	if (xInputControllerState.Gamepad.wButtons & XINPUT_GAMEPAD_B) return 1.0f;
	else return 0.0f;
}
float XInputHandler::getButtonDownX()
{
	if (xInputControllerState.Gamepad.wButtons & XINPUT_GAMEPAD_X) return 1.0f;
	else return 0.0f;
}
float XInputHandler::getButtonDownY()
{
	if (xInputControllerState.Gamepad.wButtons & XINPUT_GAMEPAD_Y) return 1.0f;
	else return 0.0f;
}
float XInputHandler::getButtonDownStart()
{
	if (xInputControllerState.Gamepad.wButtons & XINPUT_GAMEPAD_START) return 1.0f;
	else return 0.0f;
}
float XInputHandler::getButtonDownBack()
{
	if (xInputControllerState.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) return 1.0f;
	else return 0.0f;
}
float XInputHandler::getButtonDownLeftBumper()
{
	if (xInputControllerState.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) return 1.0f;
	else return 0.0f;
}
float XInputHandler::getButtonDownRightBumper()
{
	if (xInputControllerState.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) return 1.0f;
	else return 0.0f;
}

ThumbStickRotate:

#include "ThumbStickRotate.h"
#include <iostream>

ThumbStickRotate::ThumbStickRotate(XInputHandler *inputHandler, float startPos, float endPos)
{
	this->inputHandler = inputHandler;
	currentPosition = 0;
	float stepSize = (endPos - startPos) / NUMBER_OF_SEGMENTS;

	if (endPos < startPos) bRotateClockwise = false;
	else bRotateClockwise = true;

	for (int i = 0; i < NUMBER_OF_SEGMENTS; i++)
		angleSteps[i] = startPos + (stepSize * i);
}

bool ThumbStickRotate::Update()
{
	Vector2 newVec = inputHandler->getLeftStickVector();

	if (newVec.getMag() < 0.5f)
	{
		currentPosition = 0;
		return false;
	}
	float fAngle = newVec.getAngle();
	if (newVec.fX < 0.0f) fAngle = 360 - fAngle;

	std::cout << "fangle: " << fAngle << " currentPos: " << angleSteps[currentPosition] << std::endl;


}

bool ThumbStickRotate::UpdateClockwise(float fAngle)
{
	if (fAngle > angleSteps[currentPosition])
	{
		if (currentPosition == (NUMBER_OF_SEGMENTS - 1))
		{
			currentPosition = 0;
			return true;
		}
		else if (fAngle < angleSteps[currentPosition + 1])
			currentPosition++;
	}
	return false;
}
bool ThumbStickRotate::UpdateAntiClockwise(float fAngle)
{
	if (fAngle < angleSteps[currentPosition])
	{
		if (currentPosition == (NUMBER_OF_SEGMENTS - 1))
		{
			currentPosition = 0;
			return true;
		}
		else if (fAngle > angleSteps[currentPosition + 1])
			currentPosition++;
	}
	return false;
}


ThumbStickRotate::~ThumbStickRotate()
{
	this->inputHandler = nullptr;
}

ButtonMash:

#include "ButtonMash.h"
#include <iostream>

ButtonMash::ButtonMash(XInputHandler *inputHandler, XInputFctnPtr buttonPtr)
{
	m_xinptHandler = inputHandler;
	ButtonFunctionPointer = buttonPtr;
	currentValue = 0.0f;
	btnDown = false;
}

bool ButtonMash::Update(float fDT)
{
	currentValue -= 0.001f;
	if (currentValue < 0.0f) currentValue = 0.0f;

	if ((m_xinptHandler->*ButtonFunctionPointer)())
	{
		if (!btnDown)
		currentValue += 0.1f;
		btnDown = true;
	}
	else btnDown = false;

	if (currentValue >= 1.0f) return true;
	return false;
}

ButtonMash::~ButtonMash()
{
	m_xinptHandler = nullptr;
	ButtonFunctionPointer = nullptr;
}