컨트롤러 작업 처리

시스템 수준에서 Android는 게임 컨트롤러의 입력 이벤트 코드를 Android 키 코드 및 축 값으로 보고합니다. 게임에서 이러한 코드와 값을 받아 특정 인게임 작업으로 변환할 수 있습니다.

플레이어가 게임 컨트롤러를 Android 지원 기기에 물리적으로 연결하거나 무선으로 페어링하면 시스템은 컨트롤러를 입력 장치로 자동 감지하고 입력 이벤트를 보고하기 시작합니다. 게임은 활성 Activity 또는 포커스가 맞춰진 View에서 다음 콜백 메서드를 구현하여 이러한 입력 이벤트를 수신할 수 있습니다. Activity 또는 View 중 하나만 콜백을 구현해야 합니다.

사용자가 상호작용하는 특정 View 객체에서 이벤트를 캡처하는 접근 방식을 권장합니다. 콜백에서 제공한 다음 객체를 검사하여 수신한 입력 이벤트의 유형에 관한 정보를 가져옵니다.

KeyEvent
방향 패드 (D패드) 및 게임패드 버튼 이벤트를 설명하는 객체입니다. 키 이벤트에는 트리거된 특정 버튼을 나타내는 키 코드(예: DPAD_DOWN 또는 BUTTON_A)가 수반됩니다. getKeyCode()를 호출하거나 onKeyDown()와 같은 키 이벤트 콜백에서 키 코드를 가져올 수 있습니다.
MotionEvent
조이스틱 및 숄더 트리거 동작의 입력을 설명하는 객체입니다. 모션 이벤트에는 작업 코드와 일련의 축 값이 수반됩니다. 작업 코드는 조이스틱이 이동하는 것과 같이 발생한 상태 변경을 지정합니다. 축 값은 특정 물리적 컨트롤의 위치 및 기타 움직임 속성을 설명합니다(예: AXIS_X 또는 AXIS_RTRIGGER). getAction()를 호출하여 작업 코드를 가져오고 getAxisValue()을 호출하여 축 값을 가져올 수 있습니다.

이 과정에서는 위에서 언급한 View 콜백 메서드를 구현하고 KeyEventMotionEvent 객체를 처리하여 게임 화면에서 가장 일반적인 물리적 컨트롤 유형 (게임패드 버튼, 방향 패드, 조이스틱)의 입력을 처리하는 방법을 중점적으로 설명합니다.

게임 컨트롤러가 연결되어 있는지 확인

입력 이벤트를 보고할 때 Android는 게임 컨트롤러가 아닌 기기에서 발생한 이벤트와 게임 컨트롤러에서 발생한 이벤트를 구별하지 않습니다. 예를 들어 터치스크린 작업은 터치 표면의 X 좌표를 나타내는 AXIS_X 이벤트를 생성하지만 조이스틱은 조이스틱의 X 위치를 나타내는 AXIS_X 이벤트를 생성합니다. 게임에서 게임 컨트롤러 입력을 처리하는 것이 중요한 경우 먼저 입력 이벤트가 관련 소스 유형에서 발생하는지 확인해야 합니다.

연결된 입력 기기가 게임 컨트롤러인지 확인하려면 getSources()를 호출하여 기기에서 지원되는 입력 소스 유형의 결합된 비트 필드를 가져옵니다. 그런 다음 아래 필드가 설정되어 있는지 테스트할 수 있습니다.

  • SOURCE_GAMEPAD의 소스 유형은 입력 기기에 게임패드 버튼 (예: BUTTON_A)이 있음을 나타냅니다. 이 소스 유형은 게임 컨트롤러에 D패드 버튼이 있는지 엄격하게 표시하지 않습니다. 다만 대부분의 게임패드에는 일반적으로 방향 컨트롤이 있습니다.
  • SOURCE_DPAD의 소스 유형은 입력 기기에 D패드 버튼 (예: DPAD_UP)이 있음을 나타냅니다.
  • SOURCE_JOYSTICK의 소스 유형은 입력 기기에 아날로그 컨트롤 스틱 (예: AXIS_XAXIS_Y를 따라 이동을 기록하는 조이스틱)이 있음을 나타냅니다.

