이 주제에서는 입력 변환 모드가 이상적인 플레이어 환경을 제공하지 않는 게임과 관련하여 PC용 Google Play 게임즈의 마우스 입력을 구현하는 방법을 설명합니다.
PC 플레이어는 일반적으로 터치스크린이 아닌 키보드와 마우스를 사용하므로 게임에서 마우스 입력을 수용할지 고려하는 것이 중요합니다. 기본적으로 PC용 Google Play 게임즈는 왼쪽 클릭 마우스 이벤트를 단일 가상 탭 이벤트로 변환합니다. 이를 '입력 변환 모드'라고 합니다.
이 모드를 사용하면 변경이 거의 없이도 게임이 작동하지만 PC 플레이어가 자연스러운 느낌의 환경을 경험하지는 못합니다. 보완하려면 다음을 구현하는 것이 좋습니다.
- 길게 누르기 작업 대신 컨텍스트 메뉴의 마우스 오버 상태
- 길게 누르기 또는 컨텍스트 메뉴에서 대체 작업이 발생하는 마우스 오른쪽 버튼 클릭
- 누르기 및 드래그 이벤트 대신 1인칭 또는 3인칭 액션 게임의 마우스 탐색
PC에서 일반적인 UI 패턴을 지원하려면 입력 변환 모드를 사용 중지해야 합니다.
PC용 Google Play 게임즈의 입력 처리는 ChromeOS의 입력 처리와 동일합니다. PC를 지원하는 변경사항을 통해 모든 Android 플레이어의 게임도 개선됩니다.
입력 변환 모드 사용 중지
AndroidManifest.xml
파일에서 android.hardware.type.pc
기능을 선언합니다.
이 선언은 게임이 PC 하드웨어를 사용하며 입력 변환 모드를 사용 중지함을 나타냅니다. 또한 required="false"
를 추가하면 마우스를 사용하지 않고도 게임을 스마트폰 및 태블릿에 설치할 수 있게 됩니다. 예를 들면 다음과 같습니다.
<manifest ...>
<uses-feature
android:name="android.hardware.type.pc"
android:required="false" />
...
</manifest>
PC용 Google Play 게임즈 프로덕션 버전은 게임이 출시될 때 올바른 모드로 전환됩니다. 개발자 에뮬레이터에서 실행할 때 작업 표시줄 아이콘을 마우스 오른쪽 버튼으로 클릭하고 Developer Options를 선택한 다음 PC mode(KiwiMouse)를 선택하여 원시 마우스 입력을 수신합니다.
이렇게 하면 View.onGenericMotionEvent가 마우스 움직임과 마우스 이벤트임을 나타내는 소스 SOURCE_MOUSE
를 함께 보고합니다.
gameView.setOnGenericMotionListener { _, motionEvent -> var handled = false if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { // handle the mouse event here handled = true } handled }
gameView.setOnGenericMotionListener((view, motionEvent) -> { if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { // handle the mouse event here return true; } return false; });
마우스 입력 처리에 관한 자세한 내용은 ChromeOS 문서를 참고하세요.
마우스 움직임 처리
마우스 움직임을 감지하려면 ACTION_HOVER_ENTER
, ACTION_HOVER_EXIT
, ACTION_HOVER_MOVE
이벤트를 수신 대기합니다.
이는 게임에서 버튼이나 객체에 마우스를 가져가는 사용자를 감지하는 데 가장 적합합니다. 이를 감지함으로써 힌트 상자를 표시하거나 마우스 오버 상태를 구현하여 플레이어가 선택하려는 항목을 강조표시할 수 있습니다. 예를 들면 다음과 같습니다.
gameView.setOnGenericMotionListener { _, motionEvent -> var handled = false if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { when(motionEvent.action) { MotionEvent.ACTION_HOVER_ENTER -> Log.d("MA", "Mouse entered at ${motionEvent.x}, ${motionEvent.y}") MotionEvent.ACTION_HOVER_EXIT -> Log.d("MA", "Mouse exited at ${motionEvent.x}, ${motionEvent.y}") MotionEvent.ACTION_HOVER_MOVE -> Log.d("MA", "Mouse hovered at ${motionEvent.x}, ${motionEvent.y}") } handled = true } handled }
gameView.setOnGenericMotionListener((view, motionEvent) -> { if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { switch (motionEvent.getAction()) { case MotionEvent.ACTION_HOVER_ENTER: Log.d("MA", "Mouse entered at " + motionEvent.getX() + ", " + motionEvent.getY()); break; case MotionEvent.ACTION_HOVER_EXIT: Log.d("MA", "Mouse exited at " + motionEvent.getX() + ", " + motionEvent.getY()); break; case MotionEvent.ACTION_HOVER_MOVE: Log.d("MA", "Mouse hovered at " + motionEvent.getX() + ", " + motionEvent.getY()); break; } return true; } return false; });
마우스 버튼 처리
PC에는 오랫동안 마우스 왼쪽 버튼과 오른쪽 버튼이 있어서 상호작용 요소에 기본 작업과 보조 작업을 모두 제공합니다. 게임에서 버튼을 탭하는 등의 탭 작업은 마우스 왼쪽 버튼 클릭에 가장 잘 매핑되고 길게 터치 작업은 마우스 오른쪽 버튼 클릭에 매핑할 때 가장 자연스럽게 느껴집니다. 실시간 전략 게임에서는 마우스 왼쪽 버튼을 클릭하여 선택하고 마우스 오른쪽 버튼을 클릭하여 이동할 수도 있습니다. 1인칭 슈팅 게임에서 기본 발사를 왼쪽 버튼 클릭에, 보조 발사를 오른쪽 버튼 클릭에 할당할 수도 있습니다. 무한 러너 게임에서 왼쪽 버튼 클릭으로 건너뛰고 오른쪽 버튼 클릭으로 달릴 수도 있습니다. 마우스 가운데 이벤트에 대한 지원은 추가되지 않았습니다.
버튼 누르기를 처리하려면 ACTION_DOWN
및 ACTION_UP
을 사용합니다. 그런 다음 getActionButton
을 사용하여 어느 버튼이 작업을 트리거했는지 확인하거나 getButtonState
를 사용하여 모든 버튼의 상태를 가져옵니다.
다음 예에서는 enum을 사용하여 getActionButton
의 결과를 표시할 수 있습니다.
enum class MouseButton { LEFT, RIGHT, UNKNOWN; companion object { fun fromMotionEvent(motionEvent: MotionEvent): MouseButton { return when (motionEvent.actionButton) { MotionEvent.BUTTON_PRIMARY -> LEFT MotionEvent.BUTTON_SECONDARY -> RIGHT else -> UNKNOWN } } } }
enum MouseButton { LEFT, RIGHT, MIDDLE, UNKNOWN; static MouseButton fromMotionEvent(MotionEvent motionEvent) { switch (motionEvent.getActionButton()) { case MotionEvent.BUTTON_PRIMARY: return MouseButton.LEFT; case MotionEvent.BUTTON_SECONDARY: return MouseButton.RIGHT; default: return MouseButton.UNKNOWN; } } }
다음 예에서는 마우스 오버 이벤트와 유사하게 처리됩니다.
// Handle the generic motion event gameView.setOnGenericMotionListener { _, motionEvent -> var handled = false if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { when (motionEvent.action) { MotionEvent.ACTION_BUTTON_PRESS -> Log.d( "MA", "${MouseButton.fromMotionEvent(motionEvent)} pressed at ${motionEvent.x}, ${motionEvent.y}" ) MotionEvent.ACTION_BUTTON_RELEASE -> Log.d( "MA", "${MouseButton.fromMotionEvent(motionEvent)} released at ${motionEvent.x}, ${motionEvent.y}" ) } handled = true } handled }
gameView.setOnGenericMotionListener((view, motionEvent) -> { if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { switch (motionEvent.getAction()) { case MotionEvent.ACTION_BUTTON_PRESS: Log.d("MA", MouseButton.fromMotionEvent(motionEvent) + " pressed at " + motionEvent.getX() + ", " + motionEvent.getY()); break; case MotionEvent.ACTION_BUTTON_RELEASE: Log.d("MA", MouseButton.fromMotionEvent(motionEvent) + " released at " + motionEvent.getX() + ", " + motionEvent.getY()); break; } return true; } return false; });
마우스 휠 스크롤 처리
손가락 모으기로 확대/축소하는 동작 또는 영역 터치 후 드래그하여 게임 영역을 스크롤하는 대신 마우스 스크롤 휠을 사용하는 것이 좋습니다.
스크롤 휠 값을 읽으려면 ACTION_SCROLL
이벤트를 수신 대기합니다. AXIS_VSCROLL
(수직 오프셋) 및 AXIS_HSCROLL
(가로 오프셋)을 getAxisValue
와 함께 사용하여 마지막 프레임 이후의 델타를 가져올 수 있습니다. 예를 들면 다음과 같습니다.
gameView.setOnGenericMotionListener { _, motionEvent -> var handled = false if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { when (motionEvent.action) { MotionEvent.ACTION_SCROLL -> { val scrollX = motionEvent.getAxisValue(MotionEvent.AXIS_HSCROLL) val scrollY = motionEvent.getAxisValue(MotionEvent.AXIS_VSCROLL) Log.d("MA", "Mouse scrolled $scrollX, $scrollY") } } handled = true } handled }
gameView.setOnGenericMotionListener((view, motionEvent) -> { if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { switch (motionEvent.getAction()) { case MotionEvent.ACTION_SCROLL: float scrollX = motionEvent.getAxisValue(MotionEvent.AXIS_HSCROLL); float scrollY = motionEvent.getAxisValue(MotionEvent.AXIS_VSCROLL); Log.d("MA", "Mouse scrolled " + scrollX + ", " + scrollY); break; } return true; } return false; });
마우스 입력 캡처
1인칭 또는 3인칭 액션 게임과 같이 마우스 커서의 움직임을 완벽하게 제어해야 하는 일부 게임에서 마우스 움직임을 카메라 움직임에 매핑합니다. 마우스를 독점적으로 제어하려면 View.requestPointerCapture()
를 호출합니다.
requestPointerCapture()
는 뷰가 포함된 뷰 계층 구조에 포커스가 있을 때만 작동합니다. 따라서 onCreate
콜백에서는 포인터 캡처를 얻을 수 없습니다. 기본 메뉴와 상호작용할 때와 같이 플레이어 상호작용이 마우스 포인터를 캡처할 때까지 기다리거나 onWindowFocusChanged
콜백을 사용해야 합니다. 예를 들면 다음과 같습니다.
override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) { gameView.requestPointerCapture() } }
@Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { View gameView = findViewById(R.id.game_view); gameView.requestPointerCapture(); } }
requestPointerCapture()
가 캡처한 이벤트는 OnCapturedPointerListener
를 등록한 포커스 가능 뷰로 전달됩니다. 예를 들면 다음과 같습니다.
gameView.focusable = View.FOCUSABLE gameView.setOnCapturedPointerListener { _, motionEvent -> Log.d("MA", "${motionEvent.x}, ${motionEvent.y}, ${motionEvent.actionButton}") true }
gameView.setFocusable(true); gameView.setOnCapturedPointerListener((view, motionEvent) -> { Log.d("MA", motionEvent.getX() + ", " + motionEvent.getY() + ", " + motionEvent.getActionButton()); return true; });
플레이어가 일시중지 메뉴와 상호작용하는 경우와 같이 독점적인 마우스 캡처를 해제하려면 View.releasePointerCapture()
를 호출합니다.