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; value = 0.0f;
} }
} }
Ebitenmobileview.onGamepadAxesChanged(event.getDeviceId(), axis, value); Ebitenmobileview.onGamepadAxesOrHatsChanged(event.getDeviceId(), axis, value);
} }
return true; return true;
} }
@ -562,20 +562,26 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
} }
boolean[] keyExistences = inputDevice.hasKeys(gamepadButtons); boolean[] keyExistences = inputDevice.hasKeys(gamepadButtons);
int buttonNum = gamepadButtons.length - 1; int nbuttons = 0;
for (int i = gamepadButtons.length - 1; i >= 0; i--) { for (int i = 0; i < gamepadButtons.length; i++) {
if (keyExistences[i]) { if (!keyExistences[i]) {
break; break;
} }
buttonNum--; nbuttons++;
} }
int axisNum = axes.length - 1; int naxes = 0;
for (int i = axes.length - 1; i >= 0; i--) { int nhats2 = 0;
if (inputDevice.getMotionRange(axes[i], InputDevice.SOURCE_JOYSTICK) != null) { for (int i = 0; i < axes.length; i++) {
InputDevice.MotionRange range = inputDevice.getMotionRange(axes[i], InputDevice.SOURCE_JOYSTICK);
if (range == null) {
break; break;
} }
axisNum--; if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
nhats2++;
} else {
naxes++;
}
} }
String descriptor = inputDevice.getDescriptor(); String descriptor = inputDevice.getDescriptor();
@ -586,7 +592,7 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
int buttonMask = getButtonMask(inputDevice); int buttonMask = getButtonMask(inputDevice);
int axisMask = getAxisMask(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: // 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 { func HasStandardLayoutMapping(id string) bool {
mappingsM.RLock() mappingsM.RLock()
defer mappingsM.RUnlock() defer mappingsM.RUnlock()
if _, ok := gamepadButtonMappings[id]; ok { return buttonMappings(id) != nil || axisMappings(id) != nil
return true
}
if _, ok := gamepadAxisMappings[id]; ok {
return true
}
return false
} }
type GamepadState interface { type GamepadState interface {
@ -358,8 +382,8 @@ func AxisValue(id string, axis driver.StandardGamepadAxis, state GamepadState) f
mappingsM.RLock() mappingsM.RLock()
defer mappingsM.RUnlock() defer mappingsM.RUnlock()
mappings, ok := gamepadAxisMappings[id] mappings := axisMappings(id)
if !ok { if mappings == nil {
return 0 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 { func buttonValue(id string, button driver.StandardGamepadButton, state GamepadState) float64 {
mappings, ok := gamepadButtonMappings[id] mappings := buttonMappings(id)
if !ok { if mappings == nil {
return 0 return 0
} }
@ -476,7 +500,7 @@ func Update(mapping []byte) (bool, error) {
} }
// TODO: Implement this (#1557) // 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. // Note: NOT returning an error, as mappings also do not matter right now.
return false, nil return false, nil
} }
@ -501,3 +525,202 @@ func Update(mapping []byte) (bool, error) {
return true, nil 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" "time"
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
) )
type Input struct { 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 { 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 return false
} }
func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool { 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 return false
} }
func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 { 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 return 0
} }
func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 { 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 return 0
} }
@ -221,3 +254,19 @@ func (i *Input) update(keys map[driver.Key]struct{}, runes []rune, touches []Tou
func (i *Input) resetForFrame() { func (i *Input) resetForFrame() {
i.runes = nil 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 ButtonNum int
Axes [32]float32 Axes [32]float32
AxisNum int AxisNum int
Hats [16]int
HatNum int
} }
func (u *UserInterface) UpdateInput(keys map[driver.Key]struct{}, runes []rune, touches []Touch) { func (u *UserInterface) UpdateInput(keys map[driver.Key]struct{}, runes []rune, touches []Touch) {

View File

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