다음 코드 스니펫은 연결된 입력 기기가 게임 컨트롤러인지 확인할 수 있는 도우미 메서드를 보여줍니다. 실행 중인 경우 메서드는 게임 컨트롤러의 기기 ID를 검색합니다. 그런 다음 각 기기 ID를 게임 플레이어와 연결하고 연결된 각 플레이어의 게임 작업을 별도로 처리할 수 있습니다. 동일한 Android 기기에 동시에 연결된 여러 게임 컨트롤러를 지원하는 방법을 자세히 알아보려면 여러 게임 컨트롤러 지원을 참고하세요.

Kotlin

fun getGameControllerIds(): List<Int> {
    val gameControllerDeviceIds = mutableListOf<Int>()
    val deviceIds = InputDevice.getDeviceIds()
    deviceIds.forEach { deviceId ->
        InputDevice.getDevice(deviceId).apply {

            // Verify that the device has gamepad buttons, control sticks, or both.
            if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
                    || sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK) {
                // This device is a game controller. Store its device ID.
                gameControllerDeviceIds
                        .takeIf { !it.contains(deviceId) }
                        ?.add(deviceId)
            }
        }
    }
    return gameControllerDeviceIds
}

Java

public ArrayList<Integer> getGameControllerIds() {
    ArrayList<Integer> gameControllerDeviceIds = new ArrayList<Integer>();
    int[] deviceIds = InputDevice.getDeviceIds();
    for (int deviceId : deviceIds) {
        InputDevice dev = InputDevice.getDevice(deviceId);
        int sources = dev.getSources();

        // Verify that the device has gamepad buttons, control sticks, or both.
        if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
                || ((sources & InputDevice.SOURCE_JOYSTICK)
                == InputDevice.SOURCE_JOYSTICK)) {
            // This device is a game controller. Store its device ID.
            if (!gameControllerDeviceIds.contains(deviceId)) {
                gameControllerDeviceIds.add(deviceId);
            }
        }
    }
    return gameControllerDeviceIds;
}

또한 연결된 게임 컨트롤러에서 지원하는 개별 입력 기능을 확인해야 할 수 있습니다. 이 기능은 예를 들어 게임에서 이해하는 물리적 컨트롤 집합의 입력만 사용하도록 하려는 경우에 유용할 수 있습니다.

특정 키 코드나 축 코드를 연결된 게임 컨트롤러에서 지원하는지 감지하려면 다음 기법을 사용하세요.

  • Android 4.4 (API 수준 19) 이상에서는 hasKeys(int...)를 호출하여 연결된 게임 컨트롤러에서 키 코드가 지원되는지 확인할 수 있습니다.
  • Android 3.1 (API 수준 12) 이상에서는 먼저 getMotionRanges()를 호출하여 연결된 게임 컨트롤러에서 지원되는 모든 사용 가능한 축을 찾을 수 있습니다. 그런 다음 반환된 각 InputDevice.MotionRange 객체에서 getAxis()를 호출하여 축 ID를 가져옵니다.

게임패드 버튼 누름 처리

그림 1은 Android에서 키 코드와 축 값을 대부분의 게임 컨트롤러의 물리적 컨트롤에 매핑하는 방법을 보여줍니다.

그림 1. 일반 게임 컨트롤러용 프로필

그림에서 번호는 다음을 가리킵니다.

게임패드 버튼 누름에 의해 생성되는 일반적인 키 코드는 BUTTON_A, BUTTON_B, BUTTON_SELECTBUTTON_START를 포함합니다. 일부 게임 컨트롤러는 D패드 크로스바의 중앙을 누르면 DPAD_CENTER 키 코드도 트리거합니다. 게임은 getKeyCode()를 호출하거나 onKeyDown()와 같은 키 이벤트 콜백에서 키 코드를 검사할 수 있으며, 키 코드가 게임과 관련된 이벤트를 나타내는 경우 게임 작업으로 처리합니다. 표 1에는 가장 일반적인 게임패드 버튼에 권장되는 게임 작업이 나열되어 있습니다.

표 1. 게임패드 버튼에 권장되는 게임 작업

게임 작업 버튼 키 코드
메인 메뉴에서 게임을 시작하거나 게임 중 일시중지 또는 일시중지 해제 BUTTON_START*
메뉴 표시 BUTTON_SELECT*KEYCODE_MENU*
탐색 디자인 가이드에 설명된 Android 뒤로 탐색 동작과 동일합니다. KEYCODE_BACK
메뉴에서 이전 항목으로 돌아가기 BUTTON_B
선택을 확인하거나 기본 게임 작업 실행 BUTTON_ADPAD_CENTER

* 게임에서 시작, 선택 또는 메뉴 버튼이 있어야 합니다.

