입력 SDK 시작하기

이 문서에서는 PC용 Google Play 게임즈를 지원하는 게임에서 입력 SDK를 설정하고 표시하는 방법을 설명합니다. 게임에 SDK를 추가하고 게임‑액션‑사용자 입력 할당이 포함된 입력 맵을 생성하는 등의 작업이 실행됩니다.

시작하기 전에

입력 SDK를 게임에 추가하기 전에 게임 엔진의 입력 시스템을 사용하여 키보드와 마우스 입력을 지원해야 합니다.

입력 SDK는 사용자에게 표시될 수 있도록 게임에서 사용하는 컨트롤에 관한 정보를 PC용 Google Play 게임즈에 제공합니다. 사용자의 키보드 재매핑을 선택적으로 허용할 수도 있습니다.

각 컨트롤은 InputAction(예: '점프'의 경우 'J')이며 InputActionsInputGroups로 구성합니다. InputGroup은 '운전', '걷기', '기본 메뉴' 등 게임에서 다른 모드를 나타낼 수도 있습니다. InputContexts를 사용하여 게임의 다양한 지점에서 어떤 그룹이 활성 상태인지 나타낼 수도 있습니다.

키보드 재매핑이 자동으로 처리되도록 사용 설정할 수 있지만 자체 컨트롤 재매핑 인터페이스를 제공하고 싶다면 입력 SDK 재매핑을 사용 중지하면 됩니다.

다음 시퀀스 다이어그램은 Input SDK API의 작동 방식을 설명합니다.

Input SDK API를 호출하는 게임 구현과 Android 기기와의 상호작용을 보여주는 시퀀스 다이어그램

게임에서 입력 SDK를 구현하면 컨트롤이 PC용 Google Play 게임즈 오버레이에 표시됩니다.

PC용 Google Play 게임즈 오버레이

PC용 Google Play 게임즈 오버레이('오버레이')는 게임에서 정의된 컨트롤을 표시합니다. 사용자는 Shift + Tab을 눌러 언제든지 오버레이에 액세스합니다.

PC용 Google Play 게임즈 오버레이

키 결합 설계 권장사항

키 결합을 설계할 때는 다음 권장사항을 고려하세요.

  • InputActions를 논리적으로 관련된 InputGroups로 그룹화하여 게임플레이 중에 컨트롤의 탐색과 검색 가능성을 개선합니다.
  • InputGroup을 최대 1개의 InputContext에 할당합니다. InputMap을 세분화하면 오버레이에서 컨트롤 탐색 환경이 개선됩니다.
  • 게임의 다양한 장면 유형마다 InputContext를 만듭니다. 일반적으로 모든 '메뉴와 유사한' 장면에 단일 InputContext를 사용할 수 있습니다. 게임 속 미니 게임 또는 단일 장면의 대체 컨트롤에 다른 InputContexts를 사용하세요.
  • 두 작업이 동일한 InputContext에서 동일한 키를 사용하도록 설계된 경우 'Interact/Fire'와 같은 라벨 문자열을 활용합니다.
  • 두 키가 동일한 InputAction에 결합되도록 설계된 경우 게임에서 동일한 작업을 실행하는 2개의 다른 InputActions를 사용합니다. 두 InputActions에 모두 동일한 라벨 문자열을 활용해도 되지만 ID는 달라야 합니다.
  • 특수키가 키 세트에 적용되는 경우 특수키를 결합하는 여러 InputActions 대신 특수키가 있는 단일 InputAction을 사용하는 것이 좋습니다(예: Shift + W, Shift + A, Shift + S, Shift + D가 아닌 ShiftW, A, S, D 사용).
  • 입력 재매핑은 사용자가 텍스트 필드를 작성할 때 자동으로 사용 중지됩니다. Android가 게임에서 텍스트 필드를 감지하고 재매핑된 키가 이를 방해하지 않도록 하려면 Android 텍스트 필드 구현을 위한 권장사항을 따르세요. 게임에서 일반적이지 않은 텍스트 필드를 사용해야 한다면 빈 InputGroups 목록이 포함된 InputContext가 있는 setInputContext()를 사용하여 재매핑을 수동으로 사용 중지할 수 있습니다.
  • 게임에서 재매핑을 지원하는 경우 사용자가 저장한 버전과 충돌할 수 있는 민감한 작업인 키 결합을 업데이트하는 것이 좋습니다. 가능하다면 기존 컨트롤의 ID는 변경하지 마세요.

재매핑 기능

PC용 Google Play 게임즈는 입력 SDK를 사용하여 게임에서 제공하는 키 결합을 바탕으로 키보드 컨트롤 재매핑을 지원합니다. 이는 선택사항이며 완전히 사용 중지할 수 있습니다. 예를 들어 자체 키보드 재매핑 인터페이스를 제공하고 싶을 수 있습니다. 게임에서 재매핑을 사용 중지하려면 InputMap에 관해 사용 중지된 재매핑 옵션을 지정하면 됩니다(자세한 내용은 InputMap 빌드 참고).

이 기능에 액세스하려면 사용자는 오버레이를 연 후 재매핑하려는 작업을 클릭해야 합니다. 모든 재매핑 이벤트 후에 PC용 Google Play 게임즈는 사용자가 재매핑한 각 컨트롤을 게임이 수신할 것으로 예상되는 기본 컨트롤에 매핑하므로 게임에서는 플레이어의 재매핑을 몰라도 됩니다. 재매핑 이벤트 콜백을 추가하여 게임에서 키보드 컨트롤을 표시하는 데 사용되는 애셋을 선택적으로 업데이트할 수 있습니다.

키 재매핑 시도

PC용 Google Play 게임즈는 각 사용자의 재매핑된 컨트롤을 로컬에 저장하여 여러 게임 세션에서 컨트롤을 유지할 수 있습니다. 이 정보는 PC 플랫폼용 디스크에만 저장되며 모바일 환경에는 영향을 미치지 않습니다. 컨트롤 데이터는 사용자가 PC용 Google Play 게임즈를 제거하거나 재설치할 때 삭제됩니다. 이 데이터는 여러 PC 기기에 걸쳐 유지되지 않습니다.

게임에서 재매핑 기능을 지원하려면 다음 제한사항을 피하세요.

재매핑 제한사항

재매핑 기능은 키 결합에 다음 사례가 포함된 경우 게임에서 사용 중지할 수 있습니다.

  • 특수키 + 비 특수키로 구성되지 않은 다중 키 InputActions. 예를 들어 Shift + A는 유효하지만 A + B 또는 Ctrl + Alt, Shift + A + Tab은 유효하지 않습니다.
  • InputMap에 고유 ID가 반복되는 InputActions 또는 InputGroups, InputContexts가 포함되어 있습니다.

재매핑 제한사항

