internal/gamepaddb: implement the mappings for Android

Updates #1557
This commit is contained in:
Hajime Hoshi 2022-01-04 23:14:15 +09:00
parent 1b498a03cc
commit 4106fb15fe
6 changed files with 380 additions and 59 deletions

View File

@ -537,7 +537,7 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
value = 0.0f;
}
}
Ebitenmobileview.onGamepadAxesChanged(event.getDeviceId(), axis, value);
Ebitenmobileview.onGamepadAxesOrHatsChanged(event.getDeviceId(), axis, value);
}
return true;
}
@ -562,20 +562,26 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
}
boolean[] keyExistences = inputDevice.hasKeys(gamepadButtons);
int buttonNum = gamepadButtons.length - 1;
for (int i = gamepadButtons.length - 1; i >= 0; i--) {
if (keyExistences[i]) {
int nbuttons = 0;
for (int i = 0; i < gamepadButtons.length; i++) {
if (!keyExistences[i]) {
break;
}
buttonNum--;
nbuttons++;
}
int axisNum = axes.length - 1;
for (int i = axes.length - 1; i >= 0; i--) {
if (inputDevice.getMotionRange(axes[i], InputDevice.SOURCE_JOYSTICK) != null) {
int naxes = 0;
int nhats2 = 0;
for (int i = 0; i < axes.length; i++) {
InputDevice.MotionRange range = inputDevice.getMotionRange(axes[i], InputDevice.SOURCE_JOYSTICK);
if (range == null) {
break;
}
axisNum--;
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
nhats2++;
} else {
naxes++;
}
}
String descriptor = inputDevice.getDescriptor();
@ -586,7 +592,7 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
int buttonMask = getButtonMask(inputDevice);
int axisMask = getAxisMask(inputDevice);
Ebitenmobileview.onGamepadAdded(deviceId, inputDevice.getName(), buttonNum, axisNum, descriptor, vendorId, productId, buttonMask, axisMask);
Ebitenmobileview.onGamepadAdded(deviceId, inputDevice.getName(), nbuttons, naxes, nhats2/2, descriptor, vendorId, productId, buttonMask, axisMask);
}
// The implementation is copied from SDL:

File diff suppressed because one or more lines are too long

View File

@ -335,17 +335,41 @@ func toStandardGamepadAxis(str string) (driver.StandardGamepadAxis, bool) {
}
}
func buttonMappings(id string) map[driver.StandardGamepadButton]*mapping {
if m, ok := gamepadButtonMappings[id]; ok {
return m
}
if currentPlatform == platformAndroid {
// If the gamepad is not an HID API, use the default mapping on Android.
if id[14] != 'h' {
if addAndroidDefaultMappings(id) {
return gamepadButtonMappings[id]
}
}
}
return nil
}
func axisMappings(id string) map[driver.StandardGamepadAxis]*mapping {
if m, ok := gamepadAxisMappings[id]; ok {
return m
}
if currentPlatform == platformAndroid {
// If the gamepad is not an HID API, use the default mapping on Android.
if id[14] != 'h' {
if addAndroidDefaultMappings(id) {
return gamepadAxisMappings[id]
}
}
}
return nil
}
func HasStandardLayoutMapping(id string) bool {
mappingsM.RLock()
defer mappingsM.RUnlock()
if _, ok := gamepadButtonMappings[id]; ok {
return true
}
if _, ok := gamepadAxisMappings[id]; ok {
return true
}
return false
return buttonMappings(id) != nil || axisMappings(id) != nil
}
type GamepadState interface {
@ -358,8 +382,8 @@ func AxisValue(id string, axis driver.StandardGamepadAxis, state GamepadState) f
mappingsM.RLock()
defer mappingsM.RUnlock()
mappings, ok := gamepadAxisMappings[id]
if !ok {
mappings := axisMappings(id)
if mappings == nil {
return 0
}
@ -402,8 +426,8 @@ func ButtonValue(id string, button driver.StandardGamepadButton, state GamepadSt
}
func buttonValue(id string, button driver.StandardGamepadButton, state GamepadState) float64 {
mappings, ok := gamepadButtonMappings[id]
if !ok {
mappings := buttonMappings(id)
if mappings == nil {
return 0
}
@ -476,7 +500,7 @@ func Update(mapping []byte) (bool, error) {
}
// TODO: Implement this (#1557)
if currentPlatform == platformAndroid || currentPlatform == platformIOS {
if currentPlatform == platformIOS {
// Note: NOT returning an error, as mappings also do not matter right now.
return false, nil
}
@ -501,3 +525,202 @@ func Update(mapping []byte) (bool, error) {
return true, nil
}
func addAndroidDefaultMappings(id string) bool {
// See https://github.com/libsdl-org/SDL/blob/120c76c84bbce4c1bfed4e9eb74e10678bd83120/include/SDL_gamecontroller.h#L655-L680
const (
SDLControllerButtonA = 0
SDLControllerButtonB = 1
SDLControllerButtonX = 2
SDLControllerButtonY = 3
SDLControllerButtonBack = 4
SDLControllerButtonGuide = 5
SDLControllerButtonStart = 6
SDLControllerButtonLeftStick = 7
SDLControllerButtonRightStick = 8
SDLControllerButtonLeftShoulder = 9
SDLControllerButtonRightShoulder = 10
SDLControllerButtonDpadUp = 11
SDLControllerButtonDpadDown = 12
SDLControllerButtonDpadLeft = 13
SDLControllerButtonDpadRight = 14
)
// See https://github.com/libsdl-org/SDL/blob/120c76c84bbce4c1bfed4e9eb74e10678bd83120/include/SDL_gamecontroller.h#L550-L560
const (
SDLControllerAxisLeftX = 0
SDLControllerAxisLeftY = 1
SDLControllerAxisRightX = 2
SDLControllerAxisRightY = 3
SDLControllerAxisTriggerLeft = 4
SDLControllerAxisTriggerRight = 5
)
// See https://github.com/libsdl-org/SDL/blob/120c76c84bbce4c1bfed4e9eb74e10678bd83120/src/joystick/SDL_gamecontroller.c#L468-L568
const faceButtonMask = ((1 << SDLControllerButtonA) |
(1 << SDLControllerButtonB) |
(1 << SDLControllerButtonX) |
(1 << SDLControllerButtonY))
buttonMask := uint16(id[12]) | (uint16(id[13]) << 8)
axisMask := uint16(id[14]) | (uint16(id[15]) << 8)
if buttonMask == 0 && axisMask == 0 {
return false
}
if buttonMask&faceButtonMask == 0 {
return false
}
gamepadButtonMappings[id] = map[driver.StandardGamepadButton]*mapping{}
if buttonMask&(1<<SDLControllerButtonA) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonRightBottom] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonA,
}
}
if buttonMask&(1<<SDLControllerButtonB) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonRightRight] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonB,
}
} else {
// Use the back button as "B" for easy UI navigation with TV remotes.
gamepadButtonMappings[id][driver.StandardGamepadButtonRightRight] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonBack,
}
buttonMask &= ^(uint16(1) << SDLControllerButtonBack)
}
if buttonMask&(1<<SDLControllerButtonX) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonRightLeft] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonX,
}
}
if buttonMask&(1<<SDLControllerButtonY) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonRightTop] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonY,
}
}
if buttonMask&(1<<SDLControllerButtonBack) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonCenterLeft] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonBack,
}
}
if buttonMask&(1<<SDLControllerButtonGuide) != 0 {
// TODO: If SDKVersion >= 30, add this code:
//
// gamepadButtonMappings[id][driver.StandardGamepadButtonCenterCenter] = &mapping{
// Type: mappingTypeButton,
// Index: SDLControllerButtonGuide,
// }
}
if buttonMask&(1<<SDLControllerButtonStart) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonCenterRight] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonStart,
}
}
if buttonMask&(1<<SDLControllerButtonLeftStick) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonLeftStick] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonLeftStick,
}
}
if buttonMask&(1<<SDLControllerButtonRightStick) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonRightStick] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonRightStick,
}
}
if buttonMask&(1<<SDLControllerButtonLeftShoulder) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonFrontTopLeft] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonLeftShoulder,
}
}
if buttonMask&(1<<SDLControllerButtonRightShoulder) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonFrontTopRight] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonRightShoulder,
}
}
if buttonMask&(1<<SDLControllerButtonDpadUp) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonLeftTop] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonDpadUp,
}
}
if buttonMask&(1<<SDLControllerButtonDpadDown) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonLeftBottom] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonDpadDown,
}
}
if buttonMask&(1<<SDLControllerButtonDpadLeft) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonLeftLeft] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonDpadLeft,
}
}
if buttonMask&(1<<SDLControllerButtonDpadRight) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonLeftRight] = &mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonDpadRight,
}
}
if axisMask&(1<<SDLControllerAxisLeftX) != 0 {
gamepadAxisMappings[id][driver.StandardGamepadAxisLeftStickHorizontal] = &mapping{
Type: mappingTypeAxis,
Index: SDLControllerAxisLeftX,
AxisScale: 1,
AxisOffset: 0,
}
}
if axisMask&(1<<SDLControllerAxisLeftY) != 0 {
gamepadAxisMappings[id][driver.StandardGamepadAxisLeftStickVertical] = &mapping{
Type: mappingTypeAxis,
Index: SDLControllerAxisLeftY,
AxisScale: 1,
AxisOffset: 0,
}
}
if axisMask&(1<<SDLControllerAxisRightX) != 0 {
gamepadAxisMappings[id][driver.StandardGamepadAxisRightStickHorizontal] = &mapping{
Type: mappingTypeAxis,
Index: SDLControllerAxisRightX,
AxisScale: 1,
AxisOffset: 0,
}
}
if axisMask&(1<<SDLControllerAxisRightY) != 0 {
gamepadAxisMappings[id][driver.StandardGamepadAxisRightStickVertical] = &mapping{
Type: mappingTypeAxis,
Index: SDLControllerAxisRightY,
AxisScale: 1,
AxisOffset: 0,
}
}
if axisMask&(1<<SDLControllerAxisTriggerLeft) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonFrontBottomLeft] = &mapping{
Type: mappingTypeAxis,
Index: SDLControllerAxisTriggerLeft,
AxisScale: 1,
AxisOffset: 0,
}
}
if axisMask&(1<<SDLControllerAxisTriggerRight) != 0 {
gamepadButtonMappings[id][driver.StandardGamepadButtonFrontBottomRight] = &mapping{
Type: mappingTypeAxis,
Index: SDLControllerAxisTriggerRight,
AxisScale: 1,
AxisOffset: 0,
}
}
return true
}