도움말: 사용자가 게임 작업에 관한 자체 게임 컨트롤러 매핑을 맞춤설정할 수 있도록 게임에 구성 화면을 제공하는 것이 좋습니다.

다음 스니펫은 onKeyDown()를 재정의하여 BUTTON_ADPAD_CENTER 버튼이 눌리는 경우 게임 작업과 연결하는 방법을 보여줍니다.

Kotlin

class GameView(...) : View(...) {
    ...

    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        var handled = false
        if (event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD) {
            if (event.repeatCount == 0) {
                when (keyCode) {
                    // Handle gamepad and D-pad button presses to navigate the ship
                    ...

                    else -> {
                        keyCode.takeIf { isFireKey(it) }?.run {
                            // Update the ship object to fire lasers
                            ...
                            handled = true
                        }
                    }
                }
            }
            if (handled) {
                return true
            }
        }
        return super.onKeyDown(keyCode, event)
    }

    // Here we treat Button_A and DPAD_CENTER as the primary action
    // keys for the game.
    private fun isFireKey(keyCode: Int): Boolean =
            keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_BUTTON_A
}

Java

public class GameView extends View {
    ...

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean handled = false;
        if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
                == InputDevice.SOURCE_GAMEPAD) {
            if (event.getRepeatCount() == 0) {
                switch (keyCode) {
                    // Handle gamepad and D-pad button presses to
                    // navigate the ship
                    ...

                    default:
                         if (isFireKey(keyCode)) {
                             // Update the ship object to fire lasers
                             ...
                             handled = true;
                         }
                     break;
                }
            }
            if (handled) {
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    private static boolean isFireKey(int keyCode) {
        // Here we treat Button_A and DPAD_CENTER as the primary action
        // keys for the game.
        return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                || keyCode == KeyEvent.KEYCODE_BUTTON_A;
    }
}

참고: Android 4.2 (API 수준 17) 이하에서는 시스템이 기본적으로 BUTTON_A를 Android 뒤로 키로 취급합니다. 앱에서 이러한 Android 버전을 지원한다면 BUTTON_A를 기본 게임 작업으로 처리해야 합니다. 기기에서 현재 Android SDK 버전을 확인하려면 Build.VERSION.SDK_INT 값을 참고하세요.

방향 패드 입력 처리

4방향 패드 (D패드)는 많은 게임 컨트롤러에서 일반적인 물리적 컨트롤입니다. Android는 D패드의 위/아래 누름을 -1.0 (위)에서 1.0 (아래) 사이의 AXIS_HAT_Y 이벤트로 보고하고, D패드의 왼쪽 또는 오른쪽 누르기는 -1.0(왼쪽)에서 1.0 (오른쪽) 사이의 AXIS_HAT_X 이벤트로 보고합니다.

일부 컨트롤러는 D패드가 눌렸을 때 이벤트 대신 키 코드로 보고합니다. 게임에서 D패드 누르기가 중요한 경우 표 2에서 권장하는 것처럼 해트 축 이벤트와 D패드 키 코드를 동일한 입력 이벤트로 취급해야 합니다.

표 2. D패드 키 코드 및 해트 축 값에 권장되는 기본 게임 작업

게임 작업 D패드 키 코드 해트 축 코드
위로 이동 KEYCODE_DPAD_UP AXIS_HAT_Y(0 ~ -1.0의 값을 가짐)
아래로 이동 KEYCODE_DPAD_DOWN AXIS_HAT_Y(0 ~ 1.0의 값을 가짐)
왼쪽으로 이동 KEYCODE_DPAD_LEFT AXIS_HAT_X(0 ~ -1.0의 값을 가짐)
오른쪽으로 이동 KEYCODE_DPAD_RIGHT AXIS_HAT_X(0 ~ 1.0의 값을 가짐)

다음 코드 스니펫은 입력 이벤트에서 해트 축과 키 코드 값을 확인하여 D패드 방향을 결정할 수 있는 도우미 클래스를 보여줍니다.

Kotlin

class Dpad {

    private var directionPressed = -1 // initialized to -1

    fun getDirectionPressed(event: InputEvent): Int {
        if (!isDpadDevice(event)) {
            return -1
        }

        // If the input event is a MotionEvent, check its hat axis values.
        (event as? MotionEvent)?.apply {

            // Use the hat axis value to find the D-pad direction
            val xaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_X)
            val yaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_Y)

            directionPressed = when {
                // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
                // LEFT and RIGHT direction accordingly.
                xaxis.compareTo(-1.0f) == 0 -> Dpad.LEFT
                xaxis.compareTo(1.0f) == 0 -> Dpad.RIGHT
                // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
                // UP and DOWN direction accordingly.
                yaxis.compareTo(-1.0f) == 0 -> Dpad.UP
                yaxis.compareTo(1.0f) == 0 -> Dpad.DOWN
                else -> directionPressed
            }
        }
        // If the input event is a KeyEvent, check its key code.
        (event as? KeyEvent)?.apply {

            // Use the key code to find the D-pad direction.
            directionPressed = when(event.keyCode) {
                KeyEvent.KEYCODE_DPAD_LEFT -> Dpad.LEFT
                KeyEvent.KEYCODE_DPAD_RIGHT -> Dpad.RIGHT
                KeyEvent.KEYCODE_DPAD_UP -> Dpad.UP
                KeyEvent.KEYCODE_DPAD_DOWN -> Dpad.DOWN
                KeyEvent.KEYCODE_DPAD_CENTER ->  Dpad.CENTER
                else -> directionPressed
            }
        }
        return directionPressed
    }

    companion object {
        internal const val UP = 0
        internal const val LEFT = 1
        internal const val RIGHT = 2
        internal const val DOWN = 3
        internal const val CENTER = 4

        fun isDpadDevice(event: InputEvent): Boolean =
            // Check that input comes from a device with directional pads.
            event.source and InputDevice.SOURCE_DPAD != InputDevice.SOURCE_DPAD
    }
}