재매핑 키 결합을 설계할 때는 다음 제한사항을 고려하세요.

  • 키 조합으로의 재매핑은 지원되지 않습니다. 예를 들어 사용자는 Shift + ACtrl + B로 또는 AShift + A로 재매핑할 수 없습니다.
  • 마우스 버튼이 있는 InputActions에는 재매핑이 지원되지 않습니다. 예를 들어 Shift + 마우스 오른쪽 버튼 클릭은 재매핑할 수 없습니다.

PC용 Google Play 게임즈 에뮬레이터에서 키 재매핑 테스트

다음 adb 명령어를 실행하여 언제든지 PC용 Google Play 게임즈 에뮬레이터에서 재매핑 기능을 사용 설정할 수 있습니다.

adb shell dumpsys input_mapping_service --set RemappingFlagValue true

오버레이는 다음 이미지와 같이 변경됩니다.

키 재매핑이 사용 설정된 오버레이

SDK 추가

개발 플랫폼에 따라 입력 SDK를 설치합니다.

Java 및 Kotlin

모듈 수준 build.gradle 파일에 종속 항목을 추가하여 Java 또는 Kotlin용 입력 SDK를 가져옵니다.

dependencies {
  implementation 'com.google.android.libraries.play.games:inputmapping:1.1.1-beta'
  ...
}

Unity

입력 SDK는 여러 종속 항목이 포함된 표준 Unity 패키지입니다.

모든 종속 항목이 포함된 패키지를 설치해야 합니다. 패키지를 설치하는 방법에는 여러 가지가 있습니다.

.unitypackage 설치

모든 종속 항목이 있는 입력 SDK unitypackage 파일을 다운로드합니다. 애셋 > 패키지 가져오기 > 맞춤 패키지를 선택한 후 다운로드된 파일을 찾아 .unitypackage를 설치하면 됩니다.

UPM을 사용하여 설치

또는 .tgz를 다운로드하고 그 종속 항목을 설치하여 Unity Package Manager를 통해 패키지를 설치할 수도 있습니다.

OpenUPM을 사용하여 설치

OpenUPM을 사용하여 패키지를 설치할 수 있습니다.

$ openupm add com.google.android.libraries.play.games.inputmapping

샘플 게임

입력 SDK와 통합하는 방법의 예는 Kotlin 또는 Java 게임의 경우 AGDK 터널, Unity 게임의 경우 Trivial Kart를 참고하세요.

키 결합 생성

InputMap을 빌드하고 InputMappingProvider와 함께 반환하여 키 결합을 등록하세요. 다음 예는 InputMappingProvider를 간략히 설명합니다.

Kotlin

class InputSDKProvider : InputMappingProvider {
  override fun onProvideInputMap(): InputMap {
    TODO("Not yet implemented")
  }
}

Java

public class InputSDKProvider implements InputMappingProvider {
    private static final String INPUTMAP_VERSION = "1.0.0";

    @Override
    @NonNull
    public InputMap onProvideInputMap() {
        // TODO: return an InputMap
    }
}

C#

#if PLAY_GAMES_PC
using Java.Lang;
using Java.Util;
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel;

public class InputSDKProvider : InputMappingProviderCallbackHelper
{
    public static readonly string INPUT_MAP_VERSION = "1.0.0";

    public override InputMap OnProvideInputMap()
    {
        // TODO: return an InputMap
    }
}
#endif

입력 작업 정의

InputAction 클래스는 키 또는 키 조합을 게임 동작에 매핑하는 데 사용합니다. InputActions에는 모든 InputActions에 걸친 고유 ID가 있어야 합니다.

재매핑을 지원하는 경우 재매핑할 수 있는 InputActions를 정의할 수 있습니다. 게임에서 재매핑을 지원하지 않으면 모든 InputActions의 재매핑 옵션을 사용 중지됨으로 설정해야 합니다. 하지만 입력 SDK는 매우 지능적이어서 InputMap에서 재매핑이 지원되지 않으면 재매핑을 사용 중지합니다.

다음 예에서는 Space 키를 드라이브 동작에 매핑합니다.

Kotlin

companion object {
  private val driveInputAction = InputAction.create(
    "Drive",
    InputActionsIds.DRIVE.ordinal.toLong(),
    InputControls.create(listOf(KeyEvent.KEYCODE_SPACE), emptyList()),
    InputEnums.REMAP_OPTION_ENABLED)
}

Java

private static final InputAction driveInputAction = InputAction.create(
    "Drive",
    InputEventIds.DRIVE.ordinal(),
    InputControls.create(
            Collections.singletonList(KeyEvent.KEYCODE_SPACE),
            Collections.emptyList()),
    InputEnums.REMAP_OPTION_ENABLED
);

C#

private static readonly InputAction driveInputAction = InputAction.Create(
    "Drive",
    (long)InputEventIds.DRIVE,
    InputControls.Create(
        new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE) }.ToJavaList(),
        new ArrayList<Integer>()),
    InputEnums.REMAP_OPTION_ENABLED
);

오버레이에 표시된 단일 키 InputAction

동작은 마우스 입력도 나타낼 수 있습니다. 다음 예는 왼쪽 클릭이동 동작으로 설정합니다.

Kotlin

companion object {
  private val mouseInputAction = InputAction.create(
    "Move",
    InputActionsIds.MOUSE_MOVEMENT.ordinal.toLong(),
    InputControls.create(emptyList(), listOf(InputControls.MOUSE_LEFT_CLICK)),
    InputEnums.REMAP_OPTION_DISABLED)
}

Java

