mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
internal/gamepad: port the implementation for Android
This commit is contained in:
parent
ee1b5e2044
commit
d0e8efca33
167
internal/gamepad/extern_android.go
Normal file
167
internal/gamepad/extern_android.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
// Copyright 2022 The Ebiten Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gamepad
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AndroidHatDirection int
|
||||||
|
|
||||||
|
const (
|
||||||
|
AndroidHatDirectionX AndroidHatDirection = iota
|
||||||
|
AndroidHatDirectionY
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddAndroidGamepad(androidDeviceID int, name, sdlID string, axisCount, buttonCount, hatCount int) {
|
||||||
|
theGamepads.addAndroidGamepad(androidDeviceID, name, sdlID, axisCount, buttonCount, hatCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveAndroidGamepad(androidDeviceID int) {
|
||||||
|
theGamepads.removeAndroidGamepad(androidDeviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateAndroidGamepadAxis(androidDeviceID int, axis int, value float64) {
|
||||||
|
theGamepads.updateAndroidGamepadAxis(androidDeviceID, axis, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateAndroidGamepadButton(androidDeviceID int, button driver.GamepadButton, pressed bool) {
|
||||||
|
theGamepads.updateAndroidGamepadButton(androidDeviceID, button, pressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateAndroidGamepadHat(androidDeviceID int, hat int, dir AndroidHatDirection, value int) {
|
||||||
|
theGamepads.updateAndroidGamepadHat(androidDeviceID, hat, dir, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gamepads) addAndroidGamepad(androidDeviceID int, name, sdlID string, axisCount, buttonCount, hatCount int) {
|
||||||
|
g.m.Lock()
|
||||||
|
defer g.m.Unlock()
|
||||||
|
|
||||||
|
gp := g.add(name, sdlID)
|
||||||
|
gp.androidDeviceID = androidDeviceID
|
||||||
|
gp.axisCount_ = axisCount
|
||||||
|
gp.buttonCount_ = buttonCount
|
||||||
|
gp.hatCount_ = hatCount
|
||||||
|
gp.axes = make([]float64, axisCount)
|
||||||
|
gp.buttons = make([]bool, buttonCount)
|
||||||
|
gp.hats = make([]int, hatCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gamepads) removeAndroidGamepad(androidDeviceID int) {
|
||||||
|
g.m.Lock()
|
||||||
|
defer g.m.Unlock()
|
||||||
|
|
||||||
|
g.remove(func(gamepad *Gamepad) bool {
|
||||||
|
return gamepad.androidDeviceID == androidDeviceID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gamepads) updateAndroidGamepadAxis(androidDeviceID int, axis int, value float64) {
|
||||||
|
g.m.Lock()
|
||||||
|
defer g.m.Unlock()
|
||||||
|
|
||||||
|
gp := g.find(func(gamepad *Gamepad) bool {
|
||||||
|
return gamepad.androidDeviceID == androidDeviceID
|
||||||
|
})
|
||||||
|
if gp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gp.updateAndroidGamepadAxis(axis, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gamepads) updateAndroidGamepadButton(androidDeviceID int, button driver.GamepadButton, pressed bool) {
|
||||||
|
g.m.Lock()
|
||||||
|
defer g.m.Unlock()
|
||||||
|
|
||||||
|
gp := g.find(func(gamepad *Gamepad) bool {
|
||||||
|
return gamepad.androidDeviceID == androidDeviceID
|
||||||
|
})
|
||||||
|
if gp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gp.updateAndroidGamepadButton(button, pressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gamepads) updateAndroidGamepadHat(androidDeviceID int, hat int, dir AndroidHatDirection, value int) {
|
||||||
|
g.m.Lock()
|
||||||
|
defer g.m.Unlock()
|
||||||
|
|
||||||
|
gp := g.find(func(gamepad *Gamepad) bool {
|
||||||
|
return gamepad.androidDeviceID == androidDeviceID
|
||||||
|
})
|
||||||
|
if gp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gp.updateAndroidGamepadHat(hat, dir, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gamepad) updateAndroidGamepadAxis(axis int, value float64) {
|
||||||
|
g.m.Lock()
|
||||||
|
defer g.m.Unlock()
|
||||||
|
|
||||||
|
if axis < 0 || axis >= g.axisCount_ {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.axes[axis] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gamepad) updateAndroidGamepadButton(button driver.GamepadButton, pressed bool) {
|
||||||
|
g.m.Lock()
|
||||||
|
defer g.m.Unlock()
|
||||||
|
|
||||||
|
if button < 0 || int(button) >= g.buttonCount_ {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.buttons[button] = pressed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gamepad) updateAndroidGamepadHat(hat int, dir AndroidHatDirection, value int) {
|
||||||
|
g.m.Lock()
|
||||||
|
defer g.m.Unlock()
|
||||||
|
|
||||||
|
if hat < 0 || hat >= g.hatCount_ {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v := g.hats[hat]
|
||||||
|
switch dir {
|
||||||
|
case AndroidHatDirectionX:
|
||||||
|
switch {
|
||||||
|
case value < 0:
|
||||||
|
v |= hatLeft
|
||||||
|
v &^= hatRight
|
||||||
|
case value > 0:
|
||||||
|
v &^= hatLeft
|
||||||
|
v |= hatRight
|
||||||
|
default:
|
||||||
|
v &^= (hatLeft | hatRight)
|
||||||
|
}
|
||||||
|
case AndroidHatDirectionY:
|
||||||
|
switch {
|
||||||
|
case value < 0:
|
||||||
|
v |= hatUp
|
||||||
|
v &^= hatDown
|
||||||
|
case value > 0:
|
||||||
|
v &^= hatUp
|
||||||
|
v |= hatDown
|
||||||
|
default:
|
||||||
|
v &^= (hatUp | hatDown)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("gamepad: invalid direction: %d", dir))
|
||||||
|
}
|
||||||
|
g.hats[hat] = v
|
||||||
|
}
|
91
internal/gamepad/gamepad_android.go
Normal file
91
internal/gamepad/gamepad_android.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2022 The Ebiten Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gamepad
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type nativeGamepads struct{}
|
||||||
|
|
||||||
|
func (*nativeGamepads) init(gamepads *gamepads) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*nativeGamepads) update(gamepads *gamepads) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type nativeGamepad struct {
|
||||||
|
androidDeviceID int
|
||||||
|
|
||||||
|
axisCount_ int
|
||||||
|
buttonCount_ int
|
||||||
|
hatCount_ int
|
||||||
|
|
||||||
|
axes []float64
|
||||||
|
buttons []bool
|
||||||
|
hats []int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*nativeGamepad) update(gamepad *gamepads) error {
|
||||||
|
// Do nothing. The state of gamepads are given via APIs in extern_android.go.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*nativeGamepad) hasOwnStandardLayoutMapping() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepad) axisCount() int {
|
||||||
|
return g.axisCount_
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepad) buttonCount() int {
|
||||||
|
return g.buttonCount_
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepad) hatCount() int {
|
||||||
|
return g.hatCount_
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepad) axisValue(axis int) float64 {
|
||||||
|
if axis < 0 || axis >= len(g.axes) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return g.axes[axis]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepad) isButtonPressed(button int) bool {
|
||||||
|
if button < 0 || button >= len(g.buttons) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return g.buttons[button]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*nativeGamepad) buttonValue(button int) float64 {
|
||||||
|
panic("gamepad: buttonValue is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepad) hatState(hat int) int {
|
||||||
|
if hat < 0 || hat >= len(g.hats) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return g.hats[hat]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepad) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||||
|
// TODO: Implement this (#1452)
|
||||||
|
}
|
@ -12,10 +12,10 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//go:build (!darwin || ios) && !js && (!linux || android) && !windows
|
//go:build (!darwin || ios) && !js && !linux && !windows
|
||||||
// +build !darwin ios
|
// +build !darwin ios
|
||||||
// +build !js
|
// +build !js
|
||||||
// +build !linux android
|
// +build !linux
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package gamepad
|
package gamepad
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//go:build android || ios
|
//go:build android
|
||||||
// +build android ios
|
// +build android
|
||||||
|
|
||||||
package mobile
|
package mobile
|
||||||
|
|
||||||
@ -21,186 +21,104 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Gamepad struct {
|
|
||||||
ID driver.GamepadID
|
|
||||||
SDLID string
|
|
||||||
Name string
|
|
||||||
Buttons [driver.GamepadButtonNum]bool
|
|
||||||
ButtonNum int
|
|
||||||
Axes [32]float32
|
|
||||||
AxisNum int
|
|
||||||
Hats [16]int
|
|
||||||
HatNum int
|
|
||||||
}
|
|
||||||
|
|
||||||
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]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {
|
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {
|
||||||
i.ui.m.RLock()
|
return gamepad.AppendGamepadIDs(gamepadIDs)
|
||||||
defer i.ui.m.RUnlock()
|
|
||||||
|
|
||||||
for _, g := range i.gamepads {
|
|
||||||
gamepadIDs = append(gamepadIDs, g.ID)
|
|
||||||
}
|
|
||||||
return gamepadIDs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) GamepadSDLID(id driver.GamepadID) string {
|
func (i *Input) GamepadSDLID(id driver.GamepadID) string {
|
||||||
i.ui.m.RLock()
|
g := gamepad.Get(id)
|
||||||
defer i.ui.m.RUnlock()
|
if g == nil {
|
||||||
|
return ""
|
||||||
for _, g := range i.gamepads {
|
|
||||||
if g.ID != id {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return g.SDLID
|
|
||||||
}
|
}
|
||||||
return ""
|
return g.SDLID()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) GamepadName(id driver.GamepadID) string {
|
func (i *Input) GamepadName(id driver.GamepadID) string {
|
||||||
i.ui.m.RLock()
|
g := gamepad.Get(id)
|
||||||
defer i.ui.m.RUnlock()
|
if g == nil {
|
||||||
|
return ""
|
||||||
for _, g := range i.gamepads {
|
|
||||||
if g.ID != id {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if name := gamepaddb.Name(g.SDLID); name != "" {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
return g.Name
|
|
||||||
}
|
}
|
||||||
return ""
|
return g.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
|
func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
|
||||||
i.ui.m.RLock()
|
g := gamepad.Get(id)
|
||||||
defer i.ui.m.RUnlock()
|
if g == nil {
|
||||||
|
return 0
|
||||||
for _, g := range i.gamepads {
|
|
||||||
if g.ID != id {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return g.AxisNum
|
|
||||||
}
|
}
|
||||||
return 0
|
return g.AxisCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 {
|
func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 {
|
||||||
i.ui.m.RLock()
|
g := gamepad.Get(id)
|
||||||
defer i.ui.m.RUnlock()
|
if g == nil {
|
||||||
|
return 0
|
||||||
for _, g := range i.gamepads {
|
|
||||||
if g.ID != id {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if g.AxisNum <= int(axis) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return float64(g.Axes[axis])
|
|
||||||
}
|
}
|
||||||
return 0
|
return g.Axis(axis)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
|
func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
|
||||||
i.ui.m.RLock()
|
g := gamepad.Get(id)
|
||||||
defer i.ui.m.RUnlock()
|
if g == nil {
|
||||||
|
return 0
|
||||||
for _, g := range i.gamepads {
|
|
||||||
if g.ID != id {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return g.ButtonNum
|
|
||||||
}
|
}
|
||||||
return 0
|
return g.ButtonCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
|
func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
|
||||||
i.ui.m.RLock()
|
g := gamepad.Get(id)
|
||||||
defer i.ui.m.RUnlock()
|
if g == nil {
|
||||||
|
return false
|
||||||
for _, g := range i.gamepads {
|
|
||||||
if g.ID != id {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if g.ButtonNum <= int(button) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return g.Buttons[button]
|
|
||||||
}
|
}
|
||||||
return false
|
return g.Button(int(button))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool {
|
func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool {
|
||||||
i.ui.m.RLock()
|
g := gamepad.Get(id)
|
||||||
defer i.ui.m.RUnlock()
|
if g == nil {
|
||||||
|
return false
|
||||||
for _, g := range i.gamepads {
|
|
||||||
if g.ID != id {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return gamepaddb.HasStandardLayoutMapping(g.SDLID)
|
|
||||||
}
|
}
|
||||||
return false
|
return g.IsStandardLayoutAvailable()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool {
|
func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool {
|
||||||
i.ui.m.RLock()
|
g := gamepad.Get(id)
|
||||||
defer i.ui.m.RUnlock()
|
if g == nil {
|
||||||
|
return false
|
||||||
for _, g := range i.gamepads {
|
|
||||||
if g.ID != id {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return gamepaddb.IsButtonPressed(g.SDLID, button, gamepadState{&g})
|
|
||||||
}
|
}
|
||||||
return false
|
return g.IsStandardButtonPressed(button)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 {
|
func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 {
|
||||||
i.ui.m.RLock()
|
g := gamepad.Get(id)
|
||||||
defer i.ui.m.RUnlock()
|
if g == nil {
|
||||||
|
return 0
|
||||||
for _, g := range i.gamepads {
|
|
||||||
if g.ID != id {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return gamepaddb.ButtonValue(g.SDLID, button, gamepadState{&g})
|
|
||||||
}
|
}
|
||||||
return 0
|
return g.StandardButtonValue(button)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 {
|
func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 {
|
||||||
i.ui.m.RLock()
|
g := gamepad.Get(id)
|
||||||
defer i.ui.m.RUnlock()
|
if g == nil {
|
||||||
|
return 0
|
||||||
for _, g := range i.gamepads {
|
|
||||||
if g.ID != id {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return gamepaddb.AxisValue(g.SDLID, axis, gamepadState{&g})
|
|
||||||
}
|
}
|
||||||
return 0
|
return g.StandardAxisValue(axis)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) VibrateGamepad(id driver.GamepadID, duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
func (i *Input) VibrateGamepad(id driver.GamepadID, duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||||
// TODO: Implement this (#1452)
|
g := gamepad.Get(id)
|
||||||
|
if g == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.Vibrate(duration, strongMagnitude, weakMagnitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this struct after porting the gamepad part of iOS to internal/gamepad.
|
||||||
|
type Gamepad struct{}
|
||||||
|
|
||||||
|
// TODO: Remove this function after porting the gamepad part of iOS to internal/gamepad.
|
||||||
|
func (i *Input) updateGamepads() {
|
||||||
}
|
}
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
// Copyright 2021 The Ebiten Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package mobile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UpdateGamepads updates the gamepad states.
|
|
||||||
// UpdateGamepads is called when the gamepad states are given from outside like Android.
|
|
||||||
func (u *UserInterface) UpdateGamepads(gamepads []Gamepad) {
|
|
||||||
u.input.updateGamepadsFromOutside(gamepads)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Input) updateGamepadsFromOutside(gamepads []Gamepad) {
|
|
||||||
i.gamepads = i.gamepads[:0]
|
|
||||||
i.gamepads = append(i.gamepads, gamepads...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Input) updateGamepads() {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
@ -22,11 +22,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Input struct {
|
type Input struct {
|
||||||
keys map[driver.Key]struct{}
|
keys map[driver.Key]struct{}
|
||||||
runes []rune
|
runes []rune
|
||||||
touches []Touch
|
touches []Touch
|
||||||
|
ui *UserInterface
|
||||||
|
|
||||||
|
// gamepads is used only on iOS.
|
||||||
gamepads []Gamepad
|
gamepads []Gamepad
|
||||||
ui *UserInterface
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Input) CursorPosition() (x, y int) {
|
func (i *Input) CursorPosition() (x, y int) {
|
||||||
|
206
internal/uidriver/mobile/inputgamepad_ios.go
Normal file
206
internal/uidriver/mobile/inputgamepad_ios.go
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
// Copyright 2022 The Ebiten Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build ios
|
||||||
|
// +build ios
|
||||||
|
|
||||||
|
package mobile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Gamepad struct {
|
||||||
|
ID driver.GamepadID
|
||||||
|
SDLID string
|
||||||
|
Name string
|
||||||
|
Buttons [driver.GamepadButtonNum]bool
|
||||||
|
ButtonNum int
|
||||||
|
Axes [32]float32
|
||||||
|
AxisNum int
|
||||||
|
Hats [16]int
|
||||||
|
HatNum int
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {
|
||||||
|
i.ui.m.RLock()
|
||||||
|
defer i.ui.m.RUnlock()
|
||||||
|
|
||||||
|
for _, g := range i.gamepads {
|
||||||
|
gamepadIDs = append(gamepadIDs, g.ID)
|
||||||
|
}
|
||||||
|
return gamepadIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) GamepadSDLID(id driver.GamepadID) string {
|
||||||
|
i.ui.m.RLock()
|
||||||
|
defer i.ui.m.RUnlock()
|
||||||
|
|
||||||
|
for _, g := range i.gamepads {
|
||||||
|
if g.ID != id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return g.SDLID
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) GamepadName(id driver.GamepadID) string {
|
||||||
|
i.ui.m.RLock()
|
||||||
|
defer i.ui.m.RUnlock()
|
||||||
|
|
||||||
|
for _, g := range i.gamepads {
|
||||||
|
if g.ID != id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if name := gamepaddb.Name(g.SDLID); name != "" {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return g.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
|
||||||
|
i.ui.m.RLock()
|
||||||
|
defer i.ui.m.RUnlock()
|
||||||
|
|
||||||
|
for _, g := range i.gamepads {
|
||||||
|
if g.ID != id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return g.AxisNum
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 {
|
||||||
|
i.ui.m.RLock()
|
||||||
|
defer i.ui.m.RUnlock()
|
||||||
|
|
||||||
|
for _, g := range i.gamepads {
|
||||||
|
if g.ID != id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if g.AxisNum <= int(axis) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return float64(g.Axes[axis])
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
|
||||||
|
i.ui.m.RLock()
|
||||||
|
defer i.ui.m.RUnlock()
|
||||||
|
|
||||||
|
for _, g := range i.gamepads {
|
||||||
|
if g.ID != id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return g.ButtonNum
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
|
||||||
|
i.ui.m.RLock()
|
||||||
|
defer i.ui.m.RUnlock()
|
||||||
|
|
||||||
|
for _, g := range i.gamepads {
|
||||||
|
if g.ID != id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if g.ButtonNum <= int(button) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return g.Buttons[button]
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool {
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Input) VibrateGamepad(id driver.GamepadID, duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||||
|
// TODO: Implement this (#1452)
|
||||||
|
}
|
@ -34,6 +34,7 @@ import (
|
|||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/devicescale"
|
"github.com/hajimehoshi/ebiten/v2/internal/devicescale"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
|
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
|
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/hooks"
|
"github.com/hajimehoshi/ebiten/v2/internal/hooks"
|
||||||
@ -83,7 +84,9 @@ func (u *UserInterface) Update() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this call after porting the gamepad part of iOS to internal/gamepad.
|
||||||
u.input.updateGamepads()
|
u.input.updateGamepads()
|
||||||
|
gamepad.Update()
|
||||||
|
|
||||||
renderCh <- struct{}{}
|
renderCh <- struct{}{}
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/uidriver/mobile"
|
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://developer.android.com/reference/android/view/KeyEvent
|
// https://developer.android.com/reference/android/view/KeyEvent
|
||||||
@ -180,30 +180,6 @@ var androidAxisIDToHatID2 = map[int]int{
|
|||||||
axisHatY: 1,
|
axisHatY: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
// deviceIDToGamepadID is a map from Android device IDs to Ebiten gamepad IDs.
|
|
||||||
// As convention, Ebiten gamepad IDs start with 0, and many applications depend on this fact.
|
|
||||||
deviceIDToGamepadID = map[int]driver.GamepadID{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func gamepadIDFromDeviceID(deviceID int) driver.GamepadID {
|
|
||||||
if id, ok := deviceIDToGamepadID[deviceID]; ok {
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
ids := map[driver.GamepadID]struct{}{}
|
|
||||||
for _, id := range deviceIDToGamepadID {
|
|
||||||
ids[id] = struct{}{}
|
|
||||||
}
|
|
||||||
for i := driver.GamepadID(0); ; i++ {
|
|
||||||
if _, ok := ids[i]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
deviceIDToGamepadID[deviceID] = i
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
panic("ebitenmobileview: a gamepad ID cannot be determined")
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateTouchesOnAndroid(action int, id int, x, y int) {
|
func UpdateTouchesOnAndroid(action int, id int, x, y int) {
|
||||||
switch action {
|
switch action {
|
||||||
case 0x00, 0x05, 0x02: // ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_MOVE
|
case 0x00, 0x05, 0x02: // ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_MOVE
|
||||||
@ -215,27 +191,12 @@ func UpdateTouchesOnAndroid(action int, id int, x, y int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gamepadFromGamepadID(id driver.GamepadID) *mobile.Gamepad {
|
|
||||||
for i, g := range gamepads {
|
|
||||||
if g.ID == id {
|
|
||||||
return &gamepads[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func OnKeyDownOnAndroid(keyCode int, unicodeChar int, source int, deviceID int) {
|
func OnKeyDownOnAndroid(keyCode int, unicodeChar int, source int, deviceID int) {
|
||||||
switch {
|
switch {
|
||||||
case source&sourceGamepad == sourceGamepad:
|
case source&sourceGamepad == sourceGamepad:
|
||||||
// A gamepad can be detected as a keyboard. Detect the device as a gamepad first.
|
// A gamepad can be detected as a keyboard. Detect the device as a gamepad first.
|
||||||
if button, ok := androidKeyToGamepadButton[keyCode]; ok {
|
if button, ok := androidKeyToGamepadButton[keyCode]; ok {
|
||||||
id := gamepadIDFromDeviceID(deviceID)
|
gamepad.UpdateAndroidGamepadButton(deviceID, button, true)
|
||||||
g := gamepadFromGamepadID(id)
|
|
||||||
if g == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
g.Buttons[button] = true
|
|
||||||
updateGamepads()
|
|
||||||
}
|
}
|
||||||
case source&sourceJoystick == sourceJoystick:
|
case source&sourceJoystick == sourceJoystick:
|
||||||
// DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them.
|
// DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them.
|
||||||
@ -255,13 +216,7 @@ func OnKeyUpOnAndroid(keyCode int, source int, deviceID int) {
|
|||||||
case source&sourceGamepad == sourceGamepad:
|
case source&sourceGamepad == sourceGamepad:
|
||||||
// A gamepad can be detected as a keyboard. Detect the device as a gamepad first.
|
// A gamepad can be detected as a keyboard. Detect the device as a gamepad first.
|
||||||
if button, ok := androidKeyToGamepadButton[keyCode]; ok {
|
if button, ok := androidKeyToGamepadButton[keyCode]; ok {
|
||||||
id := gamepadIDFromDeviceID(deviceID)
|
gamepad.UpdateAndroidGamepadButton(deviceID, button, false)
|
||||||
g := gamepadFromGamepadID(id)
|
|
||||||
if g == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
g.Buttons[button] = false
|
|
||||||
updateGamepads()
|
|
||||||
}
|
}
|
||||||
case source&sourceJoystick == sourceJoystick:
|
case source&sourceJoystick == sourceJoystick:
|
||||||
// DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them.
|
// DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them.
|
||||||
@ -274,15 +229,8 @@ func OnKeyUpOnAndroid(keyCode int, source int, deviceID int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func OnGamepadAxesOrHatsChanged(deviceID int, axisID int, value float32) {
|
func OnGamepadAxesOrHatsChanged(deviceID int, axisID int, value float32) {
|
||||||
id := gamepadIDFromDeviceID(deviceID)
|
if axis, ok := androidAxisIDToAxisID[axisID]; ok {
|
||||||
g := gamepadFromGamepadID(id)
|
gamepad.UpdateAndroidGamepadAxis(deviceID, axis, float64(value))
|
||||||
if g == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if aid, ok := androidAxisIDToAxisID[axisID]; ok {
|
|
||||||
g.Axes[aid] = value
|
|
||||||
updateGamepads()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,38 +241,20 @@ func OnGamepadAxesOrHatsChanged(deviceID int, axisID int, value float32) {
|
|||||||
hatDown = 4
|
hatDown = 4
|
||||||
hatLeft = 8
|
hatLeft = 8
|
||||||
)
|
)
|
||||||
hid := hid2 / 2
|
hatID := hid2 / 2
|
||||||
v := g.Hats[hid]
|
var dir gamepad.AndroidHatDirection
|
||||||
if hid2%2 == 0 {
|
switch hid2 % 2 {
|
||||||
hatX := int(math.Round(float64(value)))
|
case 0:
|
||||||
if hatX < 0 {
|
dir = gamepad.AndroidHatDirectionX
|
||||||
v |= hatLeft
|
case 1:
|
||||||
v &^= hatRight
|
dir = gamepad.AndroidHatDirectionY
|
||||||
} 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
|
gamepad.UpdateAndroidGamepadHat(deviceID, hatID, dir, int(math.Round(float64(value))))
|
||||||
updateGamepads()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func OnGamepadAdded(deviceID int, name string, buttonNum int, axisNum int, hatNum int, descriptor string, vendorID int, productID int, buttonMask int, axisMask int) {
|
func OnGamepadAdded(deviceID int, name string, buttonCount int, axisCount int, hatCount 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
|
||||||
@ -350,39 +280,9 @@ func OnGamepadAdded(deviceID int, name string, buttonNum int, axisNum int, hatNu
|
|||||||
sdlid[14] = byte(axisMask)
|
sdlid[14] = byte(axisMask)
|
||||||
sdlid[15] = byte(axisMask >> 8)
|
sdlid[15] = byte(axisMask >> 8)
|
||||||
|
|
||||||
id := gamepadIDFromDeviceID(deviceID)
|
gamepad.AddAndroidGamepad(deviceID, name, hex.EncodeToString(sdlid[:]), axisCount, buttonCount, hatCount)
|
||||||
gamepads = append(gamepads, mobile.Gamepad{
|
|
||||||
ID: id,
|
|
||||||
SDLID: hex.EncodeToString(sdlid[:]),
|
|
||||||
Name: name,
|
|
||||||
ButtonNum: buttonNum,
|
|
||||||
AxisNum: axisNum,
|
|
||||||
HatNum: hatNum,
|
|
||||||
})
|
|
||||||
updateGamepads()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func OnInputDeviceRemoved(deviceID int) {
|
func OnInputDeviceRemoved(deviceID int) {
|
||||||
if id, ok := deviceIDToGamepadID[deviceID]; ok {
|
gamepad.RemoveAndroidGamepad(deviceID)
|
||||||
idx := -1
|
|
||||||
for i, g := range gamepads {
|
|
||||||
if g.ID == id {
|
|
||||||
idx = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if idx >= 0 {
|
|
||||||
lastIdx := len(gamepads) - 1
|
|
||||||
gamepads[idx], gamepads[lastIdx] = gamepads[lastIdx], gamepads[idx]
|
|
||||||
gamepads = gamepads[:len(gamepads)-1]
|
|
||||||
}
|
|
||||||
delete(deviceIDToGamepadID, deviceID)
|
|
||||||
}
|
|
||||||
updateGamepads()
|
|
||||||
}
|
|
||||||
|
|
||||||
var gamepads []mobile.Gamepad
|
|
||||||
|
|
||||||
func updateGamepads() {
|
|
||||||
mobile.Get().UpdateGamepads(gamepads)
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user