Merge pull request #4515 from bollu/improvedTilControls

Redid the tilt controls in a nicer way
This commit is contained in:
Henrik Rydgård 2013-12-22 02:13:28 -08:00
commit b0bfda6333
13 changed files with 329 additions and 18 deletions

View file

@ -1327,6 +1327,7 @@ set(NativeAppSource
UI/GameScreen.cpp
UI/GameSettingsScreen.cpp
UI/TiltAnalogSettingsScreen.cpp
UI/TiltEventProcessor.cpp
UI/TouchControlLayoutScreen.cpp
UI/TouchControlVisibilityScreen.cpp
UI/GamepadEmu.cpp

View file

@ -254,15 +254,14 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) {
#endif
// control->Get("KeyMapping",iMappingMap);
#ifdef USING_GLES2
control->Get("AccelerometerToAnalogHoriz", &bAccelerometerToAnalogHoriz, false);
control->Get("TiltBaseX", &fTiltBaseX, 0);
control->Get("TiltBaseY", &fTiltBaseY, 0);
control->Get("InvertTiltX", &bInvertTiltX, false);
control->Get("InvertTiltY", &bInvertTiltY, true);
control->Get("TiltSensitivityX", &iTiltSensitivityX, 100);
control->Get("TiltSensitivityY", &iTiltSensitivityY, 100);
control->Get("DeadzoneRadius", &fDeadzoneRadius, 0.35);
control->Get("DeadzoneRadius", &fDeadzoneRadius, 0.2);
control->Get("TiltInputType", &iTiltInputType, 0);
#endif
control->Get("DisableDpadDiagonals", &bDisableDpadDiagonals, false);
@ -539,7 +538,6 @@ void Config::Save() {
control->Set("ShowTouchDpad", bShowTouchDpad);
#ifdef USING_GLES2
control->Set("AccelerometerToAnalogHoriz", bAccelerometerToAnalogHoriz);
control->Set("TiltBaseX", fTiltBaseX);
control->Set("TiltBaseY", fTiltBaseY);
control->Set("InvertTiltX", bInvertTiltX);
@ -547,6 +545,7 @@ void Config::Save() {
control->Set("TiltSensitivityX", iTiltSensitivityX);
control->Set("TiltSensitivityY", iTiltSensitivityY);
control->Set("DeadzoneRadius", fDeadzoneRadius);
control->Set("TiltInputType", iTiltInputType);
#endif
control->Set("DisableDpadDiagonals", bDisableDpadDiagonals);
control->Set("TouchButtonStyle", iTouchButtonStyle);

View file

@ -130,7 +130,6 @@ public:
bool bShowDebuggerOnLoad;
int iShowFPSCounter;
bool bShowDebugStats;
bool bAccelerometerToAnalogHoriz;
//Analog stick tilting
//the base x and y tilt. this inclination is treated as (0,0) and the tilt input
@ -144,6 +143,9 @@ public:
int iTiltSensitivityY;
//the deadzone radius of the tilt
float fDeadzoneRadius;
//type of tilt input currently selected: Defined in TiltEventProcessor.h
//0 - no tilt, 1 - analog stick, 2 - D-Pad, 3 - Action Buttons (Tri, Cross, Square, Circle)
int iTiltInputType;
// The three tabs.
bool bGridView1;

View file

@ -83,6 +83,7 @@ SOURCES += $$P/UI/*Screen.cpp \
$$P/UI/GamepadEmu.cpp \
$$P/UI/GameInfoCache.cpp \
$$P/UI/OnScreenDisplay.cpp \
$$P/UI/TiltEventProcessor.cpp \
$$P/UI/UIShader.cpp \
$$P/UI/ui_atlas_lowmem.cpp \
$$P/android/jni/TestRunner.cpp

View file

@ -506,6 +506,7 @@ void EmuScreen::update(InputState &input) {
// Apply tilt to left stick
// TODO: Make into an axis
#ifdef USING_GLES2
/*
if (g_Config.bAccelerometerToAnalogHoriz) {
// Get the "base" coordinate system which is setup by the calibration system
float base_x = g_Config.fTiltBaseX;
@ -538,6 +539,7 @@ void EmuScreen::update(InputState &input) {
leftstick_y += clamp1(delta_y);
__CtrlSetAnalogY(clamp1(leftstick_y), CTRL_STICK_LEFT);
}
*/
#endif
// Make sure fpsLimit starts at 0

View file

@ -33,6 +33,7 @@
#include "UI/TouchControlLayoutScreen.h"
#include "UI/TouchControlVisibilityScreen.h"
#include "UI/TiltAnalogSettingsScreen.h"
#include "UI/TiltEventProcessor.h"
#include "Core/Config.h"
#include "Core/Host.h"
@ -233,10 +234,12 @@ void GameSettingsScreen::CreateViews() {
#if defined(USING_GLES2)
controlsSettings->Add(new CheckBox(&g_Config.bHapticFeedback, c->T("HapticFeedback", "Haptic Feedback (vibration)")));
controlsSettings->Add(new CheckBox(&g_Config.bAccelerometerToAnalogHoriz, c->T("Tilt", "Tilt to Analog (horizontal)")));
Choice *tiltAnalog = controlsSettings->Add(new Choice(c->T("Customize tilt")));
tiltAnalog->OnClick.Handle(this, &GameSettingsScreen::OnTiltAnalogSettings);
tiltAnalog->SetEnabledPtr(&g_Config.bAccelerometerToAnalogHoriz);
static const char *tiltTypes[] = { "None (Disabled)", "Analog Stick", "D-PAD", "PSP Action Buttons"};
controlsSettings->Add(new PopupMultiChoice(&g_Config.iTiltInputType, c->T("Tilt Input Type"), tiltTypes, 0, ARRAY_SIZE(tiltTypes), c, screenManager()))->OnClick.Handle(this, &GameSettingsScreen::OnTiltTypeChange);
Choice *customizeTilt = controlsSettings->Add(new Choice(c->T("Customize tilt")));
customizeTilt->OnClick.Handle(this, &GameSettingsScreen::OnTiltCuztomize);
customizeTilt->SetEnabledPtr((bool *)&g_Config.iTiltInputType); //<- dirty int-to-bool cast
#endif
controlsSettings->Add(new ItemHeader(c->T("OnScreen", "On-Screen Touch Controls")));
controlsSettings->Add(new CheckBox(&g_Config.bShowTouchControls, c->T("OnScreen", "On-Screen Touch Controls")));
@ -501,7 +504,14 @@ UI::EventReturn GameSettingsScreen::OnTouchControlLayout(UI::EventParams &e) {
return UI::EVENT_DONE;
};
UI::EventReturn GameSettingsScreen::OnTiltAnalogSettings(UI::EventParams &e){
//when the tilt event type is modified, we need to reset all tilt settings.
//refer to the ResetTiltEvents() function for a detailed explanation.
UI::EventReturn GameSettingsScreen::OnTiltTypeChange(UI::EventParams &e){
TiltEventProcessor::ResetTiltEvents();
return UI::EVENT_DONE;
};
UI::EventReturn GameSettingsScreen::OnTiltCuztomize(UI::EventParams &e){
screenManager()->push(new TiltAnalogSettingsScreen());
return UI::EVENT_DONE;
};

View file

@ -54,7 +54,8 @@ private:
UI::EventReturn OnTouchControlLayout(UI::EventParams &e);
UI::EventReturn OnDumpNextFrameToLog(UI::EventParams &e);
UI::EventReturn OnReloadCheats(UI::EventParams &e);
UI::EventReturn OnTiltAnalogSettings(UI::EventParams &e);
UI::EventReturn OnTiltTypeChange(UI::EventParams &e);
UI::EventReturn OnTiltCuztomize(UI::EventParams &e);
// Global settings handlers
UI::EventReturn OnLanguage(UI::EventParams &e);

View file

@ -73,6 +73,7 @@
#include "UI/OnScreenDisplay.h"
#include "UI/MiscScreens.h"
#include "UI/TiltEventProcessor.h"
// The new UI framework, for initialization
@ -669,16 +670,90 @@ void NativeKey(const KeyInput &key) {
}
void NativeAxis(const AxisInput &key) {
// ILOG("Axis id: %i value: %f", (int)key.axisId, key.value);
if (key.axisId >= JOYSTICK_AXIS_ACCELEROMETER_X && key.axisId <= JOYSTICK_AXIS_ACCELEROMETER_Z) {
// Disable accelerometer as an axis for now.
using namespace TiltEventProcessor;
//only handle tilt events if tilt is enabled.
if (g_Config.iTiltInputType == TILT_NULL){
//if tilt events are disabled, then run it through the usual way.
if (screenManager) {
screenManager->axis(key);
}
return;
}
if (isOuya && key.axisId >= JOYSTICK_AXIS_OUYA_UNKNOWN1 && key.axisId <= JOYSTICK_AXIS_OUYA_UNKNOWN4) {
return;
//create the base coordinate tilt system from the calibration data.
//This is static for no particular reason, can be un-static'ed
static Tilt baseTilt;
baseTilt.x_ = g_Config.fTiltBaseX; baseTilt.y_ = g_Config.fTiltBaseY;
//figure out what the current tilt orientation is by checking the axis event
//This is static, since we need to remember where we last were (in terms of orientation)
static Tilt currentTilt;
switch (key.axisId) {
case JOYSTICK_AXIS_ACCELEROMETER_X:
//x and y are flipped due to landscape orientation. The events are
//sent with respect to the portrait coordinate system, while we
//take all events in landscape.
//see [http://developer.android.com/guide/topics/sensors/sensors_overview.html] for details
currentTilt.y_ = key.value;
break;
case JOYSTICK_AXIS_ACCELEROMETER_Y:
currentTilt.x_ = key.value;
break;
case JOYSTICK_AXIS_ACCELEROMETER_Z:
//don't handle this now as only landscape is enabled.
//TODO: make this generic.
return;
case JOYSTICK_AXIS_OUYA_UNKNOWN1:
case JOYSTICK_AXIS_OUYA_UNKNOWN2:
case JOYSTICK_AXIS_OUYA_UNKNOWN3:
case JOYSTICK_AXIS_OUYA_UNKNOWN4:
//Don't know how to handle these. Someone should figure it out.
//Does the Ouya even have an accelerometer / gyro? I can't find any reference to these
//in the Ouya docs...
return;
default:
return;
}
if (screenManager)
screenManager->axis(key);
//figure out the sensitivity of the tilt. (sensitivity is originally 0 - 100)
//We divide by 50, so that the rest of the 50 units can be used to overshoot the
//target. If you want control, you'd keep the sensitivity ~50.
//For games that don't need much control but need fast reactions,
//then a value of 70-80 is the way to go.
float xSensitivity = g_Config.iTiltSensitivityX / 50.0;
float ySensitivity = g_Config.iTiltSensitivityY / 50.0;
//now transform out current tilt to the calibrated coordinate system
Tilt trueTilt = GenTilt(baseTilt, currentTilt, g_Config.bInvertTiltX, g_Config.bInvertTiltY, g_Config.fDeadzoneRadius, xSensitivity, ySensitivity);
//now send the appropriate tilt event
switch (g_Config.iTiltInputType) {
case TILT_ANALOG:
GenerateAnalogStickEvent(trueTilt);
break;
case TILT_DPAD:
GenerateDPadEvent(trueTilt);
break;
case TILT_ACTION_BUTTON:
GenerateActionButtonEvent(trueTilt);
break;
}
}
void NativeMessageReceived(const char *message, const char *value) {

170
UI/TiltEventProcessor.cpp Normal file
View file

@ -0,0 +1,170 @@
#define _USE_MATH_DEFINES
#include <cmath>
#include "TiltEventProcessor.h"
#include "Core/HLE/sceCtrl.h"
#include "math.h"
#include "native/base/logging.h"
using namespace TiltEventProcessor;
//deadzone is normalized - 0 to 1
//sensitivity controls how fast the deadzone reaches max value
inline float tiltInputCurve (float x, float deadzone, float sensitivity) {
const float factor = sensitivity * 1.0f / (1.0f - deadzone);
if (x > deadzone) {
return (x - deadzone) * factor * factor;
} else if (x < -deadzone) {
return (x + deadzone) * factor * factor;
} else {
return 0.0f;
}
}
//dampen the tilt according to the given deadzone amount.
inline Tilt dampTilt(const Tilt &tilt, float deadzone, float xSensitivity, float ySensitivity) {
//multiply sensitivity by 2 so that "overshoot" is possible. I personally prefer a
//sensitivity >1 for kingdom hearts and < 1 for Gods Eater. so yes, overshoot is nice
//to have.
return Tilt(tiltInputCurve(tilt.x_, deadzone, 2.0 * xSensitivity), tiltInputCurve(tilt.y_, deadzone, 2.0 * ySensitivity));
}
inline float clamp(float f) {
if (f > 1.0f) return 1.0f;
if (f < -1.0f) return -1.0f;
return f;
}
Tilt TiltEventProcessor::NormalizeTilt(const Tilt &tilt){
//setup the maximum values of the accelerometer manually per-platform
//TODO: less hacky way of doing this?
#ifdef ANDROID
//I obtained these values EXPERIMENTALLY. They may differ device to device.
//needs more testing. I'll be testing it on the Nexus 4 once I get mine back.
float maxX = 10, maxY = 8;
#else
float maxX = 1.0, maxY = 1.0;
#endif
return Tilt(tilt.x_ / maxX, tilt.y_ / maxY);
};
Tilt TiltEventProcessor::GenTilt(const Tilt &baseTilt, const Tilt &currentTilt, bool invertX, bool invertY, float deadzone, float xSensitivity, float ySensitivity) {
//first convert to the correct coordinate system
Tilt transformedTilt(currentTilt.x_ - baseTilt.x_, currentTilt.y_ - baseTilt.y_);
//invert x and y axes if needed
if (invertX) {
transformedTilt.x_ *= -1.0;
}
if (invertY) {
transformedTilt.y_ *= -1.0;
}
//next, normalize the tilt values
transformedTilt = NormalizeTilt(transformedTilt);
//finally, dampen the tilt according to our curve.
return dampTilt(transformedTilt, deadzone, xSensitivity, ySensitivity);
};
void TiltEventProcessor::GenerateAnalogStickEvent(const Tilt &tilt) {
__CtrlSetAnalogX(clamp(tilt.x_), CTRL_STICK_LEFT);
__CtrlSetAnalogY(clamp(tilt.y_), CTRL_STICK_LEFT);
};
void TiltEventProcessor::GenerateDPadEvent(const Tilt &tilt) {
static const int dir[4] = {CTRL_RIGHT, CTRL_DOWN, CTRL_LEFT, CTRL_UP};
if (tilt.x_ == 0) {
__CtrlButtonUp(CTRL_RIGHT);
__CtrlButtonUp(CTRL_LEFT);
}
if (tilt.y_ == 0) {
__CtrlButtonUp(CTRL_UP);
__CtrlButtonUp(CTRL_DOWN);
}
if (tilt.x_ == 0 && tilt.y_ == 0) {
return;
}
int ctrlMask = 0;
int direction = (int)(floorf((atan2f(tilt.y_, tilt.x_) / (2 * M_PI) * 8) + 0.5f)) & 7;
switch (direction) {
case 0: ctrlMask |= CTRL_RIGHT; break;
case 1: ctrlMask |= CTRL_RIGHT | CTRL_DOWN; break;
case 2: ctrlMask |= CTRL_DOWN; break;
case 3: ctrlMask |= CTRL_DOWN | CTRL_LEFT; break;
case 4: ctrlMask |= CTRL_LEFT; break;
case 5: ctrlMask |= CTRL_UP | CTRL_LEFT; break;
case 6: ctrlMask |= CTRL_UP; break;
case 7: ctrlMask |= CTRL_UP | CTRL_RIGHT; break;
}
for (int i = 0; i < 4; i++) {
if (ctrlMask & dir[i]) {
__CtrlButtonDown(dir[i]);
}
}
};
void TiltEventProcessor::GenerateActionButtonEvent(const Tilt &tilt) {
static const int buttons[4] = {CTRL_CIRCLE, CTRL_CROSS, CTRL_SQUARE, CTRL_TRIANGLE};
if (tilt.x_ == 0) {
__CtrlButtonUp(CTRL_SQUARE);
__CtrlButtonUp(CTRL_CIRCLE);
}
if (tilt.y_ == 0) {
__CtrlButtonUp(CTRL_TRIANGLE);
__CtrlButtonUp(CTRL_CROSS);
}
if (tilt.x_ == 0 && tilt.y_ == 0) {
return;
}
int direction = (int)(floorf((atan2f(tilt.y_, tilt.x_) / (2 * M_PI) * 4) + 0.5f)) & 3;
__CtrlButtonDown(buttons[direction]);
};
void TiltEventProcessor::ResetTiltEvents() {
//this is ugly, but it's needed since the entire tilt system is
//stateless. So, when a tilt option is changed, we have to reset
//the tilt.
//this scenario will take place without this:
//1) tilt is Analog based. the device is tilted. Analog controller is being moved
//2) user changes mode to D-Pad and goes back to game
//3) the event handling loop sends all events to D-Pad now.
// The analog controller doesn't know that the type of tilt input has changed.
// It keeps sending tilt events for the analog.
//D-Pad
__CtrlButtonUp(CTRL_RIGHT);
__CtrlButtonUp(CTRL_LEFT);
__CtrlButtonUp(CTRL_UP);
__CtrlButtonUp(CTRL_DOWN);
//action buttons
__CtrlButtonUp(CTRL_SQUARE);
__CtrlButtonUp(CTRL_CIRCLE);
__CtrlButtonUp(CTRL_TRIANGLE);
__CtrlButtonUp(CTRL_CROSS);
//analog
__CtrlSetAnalogX(0.0f, CTRL_STICK_LEFT);
__CtrlSetAnalogY(0.0f, CTRL_STICK_LEFT);
};

41
UI/TiltEventProcessor.h Normal file
View file

@ -0,0 +1,41 @@
namespace TiltEventProcessor {
enum TiltTypes{
TILT_NULL = 0,
TILT_ANALOG,
TILT_DPAD,
TILT_ACTION_BUTTON
};
//Represents a generic Tilt event
struct Tilt{
float x_, y_;
Tilt () : x_(0), y_(0) {};
Tilt (const float x, const float y) : x_(x), y_(y){}
};
Tilt NormalizeTilt(const Tilt &tilt);
//generates a tilt in the correct coordinate system based on
//calibration. BaseTilt is the "base" / "zero" tilt. currentTilt is the
//sensor tilt reading at this moment.
//NOTE- both base and current tilt *MUST BE NORMALIZED* by calling the NormalizeTilt() function.
Tilt GenTilt(const Tilt &baseTilt, const Tilt &currentTilt, bool invertX, bool invertY, float deadzone, float xSensitivity, float ySensitivity);
//the next 3 functions generate tilt events given the current Tilt amount,
//and the deadzone radius.
void GenerateAnalogStickEvent(const Tilt &tilt);
void GenerateDPadEvent(const Tilt &tilt);
void GenerateActionButtonEvent(const Tilt &tilt);
void ResetTiltEvents();
}

View file

@ -33,6 +33,7 @@
<ClCompile Include="OnScreenDisplay.cpp" />
<ClCompile Include="Store.cpp" />
<ClCompile Include="TiltAnalogSettingsScreen.cpp" />
<ClCompile Include="TiltEventProcessor.cpp" />
<ClCompile Include="TouchControlLayoutScreen.cpp" />
<ClCompile Include="TouchControlVisibilityScreen.cpp" />
<ClCompile Include="InstallZipScreen.cpp" />
@ -53,6 +54,7 @@
<ClInclude Include="OnScreenDisplay.h" />
<ClInclude Include="Store.h" />
<ClInclude Include="TiltAnalogSettingsScreen.h" />
<ClInclude Include="TiltEventProcessor.h" />
<ClInclude Include="TouchControlLayoutScreen.h" />
<ClInclude Include="TouchControlVisibilityScreen.h" />
<ClInclude Include="InstallZipScreen.h" />

View file

@ -40,6 +40,9 @@
<ClCompile Include="TiltAnalogSettingsScreen.cpp">
<Filter>Screens</Filter>
</ClCompile>
<ClCompile Include="TiltEventProcessor.cpp">
<Filter>Screens</Filter>
</ClCompile>
<ClCompile Include="InstallZipScreen.cpp">
<Filter>Screens</Filter>
</ClCompile>
@ -84,6 +87,9 @@
<ClInclude Include="TiltAnalogSettingsScreen.h">
<Filter>Screens</Filter>
</ClInclude>
<ClInclude Include="TiltEventProcessor.h">
<Filter>Screens</Filter>
</ClInclude>
<ClInclude Include="InstallZipScreen.h">
<Filter>Screens</Filter>
</ClInclude>

View file

@ -281,6 +281,7 @@ LOCAL_SRC_FILES := \
$(SRC)/UI/ControlMappingScreen.cpp \
$(SRC)/UI/GameSettingsScreen.cpp \
$(SRC)/UI/TiltAnalogSettingsScreen.cpp \
$(SRC)/UI/TiltEventProcessor.cpp \
$(SRC)/UI/TouchControlLayoutScreen.cpp \
$(SRC)/UI/TouchControlVisibilityScreen.cpp \
$(SRC)/UI/CwCheatScreen.cpp \