private static final InputAction mouseInputAction = InputAction.create(
    "Move",
    InputActionsIds.MOUSE_MOVEMENT.ordinal(),
    InputControls.create(
            Collections.emptyList(),
            Collections.singletonList(InputControls.MOUSE_LEFT_CLICK)
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

C#

private static readonly InputAction mouseInputAction = InputAction.Create(
    "Move",
    (long)InputEventIds.MOUSE_MOVEMENT,
    InputControls.Create(
        new ArrayList<Integer>(),
        new[] { new Integer((int)PlayMouseAction.MouseLeftClick) }.ToJavaList()
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

오버레이에 표시된 마우스 InputAction

키 조합은 InputAction에 여러 키 코드를 전달하여 지정됩니다. 이 예에서 Space + Shift터보 동작에 매핑되며 이는 Space드라이브에 매핑된 경우에도 작동합니다.

Kotlin

companion object {
  private val turboInputAction = InputAction.create(
    "Turbo",
    InputActionsIds.TURBO.ordinal.toLong(),
    InputControls.create(
      listOf(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SPACE),
      emptyList()),
    InputEnums.REMAP_OPTION_ENABLED)
}

Java

private static final InputAction turboInputAction = InputAction.create(
    "Turbo",
    InputActionsIds.TURBO.ordinal(),
    InputControls.create(
            Arrays.asList(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SPACE),
            Collections.emptyList()
    ),
    InputEnums.REMAP_OPTION_ENABLED
);

C#

private static readonly InputAction turboInputAction = InputAction.Create(
    "Turbo",
    (long)InputEventIds.TURBO,
    InputControls.Create(
        new[]
        {
            new Integer(AndroidKeyCode.KEYCODE_SHIFT_LEFT),
            new Integer(AndroidKeyCode.KEYCODE_SPACE)
        }.ToJavaList(),
        new ArrayList<Integer>()),
    InputEnums.REMAP_OPTION_ENABLED
);

오버레이에 표시된 다중 키 InputAction

입력 SDK를 사용하면 마우스와 키 버튼을 함께 사용하여 단일 동작을 실행할 수 있습니다. 다음 예는 Shift마우스 오른쪽 버튼을 함께 누르면 이 샘플 게임에 경유지를 추가함을 나타냅니다.

Kotlin

companion object {
  private val addWaypointInputAction = InputAction.create(
    "Add waypoint",
    InputActionsIds.ADD_WAYPOINT.ordinal.toLong(),
    InputControls.create(
      listOf(KeyEvent.KeyEvent.KEYCODE_TAB),
      listOf(InputControls.MOUSE_RIGHT_CLICK)),
    InputEnums.REMAP_OPTION_DISABLED)
}

Java

private static final InputAction addWaypointInputAction = InputAction.create(
    "Add waypoint",
    InputActionsIds.ADD_WAYPOINT.ordinal(),
    InputControls.create(
            Collections.singletonList(KeyEvent.KEYCODE_TAB),
            Collections.singletonList(InputControls.MOUSE_RIGHT_CLICK)
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

C#

private static readonly InputAction addWaypointInputAction = InputAction.Create(
    "Add waypoint",
    (long)InputEventIds.ADD_WAYPOINT,
    InputControls.Create(
        new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE) }.ToJavaList(),
        new[] { new Integer((int)PlayMouseAction.MouseRightClick) }.ToJavaList()
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

오버레이에 표시된 키 + 마우스 InputAction 조합

InputAction에는 다음 필드가 있습니다.

  • ActionLabel: 이 동작을 나타내는 UI에 표시되는 문자열입니다. 현지화는 자동으로 실행되지 않으므로 미리 현지화를 실행하세요.
  • InputControls: 이 동작에 사용된 입력 컨트롤을 정의합니다. 컨트롤은 오버레이에서 일관된 글리프에 매핑됩니다.
  • InputActionId: InputAction의 숫자 ID와 버전을 저장하는 InputIdentifier 객체입니다(자세한 내용은 키 ID 추적 참고).
  • InputRemappingOption: InputEnums.REMAP_OPTION_ENABLED 또는 InputEnums.REMAP_OPTION_DISABLED 중 하나입니다. 작업을 재매핑할 수 있는지 정의합니다. 게임에서 재매핑을 지원하지 않으면 이 필드를 건너뛰거나 사용 중지로 설정할 수 있습니다.
  • RemappedInputControls: 재매핑 이벤트에서 사용자가 설정한 재매핑된 키를 읽는 데 사용되는 읽기 전용 InputControls 객체입니다(재매핑 이벤트에 관한 알림을 수신하는 데 사용됨).

InputControls는 동작과 연결된 입력을 나타내며 다음 필드를 포함합니다.

  • AndroidKeycodes: 동작과 연결된 키보드 입력을 나타내는 정수 목록입니다. KeyEvent 클래스 또는 Unity용 AndroidKeycode 클래스에서 정의됩니다.
  • MouseActions: 이 동작과 연결된 마우스 입력을 나타내는 MouseAction 값의 목록입니다.

입력 그룹 정의

InputActionsInputGroups를 사용하여 논리적으로 관련된 작업으로 그룹화되어 오버레이에서 탐색 및 컨트롤 검색 가능성을 개선합니다. 각 InputGroup ID는 게임의 모든 InputGroups에서 고유해야 합니다.

입력 작업을 그룹으로 구성하면 플레이어가 현재 컨텍스트에 맞는 키 조합을 더 쉽게 찾을 수 있습니다.

재매핑을 지원하는 경우 재매핑할 수 있는 InputGroups를 정의할 수 있습니다. 게임에서 재매핑을 지원하지 않으면 모든 InputGroups의 재매핑 옵션을 사용 중지됨으로 설정해야 합니다. 하지만 입력 SDK는 매우 지능적이어서 InputMap에서 재매핑이 지원되지 않으면 재매핑을 사용 중지합니다.

Kotlin

companion object {
  private val menuInputGroup = InputGroup.create(
    "Menu keys",
    listOf(
      navigateUpInputAction,
      navigateLeftInputAction,
      navigateDownInputAction,
      navigateRightInputAction,
      openMenuInputAction,
      returnMenuInputAction),
    InputGroupsIds.MENU_ACTION_KEYS.ordinal.toLong(),
    InputEnums.REMAP_OPTION_ENABLED
  )
}

Java

private static final InputGroup menuInputGroup = InputGroup.create(
    "Menu keys",
    Arrays.asList(
           navigateUpInputAction,
           navigateLeftInputAction,
           navigateDownInputAction,
           navigateRightInputAction,
           openMenuInputAction,
           returnMenuInputAction),
    InputGroupsIds.MENU_ACTION_KEYS.ordinal(),
    REMAP_OPTION_ENABLED
);

C#

private static readonly InputGroup menuInputGroup = InputGroup.Create(
    "Menu keys",
    new[]
    {
        navigateUpInputAction,
        navigateLeftInputAction,
        navigateDownInputAction,
        navigateRightInputAction,
        openMenuInputAction,
        returnMenuInputAction,
    }.ToJavaList(),
    (long)InputGroupsIds.MENU_ACTION_KEYS,
    InputEnums.REMAP_OPTION_ENABLED
);

다음 예는 도로 컨트롤메뉴 컨트롤 입력 그룹을 오버레이에 표시합니다.

도로 컨트롤과 메뉴 컨트롤 입력 그룹이 포함된 InputMap을 표시하는 오버레이

InputGroup에는 다음과 같은 필드가 있습니다.

  • GroupLabel: 동작 세트를 논리적으로 그룹화하는 데 사용할 수 있는 오버레이에 표시되는 문자열입니다. 이 문자열은 자동으로 현지화되지 않습니다.
  • InputActions: 이전 단계에서 정의된 InputAction 객체 목록입니다. 이러한 작업은 모두 그룹 제목 아래에 시각적으로 표시됩니다.
  • InputGroupId: InputGroup의 숫자 ID와 버전을 저장하는 InputIdentifier 객체입니다. 자세한 내용은 키 ID 추적을 참고하세요.
  • InputRemappingOption: InputEnums.REMAP_OPTION_ENABLED 또는 InputEnums.REMAP_OPTION_DISABLED 중 하나입니다. 사용 중지되면 이 그룹에 속하는 모든 InputAction 객체는 재매핑 옵션을 사용 설정으로 지정해도 재매핑이 사용 중지됩니다 사용 설정되면 이 그룹에 속하는 모든 작업을 재매핑할 수 있습니다. 단, 개별 작업별로 사용 중지로 지정한 경우는 예외입니다.

입력 컨텍스트 정의

InputContexts를 사용하면 게임에서 다양한 게임 장면에 다양한 조합의 키보드 컨트롤을 사용할 수 있습니다. 예를 들면 다음과 같습니다.

  • 게임에서 메뉴 탐색과 이동에 다른 세트의 입력을 지정할 수 있습니다.
  • 운전과 걷기 등 게임 내 이동 모드에 따라 서로 다른 입력 세트를 지정할 수 있습니다.
  • 오버월드 탐색 또는 개별 레벨 플레이 등 게임의 현재 상태에 따라 서로 다른 입력 세트를 지정할 수 있습니다.

InputContexts를 사용할 때 오버레이는 사용 중인 컨텍스트 그룹을 먼저 표시합니다. 이 동작을 사용 설정하려면 게임이 다른 장면으로 전환될 때마다 setInputContext()를 호출하여 컨텍스트를 설정하세요. 다음 이미지는 이 동작을 보여줍니다. '운전' 장면에서 도로 컨트롤 작업이 오버레이 상단에 표시됩니다. '스토어' 메뉴를 열면 '메뉴 컨트롤' 작업이 오버레이 상단에 표시됩니다.

오버레이의 그룹을 정렬하는 InputContexts

이러한 오버레이 업데이트는 게임의 다양한 지점에서 다른 InputContext를 설정하여 실행됩니다. 이렇게 하려면 다음을 실행하세요.

  1. InputGroups를 사용하여 InputActions를 논리적으로 관련된 작업으로 그룹화합니다.
  2. 이러한 InputGroups를 게임의 다양한 부분에 관한 InputContext에 할당합니다.

동일한 InputContext에 속하는 InputGroups는 동일한 키가 사용되는 충돌하는 InputActions를 보유할 수 없습니다. 각 InputGroup을 단일 InputContext에 할당하는 것이 좋습니다.

다음 샘플 코드는 InputContext 로직을 보여줍니다.

Kotlin

companion object {
  val menuSceneInputContext = InputContext.create(
    "Menu",
    InputIdentifier.create(
      INPUTMAP_VERSION,
      InputContextIds.MENU_SCENE.ordinal.toLong()),
    listOf(basicMenuNavigationInputGroup, menuActionsInputGroup))

  val gameSceneInputContext = InputContext.create(
    "Game",
    InputIdentifier.create(
      INPUTMAP_VERSION,
      InputContextIds.GAME_SCENE.ordinal.toLong()),
    listOf(
      movementInputGroup,
      mouseActionsInputGroup,
      emojisInputGroup,
      gameActionsInputGroup))
}

Java

public static final InputContext menuSceneInputContext = InputContext.create(
        "Menu",
        InputIdentifier.create(
                INPUTMAP_VERSION,
                InputContextIds.MENU_SCENE.ordinal()),
        Arrays.asList(
                basicMenuNavigationInputGroup,
                menuActionsInputGroup
        )
);

public static final InputContext gameSceneInputContext = InputContext.create(
        "Game",
        InputIdentifier.create(
                INPUTMAP_VERSION,
                InputContextIds.GAME_SCENE.ordinal()),
        Arrays.asList(
                movementInputGroup,
                mouseActionsInputGroup,
                emojisInputGroup,
                gameActionsInputGroup
        )
);

C#

public static readonly InputContext menuSceneInputContext = InputContext.Create(
    "Menu",
    InputIdentifier.Create(
        INPUT_MAP_VERSION,
        (long)InputContextsIds.MENU_SCENE),
    new[]
    {
        basicMenuNavigationInputGroup,
        menuActionsInputGroup
    }.ToJavaList()
);

public static readonly InputContext gameSceneInputContext = InputContext.Create(
    "Game",
    InputIdentifier.Create(
        INPUT_MAP_VERSION,
        (long)InputContextsIds.GAME_SCENE),
    new[]
    {
        movementInputGroup,
        mouseActionsInputGroup,
        emojisInputGroup,
        gameActionsInputGroup
    }.ToJavaList()
);

InputContext에는 다음과 같은 필드가 있습니다.

  • LocalizedContextLabel: 컨텍스트에 속하는 그룹을 설명하는 문자열입니다.
  • InputContextId: InputContext의 숫자 ID와 버전을 저장하는 InputIdentifier 객체입니다(자세한 내용은 키 ID 추적 참고).
  • ActiveGroups: 이 컨텍스트가 활성 상태일 때 사용되고 오버레이 상단에 표시될 InputGroups 목록입니다.

입력 맵 빌드

InputMap은 게임에서 사용할 수 있는 모든 InputGroup 객체의 모음이므로 플레이어가 실행할 것으로 예상할 수 있는 모든 InputAction 객체입니다.

키 결합을 보고할 때 게임에서 사용되는 모든 InputGroups가 포함된 InputMap을 빌드합니다.

게임에서 재매핑을 지원하지 않으면 재매핑 옵션을 사용 중지로 설정하고 예약된 키를 비웁니다.

다음 예에서는 InputGroups 모음을 보고하는 데 사용되는 InputMap을 빌드합니다.

Kotlin

companion object {
  val gameInputMap = InputMap.create(
    listOf(
      basicMenuNavigationInputGroup,
      menuActionKeysInputGroup,
      movementInputGroup,
      mouseMovementInputGroup,
      pauseMenuInputGroup),
    MouseSettings.create(true, false),
    InputIdentifier.create(INPUTMAP_VERSION, INPUT_MAP_ID.toLong()),
    InputEnums.REMAP_OPTION_ENABLED,
    // Use ESCAPE as reserved remapping key
    listof(InputControls.create(listOf(KeyEvent.KEYCODE_ESCAPE), emptyList()))
  )
}

Java

public static final InputMap gameInputMap = InputMap.create(
        Arrays.asList(
                basicMenuNavigationInputGroup,
                menuActionKeysInputGroup,
                movementInputGroup,
                mouseMovementInputGroup,
                pauseMenuInputGroup),
        MouseSettings.create(true, false),
        InputIdentifier.create(INPUTMAP_VERSION, INPUT_MAP_ID),
        REMAP_OPTION_ENABLED,
        // Use ESCAPE as reserved remapping key
        Arrays.asList(
                InputControls.create(
                        Collections.singletonList(KeyEvent.KEYCODE_ESCAPE),
                        Collections.emptyList()
                )
        )
);

C#

public static readonly InputMap gameInputMap = InputMap.Create(
    new[]
    {
        basicMenuNavigationInputGroup,
        menuActionKeysInputGroup,
        movementInputGroup,
        mouseMovementInputGroup,
        pauseMenuInputGroup,
    }.ToJavaList(),
    MouseSettings.Create(true, false),
    InputIdentifier.Create(INPUT_MAP_VERSION, INPUT_MAP_ID),
    InputEnums.REMAP_OPTION_ENABLED,
    // Use ESCAPE as reserved remapping key
    new[]
    {
        InputControls.Create(
            New[] {
            new Integer(AndroidKeyCode.KEYCODE_ESCAPE)
        }.ToJavaList(),
        new ArrayList<Integer>())
    }.ToJavaList()
);

InputMap에는 다음과 같은 필드가 있습니다.

  • InputGroups: 게임에서 보고하는 InputGroups입니다. 그룹은 순서대로 오버레이에 표시됩니다. 단, setInputContext()를 호출하여 사용 중인 현재 그룹을 지정하는 경우는 예외입니다.
  • MouseSettings: MouseSettings 객체는 마우스 민감도를 조정할 수 있고 마우스가 y축에서 반전되어 있음을 나타냅니다.
  • InputMapId: InputMap의 숫자 ID와 버전을 저장하는 InputIdentifier 객체입니다(자세한 내용은 키 ID 추적 참고).
  • InputRemappingOption: InputEnums.REMAP_OPTION_ENABLED 또는 InputEnums.REMAP_OPTION_DISABLED 중 하나입니다. 재매핑 기능이 사용 설정되어 있는지 정의합니다.
  • ReservedControls: 사용자가 재매핑할 수 없는 InputControls 목록입니다.

키 ID 추적

InputAction, InputGroup, InputContext, InputMap 객체에는 고유한 숫자 ID와 문자열 버전 ID를 저장하는 InputIdentifier 객체가 포함되어 있습니다. 객체의 문자열 버전을 추적하는 것은 선택사항이지만 InputMap의 버전을 추적하는 것이 좋습니다. 문자열 버전이 제공되지 않으면 문자열은 비어 있습니다. 문자열 버전은 InputMap 객체에 필요합니다.

다음 예에서는 문자열 버전을 InputActions 또는 InputGroups에 할당합니다.

Kotlin

class InputSDKProviderKotlin : InputMappingProvider {
  companion object {
    const val INPUTMAP_VERSION = "1.0.0"
    private val enterMenuInputAction = InputAction.create(
      "Enter menu",
      InputControls.create(listOf(KeyEvent.KEYCODE_ENTER), emptyList()),
      InputIdentifier.create(
        INPUTMAP_VERSION, InputActionsIds.ENTER_MENU.ordinal.toLong()),
      InputEnums.REMAP_OPTION_ENABLED
    )

    private val movementInputGroup  = InputGroup.create(
      "Basic movement",
      listOf(
        moveUpInputAction,
        moveLeftInputAction,
        moveDownInputAction,
        mouseGameInputAction),
      InputIdentifier.create(
        INPUTMAP_VERSION, InputGroupsIds.BASIC_MOVEMENT.ordinal.toLong()),
      InputEnums.REMAP_OPTION_ENABLED)
  }
}

Java

public class InputSDKProvider implements InputMappingProvider {
    public static final String INPUTMAP_VERSION = "1.0.0";

    private static final InputAction enterMenuInputAction = InputAction.create(
            "Enter menu",
            InputControls.create(
                    Collections.singletonList(KeyEvent.KEYCODE_ENTER),
                    Collections.emptyList()),
            InputIdentifier.create(
                    INPUTMAP_VERSION, InputActionsIds.ENTER_MENU.ordinal()),
            InputEnums.REMAP_OPTION_ENABLED
    );

    private static final InputGroup movementInputGroup = InputGroup.create(
            "Basic movement",
            Arrays.asList(
                    moveUpInputAction,
                    moveLeftInputAction,
                    moveDownInputAction,
                    moveRightInputAction,
                    mouseGameInputAction
            ),
            InputIdentifier.create(
                    INPUTMAP_VERSION, InputGroupsIds.BASIC_MOVEMENT.ordinal()),
            InputEnums.REMAP_OPTION_ENABLED
    );
}

C#

#if PLAY_GAMES_PC

using Java.Lang;
using Java.Util;
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel;

public class InputSDKMappingProvider : InputMappingProviderCallbackHelper
{
    public static readonly string INPUT_MAP_VERSION = "1.0.0";

    private static readonly InputAction enterMenuInputAction =
        InputAction.Create(
            "Enter menu",
            InputControls.Create(
                new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE)}.ToJavaList(),
                new ArrayList<Integer>()),
            InputIdentifier.Create(
                INPUT_MAP_VERSION,
                (long)InputEventIds.ENTER_MENU),
            InputEnums.REMAP_OPTION_ENABLED
        );

    private static readonly InputGroup movementInputGroup = InputGroup.Create(
        "Basic movement",
        new[]
        {
            moveUpInputAction,
            moveLeftInputAction,
            moveDownInputAction,
            moveRightInputAction,
            mouseGameInputAction
        }.ToJavaList(),
        InputIdentifier.Create(
            INPUT_MAP_VERSION,
            (long)InputGroupsIds.BASIC_MOVEMENT),
        InputEnums.REMAP_OPTION_ENABLED
    );
}
#endif

InputAction 객체 숫자 ID는 InputMap의 모든 InputActions에서 고유해야 합니다. 마찬가지로 InputGroup 객체 ID도 InputMap의 모든 InputGroups에서 고유해야 합니다. 다음 샘플은 enum을 사용하여 객체의 고유 ID를 추적하는 방법을 보여줍니다.

Kotlin

enum class InputActionsIds {
    NAVIGATE_UP,
    NAVIGATE_DOWN,
    ENTER_MENU,
    EXIT_MENU,
    // ...
    JUMP,
    RUN,
    EMOJI_1,
    EMOJI_2,
    // ...
}

enum class InputGroupsIds {
    // Main menu scene
    BASIC_NAVIGATION, // WASD, Enter, Backspace
    MENU_ACTIONS, // C: chat, Space: quick game, S: store
    // Gameplay scene
    BASIC_MOVEMENT, // WASD, space: jump, Shift: run
    MOUSE_ACTIONS, // Left click: shoot, Right click: aim
    EMOJIS, // Emojis with keys 1,2,3,4 and 5
    GAME_ACTIONS, // M: map, P: pause, R: reload
}

enum class InputContextIds {
    MENU_SCENE, // Basic menu navigation, menu actions
    GAME_SCENE, // Basic movement, mouse actions, emojis, game actions
}

const val INPUT_MAP_ID = 0

Java

public enum InputActionsIds {
    NAVIGATE_UP,
    NAVIGATE_DOWN,
    ENTER_MENU,
    EXIT_MENU,
    // ...
    JUMP,
    RUN,
    EMOJI_1,
    EMOJI_2,
    // ...
}

public enum InputGroupsIds {
    // Main menu scene
    BASIC_NAVIGATION, // WASD, Enter, Backspace
    MENU_ACTIONS, // C: chat, Space: quick game, S: store
    // Gameplay scene
    BASIC_MOVEMENT, // WASD, space: jump, Shift: run
    MOUSE_ACTIONS, // Left click: shoot, Right click: aim
    EMOJIS, // Emojis with keys 1,2,3,4 and 5
    GAME_ACTIONS, // M: map, P: pause, R: reload
}

public enum InputContextIds {
    MENU_SCENE, // Basic navigation, menu actions
    GAME_SCENE, // Basic movement, mouse actions, emojis, game actions
}

public static final long INPUT_MAP_ID = 0;

C#

public enum InputActionsIds
{
    NAVIGATE_UP,
    NAVIGATE_DOWN,
    ENTER_MENU,
    EXIT_MENU,
    // ...
    JUMP,
    RUN,
    EMOJI_1,
    EMOJI_2,
    // ...
}

public enum InputGroupsIds
{
    // Main menu scene
    BASIC_NAVIGATION, // WASD, Enter, Backspace
    MENU_ACTIONS, // C: chat, Space: quick game, S: store
    // Gameplay scene
    BASIC_MOVEMENT, // WASD, space: jump, Shift: run
    MOUSE_ACTIONS, // Left click: shoot, Right click: aim
    EMOJIS, // Emojis with keys 1,2,3,4 and 5
    GAME_ACTIONS, // M: map, P: pause, R: reload
}

public enum InputContextIds
{
    MENU_SCENE, // Basic navigation, menu actions
    GAME_SCENE, // Basic movement, mouse actions, emojis, game actions
}

public static readonly long INPUT_MAP_ID = 0;

InputIdentifier에는 다음과 같은 필드가 있습니다.

  • UniqueId: 지정된 세트의 입력 데이터를 고유하고 명확하게 식별하도록 설정된 고유한 숫자 ID입니다.
  • VersionString: 두 가지 버전의 입력 데이터 변경사항 간에 한 가지 버전의 입력 데이터를 식별하도록 설정된 인간이 읽을 수 있는 버전 문자열입니다.

재매핑 이벤트에 관한 알림 수신(선택사항)

재매핑 이벤트에 관한 알림을 수신하면 게임에서 사용되는 키를 알 수 있습니다. 따라서 게임은 작업 컨트롤을 표시하는 데 사용되는 게임 화면에 표시된 애셋을 업데이트할 수 있습니다.

다음 이미지는 이 동작의 예를 보여줍니다. G, P, S 키를 각각 J, X, T로 재매핑한 후 게임의 UI 요소가 사용자가 설정한 키를 표시하도록 업데이트됩니다.

InputRemappingListener 콜백을 사용하여 재매핑 이벤트에 반응하는 UI

이 기능은 InputRemappingListener 콜백을 등록하여 실행됩니다. 이 기능을 구현하려면 먼저 InputRemappingListener 인스턴스를 등록하세요.

Kotlin

class InputSDKRemappingListener : InputRemappingListener {
  override fun onInputMapChanged(inputMap: InputMap) {
    Log.i(TAG, "Received update on input map changed.")
    if (inputMap.inputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) {
      return
    }
    for (inputGroup in inputMap.inputGroups()) {
      if (inputGroup.inputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) {
        continue
      }
      for (inputAction in inputGroup.inputActions()) {
        if (inputAction.inputRemappingOption() != InputEnums.REMAP_OPTION_DISABLED) {
          // Found InputAction remapped by user
          processRemappedAction(inputAction)
        }
      }
    }
  }

  private fun processRemappedAction(remappedInputAction: InputAction) {
    // Get remapped action info
    val remappedControls = remappedInputAction.remappedInputControls()
    val remappedKeyCodes = remappedControls.keycodes()
    val mouseActions = remappedControls.mouseActions()
    val version = remappedInputAction.inputActionId().versionString()
    val remappedActionId = remappedInputAction.inputActionId().uniqueId()
    val currentInputAction: Optional<InputAction>
    currentInputAction = if (version == null || version.isEmpty()
      || version == InputSDKProvider.INPUTMAP_VERSION
    ) {
      getCurrentVersionInputAction(remappedActionId)
    } else {
      Log.i(TAG,
            "Detected version of user-saved input action defers from current version")
      getCurrentVersionInputActionFromPreviousVersion(
        remappedActionId, version)
    }
    if (!currentInputAction.isPresent) {
      Log.e(TAG, String.format(
        "can't find remapped input action with id %d and version %s",
        remappedActionId, if (version == null || version.isEmpty()) "UNKNOWN" else version))
      return
    }
    val originalControls = currentInputAction.get().inputControls()
    val originalKeyCodes = originalControls.keycodes()
    Log.i(TAG, String.format(
      "Found input action with id %d remapped from key %s to key %s",
      remappedActionId,
      keyCodesToString(originalKeyCodes),
      keyCodesToString(remappedKeyCodes)))

    // TODO: make display changes to match controls used by the user
  }

  private fun getCurrentVersionInputAction(inputActionId: Long): Optional<InputAction> {
    for (inputGroup in InputSDKProvider.gameInputMap.inputGroups()) {
      for (inputAction in inputGroup.inputActions()) {
        if (inputAction.inputActionId().uniqueId() == inputActionId) {
          return Optional.of(inputAction)
        }
      }
    }
    return Optional.empty()
  }

  private fun getCurrentVersionInputActionFromPreviousVersion(
    inputActionId: Long, previousVersion: String
  ): Optional<InputAction7gt; {
    // TODO: add logic to this method considering the diff between the current and previous
    //  InputMap.
    return Optional.empty()
  }

  private fun keyCodesToString(keyCodes: List<Int>): String {
    val builder = StringBuilder()
    for (keyCode in keyCodes) {
      if (!builder.toString().isEmpty()) {
        builder.append(" + ")
      }
      builder.append(keyCode)
    }
    return String.format("(%s)", builder)
  }

  companion object {
    private const val TAG = "InputSDKRemappingListener"
  }
}

Java

public class InputSDKRemappingListener implements InputRemappingListener {

    private static final String TAG = "InputSDKRemappingListener";

    @Override
    public void onInputMapChanged(InputMap inputMap) {
        Log.i(TAG, "Received update on input map changed.");
        if (inputMap.inputRemappingOption() ==
                InputEnums.REMAP_OPTION_DISABLED) {
            return;
        }
        for (InputGroup inputGroup : inputMap.inputGroups()) {
            if (inputGroup.inputRemappingOption() ==
                    InputEnums.REMAP_OPTION_DISABLED) {
                continue;
            }
            for (InputAction inputAction : inputGroup.inputActions()) {
                if (inputAction.inputRemappingOption() !=
                        InputEnums.REMAP_OPTION_DISABLED) {
                    // Found InputAction remapped by user
                    processRemappedAction(inputAction);
                }
            }
        }
    }

    private void processRemappedAction(InputAction remappedInputAction) {
        // Get remapped action info
        InputControls remappedControls =
            remappedInputAction.remappedInputControls();
        List<Integer> remappedKeyCodes = remappedControls.keycodes();
        List<Integer> mouseActions = remappedControls.mouseActions();
        String version = remappedInputAction.inputActionId().versionString();
        long remappedActionId = remappedInputAction.inputActionId().uniqueId();
        Optional<InputAction> currentInputAction;
        if (version == null || version.isEmpty()
                    || version.equals(InputSDKProvider.INPUTMAP_VERSION)) {
            currentInputAction = getCurrentVersionInputAction(remappedActionId);
        } else {
            Log.i(TAG, "Detected version of user-saved input action defers " +
                    "from current version");
            currentInputAction =
                    getCurrentVersionInputActionFromPreviousVersion(
                            remappedActionId, version);
        }
        if (!currentInputAction.isPresent()) {
            Log.e(TAG, String.format(
                    "input action with id %d and version %s not found",
                    remappedActionId, version == null || version.isEmpty() ?
                            "UNKNOWN" : version));
            return;
        }
        InputControls originalControls =
                currentInputAction.get().inputControls();
        List<Integer> originalKeyCodes = originalControls.keycodes();

        Log.i(TAG, String.format(
                "Found input action with id %d remapped from key %s to key %s",
                remappedActionId,
                keyCodesToString(originalKeyCodes),
                keyCodesToString(remappedKeyCodes)));

        // TODO: make display changes to match controls used by the user
    }

    private Optional<InputAction> getCurrentVersionInputAction(
            long inputActionId) {
        for (InputGroup inputGroup :
                    InputSDKProvider.gameInputMap.inputGroups()) {
            for (InputAction inputAction : inputGroup.inputActions()) {
                if (inputAction.inputActionId().uniqueId() == inputActionId) {
                    return Optional.of(inputAction);
                }
            }
        }
        return Optional.empty();
    }

    private Optional<InputAction>
            getCurrentVersionInputActionFromPreviousVersion(
                    long inputActionId, String previousVersion) {
        // TODO: add logic to this method considering the diff between your
        // current and previous InputMap.
        return Optional.empty();
    }

    private String keyCodesToString(List<Integer> keyCodes) {
        StringBuilder builder = new StringBuilder();
        for (Integer keyCode : keyCodes) {
            if (!builder.toString().isEmpty()) {
                builder.append(" + ");
            }
            builder.append(keyCode);
        }
        return String.format("(%s)", builder);
    }
}

C#

#if PLAY_GAMES_PC

using System.Text;
using Java.Lang;
using Java.Util;
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel;
using UnityEngine;

public class InputSDKRemappingListener : InputRemappingListenerCallbackHelper
{
    public override void OnInputMapChanged(InputMap inputMap)
    {
        Debug.Log("Received update on remapped controls.");
        if (inputMap.InputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED)
        {
            return;
        }
        List<InputGroup> inputGroups = inputMap.InputGroups();
        for (int i = 0; i < inputGroups.Size(); i ++)
        {
            InputGroup inputGroup = inputGroups.Get(i);
            if (inputGroup.InputRemappingOption()
                    == InputEnums.REMAP_OPTION_DISABLED)
            {
                continue;
            }
            List<InputAction> inputActions = inputGroup.InputActions();
            for (int j = 0; j < inputActions.Size(); j ++)
            {
                InputAction inputAction = inputActions.Get(j);
                if (inputAction.InputRemappingOption()
                        != InputEnums.REMAP_OPTION_DISABLED)
                {
                    // Found action remapped by user
                    ProcessRemappedAction(inputAction);
                }
            }
        }
    }

    private void ProcessRemappedAction(InputAction remappedInputAction)
    {
        InputControls remappedInputControls =
                remappedInputAction.RemappedInputControls();
        List<Integer> remappedKeycodes = remappedInputControls.Keycodes();
        List<Integer> mouseActions = remappedInputControls.MouseActions();
        string version = remappedInputAction.InputActionId().VersionString();
        long remappedActionId = remappedInputAction.InputActionId().UniqueId();
        InputAction currentInputAction;
        if (string.IsNullOrEmpty(version)
                || string.Equals(
                version, InputSDKMappingProvider.INPUT_MAP_VERSION))
        {
            currentInputAction = GetCurrentVersionInputAction(remappedActionId);
        }
        else
        {
            Debug.Log("Detected version of used-saved input action defers" +
                " from current version");
            currentInputAction =
                GetCurrentVersionInputActionFromPreviousVersion(
                    remappedActionId, version);
        }
        if (currentInputAction == null)
        {
            Debug.LogError(string.Format(
                "Input Action with id {0} and version {1} not found",
                remappedActionId,
                string.IsNullOrEmpty(version) ? "UNKNOWN" : version));
            return;
        }
        InputControls originalControls = currentInputAction.InputControls();
        List<Integer> originalKeycodes = originalControls.Keycodes();

        Debug.Log(string.Format(
            "Found Input Action with id {0} remapped from key {1} to key {2}",
            remappedActionId,
            KeyCodesToString(originalKeycodes),
            KeyCodesToString(remappedKeycodes)));
        // TODO: update HUD according to the controls of the user
    }

    private InputAction GetCurrentVersionInputAction(
            long inputActionId)
    {
        List<InputGroup> inputGroups =
            InputSDKMappingProvider.gameInputMap.InputGroups();
        for (int i = 0; i < inputGroups.Size(); i++)
        {
            InputGroup inputGroup = inputGroups.Get(i);
            List<InputAction> inputActions = inputGroup.InputActions();
            for (int j = 0; j < inputActions.Size(); j++)
            {
                InputAction inputAction = inputActions.Get(j);
                if (inputAction.InputActionId().UniqueId() == inputActionId)
                {
                    return inputAction;
                }
            }
        }
        return null;
    }

    private InputAction GetCurrentVersionInputActionFromPreviousVersion(
            long inputActionId, string version)
    {
        // TODO: add logic to this method considering the diff between your
        // current and previous InputMap.
        return null;
    }

    private string KeyCodesToString(List<Integer> keycodes)
    {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < keycodes.Size(); i ++)
        {
            Integer keycode = keycodes.Get(i);
            if (builder.Length > 0)
            {
                builder.Append(" + ");
            }
            builder.Append(keycode.IntValue());
        }
        return string.Format("({0})", builder.ToString());
    }
}
#endif

InputRemappingListener는 사용자가 저장한 재매핑 컨트롤을 로드한 후 시작 시 그리고 사용자가 키를 재매핑할 때마다 알림을 받습니다.

초기화

InputContexts를 사용하는 경우 초기 장면에 사용된 첫 번째 컨텍스트를 비롯하여 각 전환에서 컨텍스트를 새 장면으로 설정합니다. InputMap을 등록한 후 InputContext를 설정해야 합니다.

재매핑 이벤트에 관해 알림을 받도록 InputRemappingListeners를 사용하는 경우 InputMappingProvider를 등록하기 전에 InputRemappingListener를 등록하세요. 그러지 않으면 게임이 시작 시간에 중요한 이벤트를 놓칠 수 있습니다.

다음 샘플은 API를 초기화하는 방법을 보여줍니다.

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    if (isGooglePlayGamesOnPC()) {
        val inputMappingClient = Input.getInputMappingClient(this)
        // Register listener before registering the provider
        inputMappingClient.registerRemappingListener(InputSDKRemappingListener())
        inputMappingClient.setInputMappingProvider(
                InputSDKProvider())
        // Set the context after you have registered the provider.
        inputMappingClient.setInputContext(InputSDKProvider.menuSceneInputContext)
    }
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (isGooglePlayGamesOnPC()) {
        InputMappingClient inputMappingClient =
                Input.getInputMappingClient(this);
        // Register listener before registering the provider
        inputMappingClient.registerRemappingListener(
                new InputSDKRemappingListener());
        inputMappingClient.setInputMappingProvider(
                new InputSDKProvider());
        // Set the context after you have registered the provider
        inputMappingClient.setInputContext(InputSDKProvider.menuSceneInputContext);
    }
}

C#

#if PLAY_GAMES_PC
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.InputMapping.ExternalType.Android.Content;
using Google.LibraryWrapper.Java;
#endif

public class GameManager : MonoBehaviour
{
#if PLAY_GAMES_PC
    private InputSDKMappingProvider _inputMapProvider =
        new InputSDKMappingProvider();
    private InputMappingClient _inputMappingClient;
#endif

    public void Awake()
    {
#if PLAY_GAMES_PC
        Context context = (Context)Utils.GetUnityActivity().GetRawObject();
        _inputMappingClient = Google.Android.Libraries.Play.Games.Inputmapping
            .Input.GetInputMappingClient(context);
        // Register listener before registering the provider.
        _inputMappingClient.RegisterRemappingListener(
            new InputSDKRemappingListener());
        _inputMappingClient.SetInputMappingProvider(_inputMapProvider);
        // Register context after you have registered the provider.
       _inputMappingClient.SetInputContext(
           InputSDKMappingProvider.menuSceneInputContext);
#endif
    }
}

정리

게임이 종료될 때 InputMappingProvider 인스턴스와 모든 InputRemappingListener 인스턴스를 등록 취소하세요. 하지만 이렇게 하지 않아도 입력 SDK는 매우 지능적이어서 리소스 유출을 방지할 수 있습니다.

Kotlin

override fun onDestroy() {
    if (isGooglePlayGamesOnPC()) {
        val inputMappingClient = Input.getInputMappingClient(this)
        inputMappingClient.clearInputMappingProvider()
        inputMappingClient.clearRemappingListener()
    }

    super.onDestroy()
}

Java

@Override
protected void onDestroy() {
    if (isGooglePlayGamesOnPC()) {
        InputMappingClient inputMappingClient =
                Input.getInputMappingClient(this);
        inputMappingClient.clearInputMappingProvider();
        inputMappingClient.clearRemappingListener();
    }

    super.onDestroy();
}

C#

public class GameManager : MonoBehaviour
{
    private void OnDestroy()
    {
#if PLAY_GAMES_PC
        _inputMappingClient.ClearInputMappingProvider();
        _inputMappingClient.ClearRemappingListener();
#endif
    }
}

테스트

오버레이를 수동으로 열어 플레이어 환경을 확인하거나, 자동 테스트 및 확인을 위한 adb 셸을 통해 입력 SDK 구현을 테스트할 수 있습니다.

PC용 Google Play 게임즈 에뮬레이터는 일반적인 오류를 대상으로 입력 맵의 정확성을 확인합니다. 고유 ID 중복이나 여러 입력 맵 사용, 재매핑 규칙 위반(재매핑이 사용 설정된 경우)과 같은 시나리오의 경우 오버레이에는 다음과 같은 오류 메시지가 표시됩니다. 입력 SDK 오버레이

명령줄에서 adb를 사용하여 입력 SDK 구현을 확인합니다. 현재 입력 맵을 가져오려면 다음 adb shell 명령어를 사용합니다(MY.PACKAGE.NAME을 게임 이름으로 바꿈).

adb shell dumpsys input_mapping_service --get MY.PACKAGE.NAME

InputMap이 성공적으로 등록되면 다음과 같은 출력이 표시됩니다.

Getting input map for com.example.inputsample...
Successfully received the following inputmap:
# com.google.android.libraries.play.games.InputMap@d73526e1
input_groups {
  group_label: "Basic Movement"
  input_actions {
    action_label: "Jump"
    input_controls {
      keycodes: 51
      keycodes: 19
    }
    unique_id: 0
  }
  input_actions {
    action_label: "Left"
    input_controls {
      keycodes: 29
      keycodes: 21
    }
    unique_id: 1
  }
  input_actions {
    action_label: "Right"
    input_controls {
      keycodes: 32
      keycodes: 22
    }
    unique_id: 2
  }
  input_actions {
    action_label: "Use"
    input_controls {
      keycodes: 33
      keycodes: 66
      mouse_actions: MOUSE_LEFT_CLICK
      mouse_actions_value: 0
    }
    unique_id: 3
  }
}
input_groups {
  group_label: "Special Input"
  input_actions {
    action_label: "Jump"
    input_controls {
      keycodes: 51
      keycodes: 19
      keycodes: 62
      mouse_actions: MOUSE_LEFT_CLICK
      mouse_actions_value: 0
    }
    unique_id: 4
  }
  input_actions {
    action_label: "Duck"
    input_controls {
      keycodes: 47
      keycodes: 20
      keycodes: 113
      mouse_actions: MOUSE_RIGHT_CLICK
      mouse_actions_value: 1
    }
    unique_id: 5
  }
}
mouse_settings {
  allow_mouse_sensitivity_adjustment: true
  invert_mouse_movement: true
}

현지화

입력 SDK는 Android의 현지화 시스템을 사용하지 않습니다. 따라서 InputMap을 제출할 때 현지화된 문자열을 제공해야 합니다. 게임 엔진의 현지화 시스템을 사용할 수도 있습니다.

Proguard

Proguard를 사용하여 게임을 축소할 때는 Proguard 구성 파일에 다음 규칙을 추가하여 SDK가 최종 패키지에서 제거되지 않도록 합니다.

-keep class com.google.android.libraries.play.hpe.** { *; }
-keep class com.google.android.libraries.play.games.inputmapping.** { *; }

다음 단계

입력 SDK를 게임에 통합한 후에도 남은 PC용 Google Play 게임즈 요구사항을 계속 진행할 수 있습니다. 자세한 내용은 PC용 Google Play 게임즈 시작하기를 참고하세요.