Java

public class Dpad {
    final static int UP       = 0;
    final static int LEFT     = 1;
    final static int RIGHT    = 2;
    final static int DOWN     = 3;
    final static int CENTER   = 4;

    int directionPressed = -1; // initialized to -1

    public int getDirectionPressed(InputEvent event) {
        if (!isDpadDevice(event)) {
           return -1;
        }

        // If the input event is a MotionEvent, check its hat axis values.
        if (event instanceof MotionEvent) {

            // Use the hat axis value to find the D-pad direction
            MotionEvent motionEvent = (MotionEvent) event;
            float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X);
            float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y);

            // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
            // LEFT and RIGHT direction accordingly.
            if (Float.compare(xaxis, -1.0f) == 0) {
                directionPressed =  Dpad.LEFT;
            } else if (Float.compare(xaxis, 1.0f) == 0) {
                directionPressed =  Dpad.RIGHT;
            }
            // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
            // UP and DOWN direction accordingly.
            else if (Float.compare(yaxis, -1.0f) == 0) {
                directionPressed =  Dpad.UP;
            } else if (Float.compare(yaxis, 1.0f) == 0) {
                directionPressed =  Dpad.DOWN;
            }
        }

        // If the input event is a KeyEvent, check its key code.
        else if (event instanceof KeyEvent) {

           // Use the key code to find the D-pad direction.
            KeyEvent keyEvent = (KeyEvent) event;
            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
                directionPressed = Dpad.LEFT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
                directionPressed = Dpad.RIGHT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
                directionPressed = Dpad.UP;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
                directionPressed = Dpad.DOWN;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
                directionPressed = Dpad.CENTER;
            }
        }
        return directionPressed;
    }

    public static boolean isDpadDevice(InputEvent event) {
        // Check that input comes from a device with directional pads.
        if ((event.getSource() & InputDevice.SOURCE_DPAD)
             != InputDevice.SOURCE_DPAD) {
             return true;
         } else {
             return false;
         }
     }
}

게임에서 D패드 입력을 처리할 때 (예: onGenericMotionEvent() 또는 onKeyDown() 콜백) 이 도우미 클래스를 사용할 수 있습니다.

예:

Kotlin

private val dpad = Dpad()
...
override fun onGenericMotionEvent(event: MotionEvent): Boolean {
    if (Dpad.isDpadDevice(event)) {
        when (dpad.getDirectionPressed(event)) {
            Dpad.LEFT -> {
                // Do something for LEFT direction press
                ...
                return true
            }
            Dpad.RIGHT -> {
                // Do something for RIGHT direction press
                ...
                return true
            }
            Dpad.UP -> {
                // Do something for UP direction press
                ...
                return true
            }
            ...
        }
    }

    // Check if this event is from a joystick movement and process accordingly.
    ...
}

Java

