added SDL controller support

This commit is contained in:
Mathew Velasquez 2015-09-04 01:53:41 -04:00
parent 5f99c595af
commit 186dc1bfe9
10 changed files with 420 additions and 64 deletions

View file

@ -23,6 +23,7 @@
#include <SDL_events.h>
#include <SDL_joystick.h>
#include <SDL_gamecontroller.h>
#include <SDL_messagebox.h>
#include <SDL_timer.h>
#include <SDL_thread.h>
@ -40,6 +41,8 @@
#include <string.h>
#include <map>
typedef void (ALC_APIENTRY *LPALCDEVICEPAUSESOFT) (ALCdevice *device);
typedef void (ALC_APIENTRY *LPALCDEVICERESUMESOFT) (ALCdevice *device);
@ -70,6 +73,7 @@ initALCFunctions(ALCdevice *alcDev)
#define HAVE_ALC_DEVICE_PAUSE alc.DevicePause
uint8_t EventThread::keyStates[];
EventThread::ControllerState EventThread::gcState;
EventThread::JoyState EventThread::joyState;
EventThread::MouseState EventThread::mouseState;
EventThread::TouchState EventThread::touchState;
@ -131,9 +135,21 @@ void EventThread::process(RGSSThreadData &rtData)
bool terminate = false;
SDL_Joystick *js = 0;
if (SDL_NumJoysticks() > 0)
js = SDL_JoystickOpen(0);
std::map<int, SDL_GameController*> controllers;
std::map<int, SDL_Joystick*> joysticks;
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
if (SDL_IsGameController(i)) {
//Load as game controller
SDL_GameController *gc = SDL_GameControllerOpen(i);
int id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gc));
controllers[id] = gc;
} else {
//Fall back to joystick
SDL_Joystick *js = SDL_JoystickOpen(i);
joysticks[SDL_JoystickInstanceID(js)] = js;
}
}
char buffer[128];
@ -145,6 +161,12 @@ void EventThread::process(RGSSThreadData &rtData)
int winW, winH;
int i;
SDL_Joystick *js;
SDL_GameController *gc;
int id;
std::map<int, SDL_Joystick*>::iterator jsit;
std::map<int, SDL_GameController*>::iterator gcit;
SDL_GetWindowSize(win, &winW, &winH);
SettingsMenu *sMenu = 0;
@ -157,7 +179,7 @@ void EventThread::process(RGSSThreadData &rtData)
break;
}
if (sMenu && sMenu->onEvent(event))
if (sMenu && sMenu->onEvent(event, joysticks))
{
if (sMenu->destroyReq())
{
@ -330,31 +352,64 @@ void EventThread::process(RGSSThreadData &rtData)
keyStates[event.key.keysym.scancode] = false;
break;
case SDL_CONTROLLERBUTTONDOWN:
gcState.buttons[event.cbutton.button] = true;
break;
case SDL_CONTROLLERBUTTONUP:
gcState.buttons[event.cbutton.button] = false;
break;
case SDL_CONTROLLERAXISMOTION:
gcState.axes[event.caxis.axis] = event.caxis.value;
break;
case SDL_CONTROLLERDEVICEADDED:
gc = SDL_GameControllerOpen(event.jdevice.which);
id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gc));
controllers[id] = gc;
break;
case SDL_CONTROLLERDEVICEREMOVED:
gcit = controllers.find(event.jdevice.which);
SDL_GameControllerClose(gcit->second);
controllers.erase(gcit);
break;
case SDL_JOYBUTTONDOWN :
joyState.buttons[event.jbutton.button] = true;
if (joysticks.find(event.jbutton.which) != joysticks.end())
joyState.buttons[event.jbutton.button] = true;
break;
case SDL_JOYBUTTONUP :
joyState.buttons[event.jbutton.button] = false;
if (joysticks.find(event.jbutton.which) != joysticks.end())
joyState.buttons[event.jbutton.button] = false;
break;
case SDL_JOYHATMOTION :
joyState.hats[event.jhat.hat] = event.jhat.value;
if (joysticks.find(event.jbutton.which) != joysticks.end())
joyState.hats[event.jhat.hat] = event.jhat.value;
break;
case SDL_JOYAXISMOTION :
joyState.axes[event.jaxis.axis] = event.jaxis.value;
if (joysticks.find(event.jbutton.which) != joysticks.end())
joyState.axes[event.jaxis.axis] = event.jaxis.value;
break;
case SDL_JOYDEVICEADDED :
if (event.jdevice.which > 0)
if (SDL_IsGameController(event.jdevice.which))
break;
js = SDL_JoystickOpen(0);
js = SDL_JoystickOpen(event.jdevice.which);
joysticks[SDL_JoystickInstanceID(js)] = js;
break;
case SDL_JOYDEVICEREMOVED :
resetInputStates();
jsit = joysticks.find(event.jdevice.which);
if (jsit != joysticks.end()) {
SDL_JoystickClose(jsit->second);
joysticks.erase(jsit);
resetInputStates();
}
break;
case SDL_MOUSEBUTTONDOWN :
@ -442,8 +497,10 @@ void EventThread::process(RGSSThreadData &rtData)
/* Just in case */
rtData.syncPoint.resumeThreads();
if (SDL_JoystickGetAttached(js))
SDL_JoystickClose(js);
for (gcit = controllers.begin(); gcit != controllers.end(); ++gcit)
SDL_GameControllerClose(gcit->second);
for (jsit = joysticks.begin(); jsit != joysticks.end(); ++jsit)
SDL_JoystickClose(jsit->second);
delete sMenu;
}
@ -514,6 +571,7 @@ void EventThread::cleanup()
void EventThread::resetInputStates()
{
memset(&keyStates, 0, sizeof(keyStates));
memset(&gcState, 0, sizeof(gcState));
memset(&joyState, 0, sizeof(joyState));
memset(&mouseState.buttons, 0, sizeof(mouseState.buttons));
memset(&touchState, 0, sizeof(touchState));

View file

@ -29,6 +29,7 @@
#include <SDL_scancode.h>
#include <SDL_joystick.h>
#include <SDL_gamecontroller.h>
#include <SDL_mouse.h>
#include <SDL_mutex.h>
@ -46,6 +47,12 @@ union SDL_Event;
class EventThread
{
public:
struct ControllerState
{
int axes[SDL_CONTROLLER_AXIS_MAX];
bool buttons[SDL_CONTROLLER_BUTTON_MAX];
};
struct JoyState
{
int axes[256];
@ -72,6 +79,7 @@ public:
};
static uint8_t keyStates[SDL_NUM_SCANCODES];
static ControllerState gcState;
static JoyState joyState;
static MouseState mouseState;
static TouchState touchState;

View file

@ -100,6 +100,56 @@ struct KbBinding : public Binding
SDL_Scancode source;
};
/* Controller button binding */
struct GcButtonBinding : public Binding
{
GcButtonBinding() {}
bool sourceActive() const
{
return EventThread::gcState.buttons[source];
}
bool sourceRepeatable() const
{
return true;
}
uint8_t source;
};
/* Controller axis binding */
struct GcAxisBinding : public Binding
{
GcAxisBinding() {}
GcAxisBinding(uint8_t source,
AxisDir dir,
Input::ButtonCode target)
: Binding(target),
source(source),
dir(dir)
{}
bool sourceActive() const
{
int val = EventThread::gcState.axes[source];
if (dir == Negative)
return val < -JAXIS_THRESHOLD;
else /* dir == Positive */
return val > JAXIS_THRESHOLD;
}
bool sourceRepeatable() const
{
return true;
}
uint8_t source;
AxisDir dir;
};
/* Joystick button binding */
struct JsButtonBinding : public Binding
{
@ -262,6 +312,8 @@ struct InputPrivate
{
std::vector<KbBinding> kbStatBindings;
std::vector<KbBinding> kbBindings;
std::vector<GcAxisBinding> gcABindings;
std::vector<GcButtonBinding> gcBBindings;
std::vector<JsAxisBinding> jsABindings;
std::vector<JsHatBinding> jsHBindings;
std::vector<JsButtonBinding> jsBBindings;
@ -370,6 +422,8 @@ struct InputPrivate
void applyBindingDesc(const BDescVec &d)
{
kbBindings.clear();
gcABindings.clear();
gcBBindings.clear();
jsABindings.clear();
jsHBindings.clear();
jsBBindings.clear();
@ -395,6 +449,25 @@ struct InputPrivate
break;
}
case CAxis :
{
GcAxisBinding bind;
bind.source = src.d.ja.axis;
bind.dir = src.d.ja.dir;
bind.target = desc.target;
gcABindings.push_back(bind);
break;
}
case CButton :
{
GcButtonBinding bind;
bind.source = src.d.jb;
bind.target = desc.target;
gcBBindings.push_back(bind);
break;
}
case JAxis :
{
JsAxisBinding bind;
@ -435,6 +508,8 @@ struct InputPrivate
appendBindings(msBindings);
appendBindings(kbBindings);
appendBindings(gcABindings);
appendBindings(gcBBindings);
appendBindings(jsABindings);
appendBindings(jsHBindings);
appendBindings(jsBBindings);

View file

@ -64,24 +64,38 @@ struct JsBindingData
}
};
struct GcBindingData
{
int source;
Input::ButtonCode target;
void add(BDescVec &d) const
{
SourceDesc src;
src.type = CButton;
src.d.jb = source;
BindingDesc desc;
desc.src = src;
desc.target = target;
d.push_back(desc);
}
};
/* Common */
static const KbBindingData defaultKbBindings[] =
{
{ SDL_SCANCODE_LEFT, Input::Left },
{ SDL_SCANCODE_LEFT, Input::Left },
{ SDL_SCANCODE_RIGHT, Input::Right },
{ SDL_SCANCODE_UP, Input::Up },
{ SDL_SCANCODE_DOWN, Input::Down },
{ SDL_SCANCODE_H, Input::Left },
{ SDL_SCANCODE_L, Input::Right },
{ SDL_SCANCODE_K, Input::Up },
{ SDL_SCANCODE_J, Input::Down },
{ SDL_SCANCODE_Z, Input::Action },
{ SDL_SCANCODE_SPACE, Input::Action },
{ SDL_SCANCODE_RETURN, Input::Action },
{ SDL_SCANCODE_SPACE, Input::Action },
{ SDL_SCANCODE_X, Input::Cancel },
{ SDL_SCANCODE_ESCAPE, Input::Cancel },
{ SDL_SCANCODE_A, Input::Menu },
{ SDL_SCANCODE_ESCAPE, Input::Menu },
{ SDL_SCANCODE_KP_0, Input::Menu },
{ SDL_SCANCODE_RETURN, Input::Menu },
{ SDL_SCANCODE_S, Input::Items },
{ SDL_SCANCODE_LSHIFT, Input::Run },
{ SDL_SCANCODE_LCTRL, Input::Deactivate },
@ -91,17 +105,28 @@ static const KbBindingData defaultKbBindings[] =
static elementsN(defaultKbBindings);
static const JsBindingData defaultJsBindings[] =
static const GcBindingData defaultGcBindings[] =
{
{ 0, Input::Action },
{ SDL_CONTROLLER_BUTTON_DPAD_LEFT, Input::Left },
{ SDL_CONTROLLER_BUTTON_DPAD_RIGHT, Input::Right },
{ SDL_CONTROLLER_BUTTON_DPAD_UP, Input::Up },
{ SDL_CONTROLLER_BUTTON_DPAD_DOWN, Input::Down },
{ SDL_CONTROLLER_BUTTON_A, Input::Action },
{ SDL_CONTROLLER_BUTTON_B, Input::Cancel },
{ SDL_CONTROLLER_BUTTON_X, Input::Run },
{ SDL_CONTROLLER_BUTTON_Y, Input::Items },
{ SDL_CONTROLLER_BUTTON_START, Input::Menu },
{ SDL_CONTROLLER_BUTTON_BACK, Input::Deactivate },
{ SDL_CONTROLLER_BUTTON_LEFTSHOULDER, Input::L },
{ SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, Input::R },
};
static elementsN(defaultJsBindings);
static elementsN(defaultGcBindings);
static void addAxisBinding(BDescVec &d, uint8_t axis, AxisDir dir, Input::ButtonCode target)
static void addGcAxisBinding(BDescVec &d, uint8_t axis, AxisDir dir, Input::ButtonCode target)
{
SourceDesc src;
src.type = JAxis;
src.type = CAxis;
src.d.ja.axis = axis;
src.d.ja.dir = dir;
@ -112,20 +137,6 @@ static void addAxisBinding(BDescVec &d, uint8_t axis, AxisDir dir, Input::Button
d.push_back(desc);
}
static void addHatBinding(BDescVec &d, uint8_t hat, uint8_t pos, Input::ButtonCode target)
{
SourceDesc src;
src.type = JHat;
src.d.jh.hat = hat;
src.d.jh.pos = pos;
BindingDesc desc;
desc.src = src;
desc.target = target;
d.push_back(desc);
}
BDescVec genDefaultBindings()
{
BDescVec d;
@ -133,23 +144,20 @@ BDescVec genDefaultBindings()
for (size_t i = 0; i < defaultKbBindingsN; ++i)
defaultKbBindings[i].add(d);
for (size_t i = 0; i < defaultJsBindingsN; ++i)
defaultJsBindings[i].add(d);
for (size_t i = 0; i < defaultGcBindingsN; ++i)
defaultGcBindings[i].add(d);
addAxisBinding(d, 0, Negative, Input::Left );
addAxisBinding(d, 0, Positive, Input::Right);
addAxisBinding(d, 1, Negative, Input::Up );
addAxisBinding(d, 1, Positive, Input::Down );
addHatBinding(d, 0, SDL_HAT_LEFT, Input::Left );
addHatBinding(d, 0, SDL_HAT_RIGHT, Input::Right);
addHatBinding(d, 0, SDL_HAT_UP, Input::Up );
addHatBinding(d, 0, SDL_HAT_DOWN, Input::Down );
addGcAxisBinding(d, SDL_CONTROLLER_AXIS_LEFTX, Negative, Input::Left );
addGcAxisBinding(d, SDL_CONTROLLER_AXIS_LEFTX, Positive, Input::Right );
addGcAxisBinding(d, SDL_CONTROLLER_AXIS_LEFTY, Negative, Input::Up );
addGcAxisBinding(d, SDL_CONTROLLER_AXIS_LEFTY, Positive, Input::Down );
addGcAxisBinding(d, SDL_CONTROLLER_AXIS_TRIGGERLEFT, Positive, Input::Deactivate);
addGcAxisBinding(d, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, Positive, Input::Run );
return d;
}
#define FORMAT_VER 3
#define FORMAT_VER 0
struct Header
{
@ -239,12 +247,14 @@ static bool verifyDesc(const BindingDesc &desc)
return true;
case Key:
return src.d.scan < SDL_NUM_SCANCODES;
case CButton:
case JButton:
return true;
case JHat:
/* Only accept single directional binds */
return src.d.jh.pos == SDL_HAT_LEFT || src.d.jh.pos == SDL_HAT_RIGHT ||
src.d.jh.pos == SDL_HAT_UP || src.d.jh.pos == SDL_HAT_DOWN;
case CAxis:
case JAxis:
return src.d.ja.dir == Negative || src.d.ja.dir == Positive;
default:

View file

@ -26,6 +26,7 @@
#include <SDL_scancode.h>
#include <SDL_joystick.h>
#include <SDL_gamecontroller.h>
#include <stdint.h>
#include <assert.h>
#include <vector>
@ -40,6 +41,8 @@ enum SourceType
{
Invalid,
Key,
CButton,
CAxis,
JButton,
JAxis,
JHat
@ -82,8 +85,10 @@ struct SourceDesc
return true;
case Key:
return d.scan == o.d.scan;
case CButton:
case JButton:
return d.jb == o.d.jb;
case CAxis:
case JAxis:
return (d.ja.axis == o.d.ja.axis) && (d.ja.dir == o.d.ja.dir);
case JHat:

View file

@ -41,6 +41,7 @@
#include "binding.h"
#include "icon.png.xxd"
#include "gamecontrollerdb.txt.xxd"
static void
rgssThreadError(RGSSThreadData *rtData, const std::string &msg)
@ -172,7 +173,7 @@ int main(int argc, char *argv[])
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
/* initialize SDL first */
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0)
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0)
{
showInitError(std::string("Error initializing SDL: ") + SDL_GetError());
return 0;
@ -295,6 +296,11 @@ int main(int argc, char *argv[])
RGSSThreadData rtData(&eventThread, argv[0], win,
alcDev, mode.refresh_rate, conf);
/* Add controller bindings from embedded controller DB */
SDL_RWops *controllerDB = SDL_RWFromConstMem(assets_gamecontrollerdb_txt,
assets_gamecontrollerdb_txt_len);
SDL_GameControllerAddMappingsFromRW(controllerDB, 1);
int winW, winH;
SDL_GetWindowSize(win, &winW, &winH);
rtData.windowSizeMsg.post(Vec2i(winW, winH));

View file

@ -81,6 +81,24 @@ static elementsN(vButtons);
/* Human readable string representation */
std::string sourceDescString(const SourceDesc &src)
{
static const char *const gcButtonNames[SDL_CONTROLLER_BUTTON_MAX] = {
"A Button",
"B Button",
"X Button",
"Y Button",
"Back Button",
"Guide Button",
"Start Button",
"Left Stick",
"Right Stick",
"Left Shoulder",
"Right Shoulder",
"D-Pad (Up)",
"D-Pad (Down)",
"D-Pad (Left)",
"D-Pad (Right)",
};
char buf[128];
char pos;
@ -100,10 +118,44 @@ std::string sourceDescString(const SourceDesc &src)
if (*str == '\0')
return "Unknown key";
else
return str;
return std::string(str) + " Key";
}
case CButton:
snprintf(buf, sizeof(buf), "%s", gcButtonNames[src.d.jb]);
return buf;
case CAxis:
switch (src.d.ja.axis) {
case SDL_CONTROLLER_AXIS_LEFTX:
if (src.d.ja.dir == Negative)
return "Left Stick (Left)";
else
return "Left Stick (Right)";
case SDL_CONTROLLER_AXIS_LEFTY:
if (src.d.ja.dir == Negative)
return "Left Stick (Up)";
else
return "Left Stick (Down)";
case SDL_CONTROLLER_AXIS_RIGHTX:
if (src.d.ja.dir == Negative)
return "Right Stick (Left)";
else
return "Right Stick (Right)";
case SDL_CONTROLLER_AXIS_RIGHTY:
if (src.d.ja.dir == Negative)
return "Right Stick (Up)";
else
return "Right Stick (Down)";
case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
return "Left Trigger";
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
return "Right Trigger";
}
return "";
case JButton:
snprintf(buf, sizeof(buf), "JS %d", src.d.jb);
snprintf(buf, sizeof(buf), "Joy Button %d", src.d.jb);
return buf;
case JHat:
@ -128,12 +180,12 @@ std::string sourceDescString(const SourceDesc &src)
default:
pos = '-';
}
snprintf(buf, sizeof(buf), "Hat %d:%c",
snprintf(buf, sizeof(buf), "Joy Hat %d:%c",
src.d.jh.hat, pos);
return buf;
case JAxis:
snprintf(buf, sizeof(buf), "Axis %d%c",
snprintf(buf, sizeof(buf), "Joy Axis %d%c",
src.d.ja.axis, src.d.ja.dir == Negative ? '-' : '+');
return buf;
}
@ -638,9 +690,28 @@ struct SettingsMenuPrivate
break;
case SDL_CONTROLLERBUTTONDOWN:
desc.type = CButton;
desc.d.jb = event.cbutton.button;
break;
case SDL_CONTROLLERAXISMOTION:
{
int v = event.caxis.value;
/* Only register if pushed halfway through */
if (v > -JAXIS_THRESHOLD && v < JAXIS_THRESHOLD)
return true;
desc.type = CAxis;
desc.d.ja.axis = event.caxis.axis;
desc.d.ja.dir = v < 0 ? Negative : Positive;
break;
}
case SDL_JOYBUTTONDOWN:
desc.type = JButton;
desc.d.jb = event.jbutton.button;
desc.d.jb = event.cbutton.button;
break;
case SDL_JOYHATMOTION:
@ -1011,7 +1082,7 @@ SettingsMenu::SettingsMenu(RGSSThreadData &rtData)
const char *info = "Use left click to bind a slot, right click to clear its binding";
p->infoLabel = Label(p, IntRect(16, 16, winSize.x, 16), info, cText, cText, cText);
const char *warn = "Warning: Same physical key bound to multiple slots";
const char *warn = "Warning: Same physical action bound to multiple slots";
p->dupWarnLabel = Label(p, IntRect(16, 40, winSize.x, 16), warn, 255, 0, 0);
p->widgets.push_back(&p->infoLabel);
@ -1034,7 +1105,8 @@ SettingsMenu::~SettingsMenu()
delete p;
}
bool SettingsMenu::onEvent(const SDL_Event &event)
bool SettingsMenu::onEvent(const SDL_Event &event,
const std::map<int, SDL_Joystick*> &joysticks)
{
/* First, check whether this event is for us */
switch (event.type)
@ -1051,6 +1123,9 @@ bool SettingsMenu::onEvent(const SDL_Event &event)
return false;
break;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
case SDL_CONTROLLERAXISMOTION:
case SDL_JOYBUTTONDOWN :
case SDL_JOYBUTTONUP :
case SDL_JOYHATMOTION :
@ -1129,11 +1204,29 @@ bool SettingsMenu::onEvent(const SDL_Event &event)
break;
}
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERAXISMOTION:
if (p->state != AwaitingInput)
return true;
break;
case SDL_JOYBUTTONDOWN:
if (p->state != AwaitingInput)
return true;
if (joysticks.find(event.jbutton.which) == joysticks.end())
return true;
break;
case SDL_JOYHATMOTION:
if (p->state != AwaitingInput)
return true;
if (joysticks.find(event.jhat.which) == joysticks.end())
return true;
break;
case SDL_JOYAXISMOTION:
if (p->state != AwaitingInput)
return true;
if (joysticks.find(event.jaxis.which) == joysticks.end())
return true;
break;
case SDL_MOUSEBUTTONDOWN:

View file

@ -24,6 +24,9 @@
#include <stdint.h>
#include <map>
#include <SDL_joystick.h>
struct SettingsMenuPrivate;
struct RGSSThreadData;
union SDL_Event;
@ -35,7 +38,8 @@ public:
~SettingsMenu();
/* Returns true if the event was consumed */
bool onEvent(const SDL_Event &event);
bool onEvent(const SDL_Event &event,
const std::map<int, SDL_Joystick*> &joysticks);
void raise();
bool destroyReq() const;