View File

@ -21,6 +21,7 @@ import (
"time"
"github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
type Input struct {
@ -130,22 +131,54 @@ func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.Gamepa
}
func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool {
// TODO: Implement this (#1557)
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.HasStandardLayoutMapping(g.SDLID)
}
return false
}
func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool {
// TODO: Implement this (#1557)
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.IsButtonPressed(g.SDLID, button, gamepadState{&g})
}
return false
}
func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 {
// TODO: Implement this (#1557)
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.ButtonValue(g.SDLID, button, gamepadState{&g})
}
return 0
}
func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 {
// TODO: Implement this (#1557)
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.AxisValue(g.SDLID, axis, gamepadState{&g})
}
return 0
}
@ -221,3 +254,19 @@ func (i *Input) update(keys map[driver.Key]struct{}, runes []rune, touches []Tou
func (i *Input) resetForFrame() {
i.runes = nil
}
type gamepadState struct {
g *Gamepad
}
func (s gamepadState) Axis(index int) float64 {
return float64(s.g.Axes[index])
}
func (s gamepadState) Button(index int) bool {
return s.g.Buttons[index]
}
func (s gamepadState) Hat(index int) int {
return s.g.Hats[index]
}

View File

@ -466,6 +466,8 @@ type Gamepad struct {
ButtonNum int
Axes [32]float32
AxisNum int
Hats [16]int
HatNum int
}
func (u *UserInterface) UpdateInput(keys map[driver.Key]struct{}, runes []rune, touches []Touch) {

View File

@ -17,6 +17,7 @@ package ebitenmobileview
import (
"encoding/hex"
"hash/crc32"
"math"
"unicode"
"github.com/hajimehoshi/ebiten/v2/internal/driver"
@ -149,31 +150,34 @@ var androidAxisIDToAxisID = map[int]int{
axisRx: 3,
axisRy: 4,
axisRz: 5,
axisHatX: 6,
axisHatY: 7,
axisLtrigger: 8,
axisRtrigger: 9,
axisThrottle: 10,
axisRudder: 11,
axisWheel: 12,
axisGas: 13,
axisBrake: 14,
axisGeneric1: 15,
axisGeneric2: 16,
axisGeneric3: 17,
axisGeneric4: 18,
axisGeneric5: 19,
axisGeneric6: 20,
axisGeneric7: 21,
axisGeneric8: 22,
axisGeneric9: 23,
axisGeneric10: 24,
axisGeneric11: 25,
axisGeneric12: 26,
axisGeneric13: 27,
axisGeneric14: 28,
axisGeneric15: 29,
axisGeneric16: 30,
axisLtrigger: 6,
axisRtrigger: 7,
axisThrottle: 8,
axisRudder: 9,
axisWheel: 10,
axisGas: 11,
axisBrake: 12,
axisGeneric1: 13,
axisGeneric2: 14,
axisGeneric3: 15,
axisGeneric4: 16,
axisGeneric5: 17,
axisGeneric6: 18,
axisGeneric7: 19,
axisGeneric8: 20,
axisGeneric9: 21,
axisGeneric10: 22,
axisGeneric11: 23,
axisGeneric12: 24,
axisGeneric13: 25,
axisGeneric14: 26,
axisGeneric15: 27,
axisGeneric16: 28,
}
var androidAxisIDToHatID2 = map[int]int{
axisHatX: 0,
axisHatY: 1,
}
var (
@ -269,22 +273,58 @@ func OnKeyUpOnAndroid(keyCode int, source int, deviceID int) {
}
}
func OnGamepadAxesChanged(deviceID int, axisID int, value float32) {
func OnGamepadAxesOrHatsChanged(deviceID int, axisID int, value float32) {
id := gamepadIDFromDeviceID(deviceID)
g := gamepadFromGamepadID(id)
if g == nil {
return
}
aid, ok := androidAxisIDToAxisID[axisID]
if !ok {
// Unexpected axis value.
if aid, ok := androidAxisIDToAxisID[axisID]; ok {
g.Axes[aid] = value
updateGamepads()
return
}
if hid2, ok := androidAxisIDToHatID2[axisID]; ok {
const (
hatUp = 1
hatRight = 2
hatDown = 4
hatLeft = 8
)
hid := hid2 / 2
v := g.Hats[hid]
if hid2%2 == 0 {
hatX := int(math.Round(float64(value)))
if hatX < 0 {
v |= hatLeft
v &= ^hatRight
} else if hatX > 0 {
v &= ^hatLeft
v |= hatRight
} else {
v &= ^(hatLeft | hatRight)
}
} else {
hatY := int(math.Round(float64(value)))
if hatY < 0 {
v |= hatUp
v &= ^hatDown
} else if hatY > 0 {
v &= ^hatUp
v |= hatDown
} else {
v &= ^(hatUp | hatDown)
}
}
g.Hats[hid] = v
updateGamepads()
return
}
g.Axes[aid] = value
updateGamepads()
}
func OnGamepadAdded(deviceID int, name string, buttonNum int, axisNum int, descriptor string, vendorID int, productID int, buttonMask int, axisMask int) {
func OnGamepadAdded(deviceID int, name string, buttonNum int, axisNum int, hatNum int, descriptor string, vendorID int, productID int, buttonMask int, axisMask int) {
// This emulates the implementation of Android_AddJoystick.
// https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/src/joystick/android/SDL_sysjoystick.c#L386
const SDL_HARDWARE_BUS_BLUETOOTH = 0x05
@ -317,6 +357,7 @@ func OnGamepadAdded(deviceID int, name string, buttonNum int, axisNum int, descr
Name: name,
ButtonNum: buttonNum,
AxisNum: axisNum,
HatNum: hatNum,
})
updateGamepads()
}