Dpad dpad = new Dpad();
...
@Override
public boolean onGenericMotionEvent(MotionEvent event) {

    // Check if this event if from a D-pad and process accordingly.
    if (Dpad.isDpadDevice(event)) {

       int press = dpad.getDirectionPressed(event);
       switch (press) {
            case LEFT:
                // Do something for LEFT direction press
                ...
                return true;
            case RIGHT:
                // Do something for RIGHT direction press
                ...
                return true;
            case UP:
                // Do something for UP direction press
                ...
                return true;
            ...
        }
    }

    // Check if this event is from a joystick movement and process accordingly.
    ...
}

조이스틱 이동 처리

플레이어가 게임 컨트롤러에서 조이스틱을 움직이면 Android는 ACTION_MOVE 작업 코드와 조이스틱 축의 업데이트된 위치가 포함된 MotionEvent를 보고합니다. 게임은 MotionEvent에서 제공하는 데이터를 사용하여 관심 있는 조이스틱 움직임이 발생했는지 확인할 수 있습니다.

조이스틱 모션 이벤트는 단일 객체 내에서 여러 이동 샘플을 함께 일괄 처리할 수 있습니다. MotionEvent 객체에는 각 조이스틱 축의 현재 위치 및 각 축의 여러 기록 위치가 포함되어 있습니다. Android는 작업 코드가 ACTION_MOVE인 모션 이벤트 (예: 조이스틱 이동)를 보고할 때 효율성을 위해 축 값을 일괄 처리합니다. 축의 과거 값은 현재 축 값보다 오래된 고유한 값의 집합과 이전 모션 이벤트에 보고된 값보다 최신인 값의 집합으로 구성됩니다. 자세한 내용은 MotionEvent 참조를 확인하세요.

이전 정보를 사용하면 조이스틱 입력에 따라 게임 객체의 움직임을 더 정확하게 렌더링할 수 있습니다. 현재 값과 이전 값을 검색하려면 getAxisValue() 또는 getHistoricalAxisValue()를 호출하세요. getHistorySize()를 호출하여 조이스틱 이벤트의 과거 지점 수를 확인할 수도 있습니다.

다음 스니펫은 onGenericMotionEvent() 콜백을 재정의하여 조이스틱 입력을 처리하는 방법을 보여줍니다. 먼저 축의 이전 값을 처리한 다음 현재 위치를 처리해야 합니다.

Kotlin

class GameView(...) : View(...) {

    override fun onGenericMotionEvent(event: MotionEvent): Boolean {

        // Check that the event came from a game controller
        return if (event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
                && event.action == MotionEvent.ACTION_MOVE) {

            // Process the movements starting from the
            // earliest historical position in the batch
            (0 until event.historySize).forEach { i ->
                // Process the event at historical position i
                processJoystickInput(event, i)
            }

            // Process the current movement sample in the batch (position -1)
            processJoystickInput(event, -1)
            true
        } else {
            super.onGenericMotionEvent(event)
        }
    }
}

Java

public class GameView extends View {

    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {

        // Check that the event came from a game controller
        if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) ==
                InputDevice.SOURCE_JOYSTICK &&
                event.getAction() == MotionEvent.ACTION_MOVE) {

            // Process all historical movement samples in the batch
            final int historySize = event.getHistorySize();

            // Process the movements starting from the
            // earliest historical position in the batch
            for (int i = 0; i < historySize; i++) {
                // Process the event at historical position i
                processJoystickInput(event, i);
            }

            // Process the current movement sample in the batch (position -1)
            processJoystickInput(event, -1);
            return true;
        }
        return super.onGenericMotionEvent(event);
    }
}

조이스틱 입력을 사용하기 전에 조이스틱이 중앙에 있는지 확인한 다음 그에 따라 축 이동을 계산해야 합니다. 조이스틱은 일반적으로 평면 영역, 즉 축이 중앙에 배치된 것으로 간주되는 (0,0) 좌표 근처의 값 범위를 갖습니다. Android에서 보고된 축 값이 평평한 영역 내에 있는 경우 컨트롤러를 정지 상태로 취급해야 합니다 (즉, 두 축을 따라 움직임이 없음).

아래 스니펫은 각 축을 따라 이동하는 것을 계산하는 도우미 메서드를 보여줍니다. 아래에 설명된 processJoystickInput() 메서드에서 이 도우미를 호출합니다.

Kotlin

private fun getCenteredAxis(
        event: MotionEvent,
        device: InputDevice,
        axis: Int,
        historyPos: Int
): Float {
    val range: InputDevice.MotionRange? = device.getMotionRange(axis, event.source)

    // A joystick at rest does not always report an absolute position of
    // (0,0). Use the getFlat() method to determine the range of values
    // bounding the joystick axis center.
    range?.apply {
        val value: Float = if (historyPos < 0) {
            event.getAxisValue(axis)
        } else {
            event.getHistoricalAxisValue(axis, historyPos)
        }

        // Ignore axis values that are within the 'flat' region of the
        // joystick axis center.
        if (Math.abs(value) > flat) {
            return value
        }
    }
    return 0f
}

Java

private static float getCenteredAxis(MotionEvent event,
        InputDevice device, int axis, int historyPos) {
    final InputDevice.MotionRange range =
            device.getMotionRange(axis, event.getSource());

    // A joystick at rest does not always report an absolute position of
    // (0,0). Use the getFlat() method to determine the range of values
    // bounding the joystick axis center.
    if (range != null) {
        final float flat = range.getFlat();
        final float value =
                historyPos < 0 ? event.getAxisValue(axis):
                event.getHistoricalAxisValue(axis, historyPos);

        // Ignore axis values that are within the 'flat' region of the
        // joystick axis center.
        if (Math.abs(value) > flat) {
            return value;
        }
    }
    return 0;
}

종합하면 게임에서 조이스틱 이동을 처리하는 방법은 다음과 같습니다.

Kotlin

private fun processJoystickInput(event: MotionEvent, historyPos: Int) {

    val inputDevice = event.device

    // Calculate the horizontal distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat axis, or the right control stick.
    var x: Float = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_X, historyPos)
    if (x == 0f) {
        x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_X, historyPos)
    }
    if (x == 0f) {
        x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Z, historyPos)
    }

    // Calculate the vertical distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat switch, or the right control stick.
    var y: Float = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Y, historyPos)
    if (y == 0f) {
        y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_Y, historyPos)
    }
    if (y == 0f) {
        y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_RZ, historyPos)
    }

    // Update the ship object based on the new x and y values
}

Java

private void processJoystickInput(MotionEvent event,
        int historyPos) {

    InputDevice inputDevice = event.getDevice();

    // Calculate the horizontal distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat axis, or the right control stick.
    float x = getCenteredAxis(event, inputDevice,
            MotionEvent.AXIS_X, historyPos);
    if (x == 0) {
        x = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_HAT_X, historyPos);
    }
    if (x == 0) {
        x = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_Z, historyPos);
    }

    // Calculate the vertical distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat switch, or the right control stick.
    float y = getCenteredAxis(event, inputDevice,
            MotionEvent.AXIS_Y, historyPos);
    if (y == 0) {
        y = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_HAT_Y, historyPos);
    }
    if (y == 0) {
        y = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_RZ, historyPos);
    }

    // Update the ship object based on the new x and y values
}

단일 조이스틱 이상의 정교한 기능을 갖춘 게임 컨트롤러를 지원하려면 다음 권장사항을 따르세요.

  • 듀얼 컨트롤러 스틱의 처리. 많은 게임 컨트롤러에는 왼쪽과 오른쪽 조이스틱이 모두 있습니다. Android는 왼쪽 스틱의 수평 이동은 AXIS_X 이벤트로, 수직 이동은 AXIS_Y 이벤트로 보고합니다. 오른쪽 스틱의 경우 수평 이동은 AXIS_Z 이벤트로, 수직 이동은 AXIS_RZ 이벤트로 보고합니다. 코드에서 두 컨트롤러 스틱을 모두 처리해야 합니다.
  • 숄더 트리거 누르기 처리 (다른 입력 방법 제공) 일부 컨트롤러에는 왼쪽 및 오른쪽 숄더 트리거가 있습니다. 이러한 트리거가 있으면 Android는 왼쪽 트리거 누름을 AXIS_LTRIGGER 이벤트로 보고하고 오른쪽 트리거 누름을 AXIS_RTRIGGER 이벤트로 보고합니다. Android 4.3 (API 수준 18)에서는 AXIS_LTRIGGER를 생성하는 컨트롤러도 AXIS_BRAKE 축의 동일한 값을 보고합니다. 이는 AXIS_RTRIGGERAXIS_GAS의 경우에도 마찬가지입니다. Android는 모든 아날로그 트리거 누르기를 0.0 (해제됨)에서 1.0 (완전히 눌림) 사이의 정규화된 값으로 보고합니다. 모든 컨트롤러에 트리거가 있는 것은 아니므로 플레이어가 다른 버튼으로 이러한 게임 작업을 할 수 있도록 하는 것이 좋습니다.