Làm quen với SDK Đầu vào

Tài liệu này mô tả cách thiết lập và hiển thị SDK Đầu vào trong những trò chơi có hỗ trợ Google Play Games trên máy tính. Các tác vụ bao gồm thêm SDK cho trò chơi của mình và tạo một bản đồ đầu vào, trong đó chứa sự gán đầu vào của trò chơi với hành động của người dùng.

Trước khi bạn bắt đầu

Trước khi thêm SDK Đầu vào vào trò chơi, bạn phải hỗ trợ hoạt động đầu vào bằng bàn phím và chuột thông qua hệ thống đầu vào của công cụ phát triển trò chơi.

SDK Đầu vào cung cấp thông tin cho Google Play Games trên máy tính về những chế độ điều khiển mà trò chơi của bạn sử dụng để người dùng có thể thấy những chế độ đó. SDK này cũng tuỳ ý cho phép gán lại bằng bàn phím cho người dùng.

Mỗi chế độ điều khiển là một InputAction (ví dụ: "J" cho "Nhảy") và bạn sẽ sắp xếp InputActions thành InputGroups. Một InputGroup có thể đại diện cho một chế độ khác trong trò chơi, chẳng hạn như "Lái xe", "Đi bộ" hoặc "Trình đơn chính". Bạn cũng có thể dùng InputContexts để cho biết nhóm nào đang hoạt động ở các thời điểm khác nhau của trò chơi.

Bạn có thể bật chế độ tự động xử lý hoạt động ánh xạ bằng bàn phím cho mình, nhưng nếu muốn cung cấp giao diện điều khiển hoạt động ánh xạ của riêng mình thì bạn có thể tắt chế độ ánh xạ lại SDK Đầu vào.

Sơ đồ trình tự sau đây mô tả cách thức API của SDK Đầu vào hoạt động:

Sơ đồ trình tự khi triển khai một trò chơi gọi API SDK Đầu vào và hoạt động tương tác của API đó với thiết bị Android.

Khi trò chơi của bạn triển khai SDK Đầu vào, các chế độ điều khiển của bạn sẽ hiển thị trong lớp phủ Google Play Games trên máy tính.

Lớp phủ Google Play Games trên máy tính

Lớp phủ Google Play Games trên máy tính ("lớp phủ") hiển thị các chế độ điều khiển do trò chơi của bạn xác định. Người dùng sẽ truy cập vào lớp phủ bất cứ lúc nào bằng cách nhấn tổ hợp phím Shift + Tab.

Lớp phủ Google Play Games trên máy tính.

Các phương pháp hay nhất để thiết kế các liên kết phím

Khi thiết kế liên kết phím, hãy cân nhắc các phương pháp hay nhất sau đây:

  • Nhóm InputActions vào InputGroups có liên quan về mặt logic để cải thiện khả năng điều hướng và khả năng được phát hiện của các chế độ điều khiển trong khi chơi.
  • Gán mỗi InputGroup cho tối đa một InputContext. InputMap chi tiết mang lại một trải nghiệm tốt hơn khi điều hướng các chế độ điều khiển của bạn trong lớp phủ.
  • Tạo một InputContext cho mỗi loại cảnh khác nhau của trò chơi. Thông thường, bạn có thể dùng một InputContext cho mọi cảnh "giống với trình đơn" của mình. Hãy sử dụng InputContexts khác cho mọi trò chơi nhỏ trong trò chơi của bạn hoặc cho các chế độ điều khiển thay thế của một cảnh.
  • Nếu 2 hành động được thiết kế để dùng cùng một phím trong cùng một InputContext, hãy tận dụng chuỗi nhãn như "Tương tác/Kích hoạt".
  • Nếu 2 phím được thiết kế để liên kết đến cùng một InputAction, hãy dùng 2 InputActions khác nhau để thực hiện cùng một hành động trong trò chơi. Bạn có thể tận dụng cùng một văn bản nhãn cho cả 2 InputActions, nhưng ID thì phải khác nhau.
  • Nếu một phím bổ trợ được áp dụng cho một loạt phím, hãy cân nhắc dùng một InputAction cho phím bổ trợ đó thay vì dùng nhiều InputActions cho các tổ hợp phím bổ trợ (ví dụ: dùng phím Shift cùng các phím W, A, S, D thay vì dùng các tổ hợp phím Shift + W, Shift + A, Shift + S, Shift + D).
  • Tính năng ánh xạ lại đầu vào sẽ tự động bị tắt khi người dùng ghi vào trường văn bản. Hãy áp dụng các phương pháp hay nhất khi triển khai các trường văn bản trong Android nhằm đảm bảo rằng Android có thể phát hiện các trường văn bản trong trò chơi của bạn và ngăn những phím được ánh xạ lại can thiệp vào các trường đó. Nếu trò chơi của bạn phải sử dụng các trường văn bản hiếm gặp, thì bạn có thể dùng setInputContext() với InputContext có chứa một danh sách InputGroups trống để tắt thủ công tính năng ánh xạ lại.
  • Nếu trò chơi của bạn hỗ trợ tính năng ánh xạ lại, hãy nhớ rằng việc cập nhật các liên kết phím là một thao tác nhạy cảm có thể xung đột với các phiên bản mà người dùng đã lưu. Tránh thay đổi ID của các chế độ điều khiển hiện có khi có thể.

Tính năng gán lại

Google Play Games trên máy tính hỗ trợ tính năng ánh xạ lại chế độ điều khiển bằng bàn phím dựa trên các liên kết phím mà trò chơi của bạn cung cấp thông qua SDK Đầu vào. Đây là tính năng không bắt buộc và có thể tắt hoàn toàn. Ví dụ: bạn nên cung cấp giao diện ánh xạ lại bàn phím của riêng mình. Để tắt tính năng ánh xạ lại cho trò chơi, bạn chỉ cần chỉ định tuỳ chọn ánh xạ lại ở trạng thái tắt cho InputMap (xem bài viết Tạo InputMap để biết thêm thông tin).

Để dùng tính năng này, người dùng cần mở lớp phủ rồi nhấp vào hành động mà họ muốn gán lại. Sau mỗi sự kiện ánh xạ lại, Google Play Games trên máy tính sẽ ánh xạ từng lựa chọn điều khiển do người dùng ánh xạ lại với các chế độ điều khiển mặc định mà trò chơi muốn nhận được, do đó, trò chơi của bạn không cần nhận biết về hoạt động ánh xạ lại của người chơi. Bạn có thể tuỳ ý cập nhật các thành phần dùng để hiển thị các chế độ điều khiển bằng bàn phím trong trò chơi của mình bằng cách thêm một lệnh gọi lại cho các sự kiện gán lại.

Thao tác gán lại phím

Google Play Games trên máy tính lưu trữ cục bộ các chế độ điều khiển được ánh xạ lại cho từng người dùng để họ có thể sử dụng cùng chế độ điều khiển đã lưu giữa các phiên chơi. Thông tin này chỉ được lưu trữ trên ổ đĩa dành cho nền tảng máy tính và không làm ảnh hưởng đến trải nghiệm trên thiết bị di động. Dữ liệu điều khiển sẽ bị xoá khi người dùng gỡ cài đặt hoặc cài đặt lại Google Play Games trên máy tính. Dữ liệu này không tồn tại vĩnh viễn trên nhiều thiết bị máy tính.

Để hỗ trợ tính năng gán lại trong trò chơi của bạn, hãy tránh các hạn chế sau:

Các hạn chế của tính năng gán lại

Bạn có thể tắt các tính năng ánh xạ lại trong trò chơi của mình nếu các liên kết phím có chứa bất kỳ trường hợp nào sau đây:

  • InputActions dùng nhiều phím không có một phím bổ trợ + một phím không phải phím bổ trợ. Ví dụ: tổ hợp phím Shift + A là hợp lệ nhưng tổ hợp phím A + B, Ctrl + Alt hoặc Shift + A + Tab thì không.
  • InputMap có chứa InputActions, InputGroups hoặc InputContexts có mã nhận dạng duy nhất bị lặp.

Các điểm hạn chế của tính năng gán lại

Khi thiết kế các liên kết phím để ánh xạ lại, hãy xem xét các điểm hạn chế sau:

  • Không hỗ trợ tính năng gán lại cho các tổ hợp phím. Ví dụ: người dùng không thể gán lại tổ hợp phím Shift + A cho Ctrl + B hoặc phím A cho tổ hợp phím Shift + A.
  • Không hỗ trợ tính năng ánh xạ lại cho InputActions bằng các nút trên chuột. Ví dụ: Không thể gán lại tổ hợp phím Shift + Nhấp chuột phải.

Kiểm thử tính năng gán lại phím trên Trình mô phỏng Google Play Games trên máy tính

Bạn có thể bật tính năng ánh xạ lại trong Trình mô phỏng Google Play Games trên máy tính bất cứ lúc nào bằng cách phát lệnh adb sau đây:

adb shell dumpsys input_mapping_service --set RemappingFlagValue true

Sự thay đổi lớp phủ như trong hình ảnh sau đây:

Lớp phủ khi tính năng gán lại phím đang bật.

Thêm SDK

Cài đặt SDK Đầu vào theo nền tảng phát triển của bạn.

Java và Kotlin

Tải SDK Đầu vào cho Java hoặc Kotlin bằng cách thêm một phần phụ thuộc vào tệp build.gradle ở cấp mô-đun:

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

Unity

SDK Đầu vào là một gói Unity tiêu chuẩn có một vài phần phụ thuộc.

Bạn cần cài đặt gói này cùng với tất cả các phần phụ thuộc. Có một số cách để cài đặt các gói này.

Cài đặt .unitypackage

Tải tệp unitypackage của SDK Đầu vào xuống cùng với tất cả các phần phụ thuộc của nó. Bạn có thể cài đặt .unitypackage bằng cách chọn Assets > Import package > Custom Package (Thành phần > Nhập gói > Gói tuỳ chỉnh) và tìm tệp bạn đã tải xuống.

Cài đặt bằng UPM

Ngoài ra, bạn có thể cài đặt gói này thông qua Trình quản lý gói của Unity bằng cách tải .tgz xuống và cài đặt các phần phụ thuộc của nó:

Cài đặt bằng OpenUPM

Bạn có thể cài đặt gói này bằng cách sử dụng OpenUPM.

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

Trò chơi mẫu

Để biết ví dụ về cách tích hợp với SDK Đầu vào, hãy xem AGDK Tunnel (Đường hầm AGDK) dành cho các trò chơi viết bằng Kotlin hoặc Java và Trivial Kart dành cho trò chơi viết bằng Unity.

Tạo các liên kết phím

Đăng ký các liên kết phím của bạn bằng cách tạo một InputMap và trả nó về bằng một InputMappingProvider. Ví dụ sau đây mô tả một 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

Xác định hành động đầu vào

Lớp InputAction dùng để liên kết một phím hoặc tổ hợp phím với một hành động trong trò chơi. InputActions phải có mã nhận dạng duy nhất trên tất cả InputActions.

Nếu đang hỗ trợ tính năng ánh xạ lại, thì bạn có thể xác định InputActions nào có thể được ánh xạ lại. Nếu trò chơi của bạn không hỗ trợ tính năng ánh xạ lại, thì bạn nên đặt tuỳ chọn ánh xạ lại ở trạng thái tắt cho tất cả InputActions, tuy nhiên, SDK Đầu vào có thể tắt tính năng ánh xạ lại nếu bạn không hỗ trợ tính năng này trong InputMap.

Ví dụ sau liên kết phím cách với hành động Drive (Lái xe).

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 bằng một phím hiển thị trong lớp phủ.

Các hành động cũng có thể đại diện cho thao tác đầu vào bằng chuột. Ví dụ sau gán thao tác Nhấp chuột trái cho hành động Move (Di chuyển):

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 bằng chuột hiển thị trong lớp phủ.

Các tổ hợp phím được chỉ định bằng cách chuyển nhiều mã phím vào InputAction của bạn. Trong ví dụ này, tổ hợp phím cách + shift được ánh xạ với hành động Turbo (Xoay), tổ hợp phím này hoạt động ngay cả khi phím Cách được ánh xạ với hành động Drive (Lái xe).

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 dùng nhiều phím hiển thị trong lớp phủ.

SDK Đầu vào cho phép bạn kết hợp các nút trên chuột và phím cho một hành động. Ví dụ này cho thấy một điểm tham chiếu sẽ được thêm vào trò chơi mẫu sau đây khi nhấn đồng thời phím ShiftNhấp chuột phải:

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 dùng tổ hợp phím + chuột hiển thị trong lớp phủ.

InputAction có các trường sau đây:

  • ActionLabel: là chuỗi hiển thị trong giao diện người dùng để thể hiện hành động này. Quá trình bản địa hoá không tự động được thực hiện, vì vậy, hãy thực hiện trước mọi quá trình bản địa hoá.
  • InputControls: xác định các chế độ điều khiển đầu vào mà hành động này sử dụng. Chế độ điều khiển liên kết với các ký tự nhất quán trong lớp phủ.
  • InputActionId: đối tượng InputIdentifier có chứa phiên bản và mã số của InputAction (xem phần Theo dõi mã nhận dạng phím để biết thêm thông tin).
  • InputRemappingOption: một trong số InputEnums.REMAP_OPTION_ENABLED hoặc InputEnums.REMAP_OPTION_DISABLED. Xác định xem hành động này có được phép gán lại không. Nếu trò chơi của bạn không hỗ trợ tính năng ánh xạ lại, thì bạn có thể bỏ qua trường này hoặc chỉ cần đặt trường này thành tắt.
  • RemappedInputControls: đối tượng InputControls chỉ đọc dùng để đọc phím được ánh xạ lại do người dùng đặt trên các sự kiện ánh xạ lại (dùng để nhận thông báo về sự kiện ánh xạ lại).

InputControls là đầu vào được liên kết với một hành động và có chứa các trường sau:

  • AndroidKeycodes: là danh sách các số nguyên tượng trưng cho đầu vào bằng bàn phím được liên kết với một hành động. Các số nguyên này được xác định trong lớp KeyEvent hoặc lớp AndroidKeycode cho Unity.
  • MouseActions: là danh sách các giá trị MouseAction tượng trưng cho đầu vào bằng chuột được liên kết với hành động này.

Xác định nhóm đầu vào

InputActions được nhóm với các hành động có liên quan về mặt logic bằng cách sử dụng InputGroups để cải thiện khả năng điều hướng và khả năng được phát hiện của các chế độ điều khiển trong lớp phủ. Mỗi mã InputGroup phải là duy nhất trên tất cả InputGroups trong trò chơi của bạn.

Bằng cách xếp hành động đầu vào thành các nhóm, bạn giúp người chơi tìm thấy đúng liên kết phím cho bối cảnh hiện tại của họ dễ dàng hơn.

Nếu đang hỗ trợ tính năng ánh xạ lại, thì bạn có thể xác định InputGroups nào có thể được ánh xạ lại. Nếu trò chơi của bạn không hỗ trợ tính năng ánh xạ lại, thì bạn nên đặt tuỳ chọn ánh xạ lại ở trạng thái tắt cho tất cả InputGroups, tuy nhiên, SDK Đầu vào có thể tắt tính năng ánh xạ lại nếu bạn không hỗ trợ tính năng này trong 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
);

Ví dụ sau đây hiển thị các nhóm đầu vào là Road controls (Điều khiển đường đi) và Menu controls (Điều khiển trình đơn) trong lớp phủ:

Lớp phủ hiển thị một InputMap có chứa các nhóm đầu vào là Road controls (Điều khiển đường đi) và Menu controls (Điều khiển trình đơn).

InputGroup có các trường sau đây:

  • GroupLabel: là một chuỗi hiển thị trong lớp phủ có thể dùng để nhóm một loạt hành động theo logic. Chuỗi này không tự động được bản địa hoá.
  • InputActions: là một danh sách các đối tượng InputAction mà bạn xác định trong bước trước. Tất cả những hành động này đều được hiển thị trực quan bên dưới tiêu đề nhóm.
  • InputGroupId: đối tượng InputIdentifier có chứa phiên bản và mã số của InputGroup. Hãy xem phần Theo dõi mã nhận dạng phím để biết thêm thông tin.
  • InputRemappingOption: một trong số InputEnums.REMAP_OPTION_ENABLED hoặc InputEnums.REMAP_OPTION_DISABLED. Nếu bị tắt, tất cả các đối tượng InputAction thuộc nhóm này sẽ bị tắt tính năng ánh xạ lại ngay cả khi chúng chỉ định tuỳ chọn ánh xạ lại ở trạng thái bật. Nếu được bật, tất cả các hành động thuộc nhóm này đều có thể ánh xạ lại trừ phi các hành động riêng lẻ chỉ định là tắt.

Xác định bối cảnh đầu vào

InputContexts cho phép trò chơi của bạn sử dụng một loạt chế độ điều khiển khác bằng bàn phím cho các cảnh khác nhau của trò chơi. Ví dụ:

  • Bạn có thể chỉ định các loạt đầu vào khác nhau để điều hướng trình đơn và di chuyển trong trò chơi.
  • Bạn có thể chỉ định các loạt đầu vào riêng tuỳ theo chế độ chuyển động trong trò chơi, chẳng hạn như lái xe và đi bộ.
  • Bạn có thể chỉ định các loạt đầu vào khác nhau tuỳ theo trạng thái hiện tại của trò chơi, chẳng hạn như khám phá tổng thể và chơi một cấp độ riêng lẻ.

Khi sử dụng InputContexts, trước tiên, lớp phủ này sẽ hiện các nhóm bối cảnh đang sử dụng. Để cho phép hành động này, hãy gọi setInputContext() để đặt bối cảnh bất cứ khi nào trò chơi của bạn chuyển sang một cảnh khác. Hình ảnh sau đây minh hoạ hành động này: trong cảnh "lái xe", hành động Road controls (Điều khiển đường đi) hiện ở đầu lớp phủ. Khi mở trình đơn "store" (cửa hàng), hành động "Menu controls" (Điều khiển trình đơn) hiển thị ở đầu lớp phủ.

Nhóm phân loại InputContexts trong lớp phủ.

Bạn sẽ thấy những thông tin cập nhật này về lớp phủ khi đặt một InputContext khác tại các thời điểm khác nhau trong trò chơi. Cách thực hiện việc này:

  1. Nhóm InputActions với các hành động có liên quan về mặt logic bằng InputGroups
  2. Chỉ định những InputGroups này cho một InputContext đối với các phần khác nhau trong trò chơi của bạn

InputGroups thuộc cùng một InputContext không thể có InputActions xung đột khi dùng cùng một phím. Tốt nhất là nên chỉ định từng InputGroup cho một InputContext.

Mã mẫu sau đây minh hoạ logic 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 có các trường sau đây:

  • LocalizedContextLabel: một chuỗi mô tả các nhóm thuộc bối cảnh này.
  • InputContextId: đối tượng InputIdentifier có chứa phiên bản và mã số của InputContext (xem phần Theo dõi mã nhận dạng phím để biết thêm thông tin).
  • ActiveGroups: một danh sách InputGroups để dùng và hiển thị ở đầu lớp phủ khi bối cảnh này ở trạng thái hoạt động.

Tạo một bản đồ đầu vào

InputMap là tập hợp tất cả các đối tượng InputGroup có trong một trò chơi, do đó sẽ có mọi đối tượng InputAction mà người chơi muốn thực hiện.

Khi báo cáo các liên kết phím, bạn sẽ tạo một InputMap bằng tất cả các InputGroups dùng trong trò chơi của mình.

Nếu trò chơi của bạn không hỗ trợ tính năng ánh xạ lại, hãy đặt tuỳ chọn ánh xạ lại ở trạng thái tắt và các phím dành riêng ở trạng thái trống.

Ví dụ sau đây tạo một InputMap dùng để báo cáo một tập hợp InputGroups.

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 có các trường sau đây:

  • InputGroups: InputGroups được trò chơi của bạn báo cáo. Các nhóm sẽ hiển thị theo thứ tự trong lớp phủ, trừ phi các nhóm hiện tại đang được sử dụng gọi setInputContext() chỉ định khác.
  • MouseSettings: Đối tượng MouseSettings cho biết rằng bạn có thể điều chỉnh độ nhạy của chuột và chuột được đảo ngược trên trục y.
  • InputMapId: đối tượng InputIdentifier có chứa phiên bản và mã số của InputMap (xem phần Theo dõi mã nhận dạng phím để biết thêm thông tin).
  • InputRemappingOption: một trong số InputEnums.REMAP_OPTION_ENABLED hoặc InputEnums.REMAP_OPTION_DISABLED. Xác định xem tính năng gán lại có được bật hay không.
  • ReservedControls: một danh sách InputControls mà người dùng sẽ không được phép gán lại.

Theo dõi mã nhận dạng phím

Các đối tượng InputAction, InputGroup, InputContextInputMap chứa một đối tượng InputIdentifier lưu trữ một mã phiên bản chuỗi và mã số duy nhất. Việc theo dõi phiên bản chuỗi của các đối tượng là không bắt buộc nhưng bạn nên theo dõi các phiên bản của InputMap. Nếu phiên bản chuỗi không được cung cấp, thì chuỗi đó trống. Bắt buộc phải có phiên bản chuỗi cho các đối tượng InputMap.

Ví dụ sau đây sẽ chỉ định một phiên bản chuỗi cho InputActions hoặc 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

Mã số của các đối tượng InputAction phải là duy nhất trên tất cả các InputActions trong InputMap. Tương tự, mã đối tượng InputGroup phải là duy nhất trên tất cả các InputGroups trong một InputMap. Mẫu sau đây minh hoạ cách dùng enum để theo dõi mã nhận dạng duy nhất của đối tượng:

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 có các trường sau đây:

  • UniqueId: một loạt mã số duy nhất dùng để xác định rõ một loạt dữ liệu đầu vào nhất định theo cách riêng.
  • VersionString: một loạt chuỗi phiên bản mà con người có thể đọc được để xác định một phiên bản dữ liệu đầu vào giữa 2 phiên bản thay đổi dữ liệu đầu vào.

Nhận thông báo về sự kiện gán lại (Không bắt buộc)

Nhận thông báo về các sự kiện gán lại để biết các phím đang được dùng trong trò chơi của bạn. Việc này cho phép trò chơi của bạn cập nhật các thành phần xuất hiện trên màn hình trò chơi dùng để hiển thị các chế độ điều khiển hành động.

Hình ảnh sau đây minh hoạ ví dụ về hành động này sau khi ánh xạ lại lần lượt các phím G, PS thành J, XT, các phần tử trên giao diện người dùng của trò chơi được cập nhật để hiển thị các phím do người dùng đặt.

Giao diện người dùng phản ứng với các sự kiện gán lại bằng cách sử dụng lệnh gọi lại inputRemappingListener.

Bạn có thể thực hiện chức năng này bằng cách đăng ký một lệnh gọi lại InputRemappingListener. Để triển khai tính năng này, hãy bắt đầu bằng cách đăng ký một thực thể 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 nhận được thông báo tại thời điểm chạy sau khi tải các chế độ điều khiển ánh xạ lại do người dùng lưu và sau mỗi lần người dùng ánh xạ lại các phím của họ.

Khởi tạo

Nếu bạn đang dùng InputContexts, hãy đặt bối cảnh trên mỗi lần chuyển đổi sang một cảnh mới, bao gồm cả bối cảnh đầu tiên dùng cho cảnh ban đầu của bạn. Bạn cần đặt InputContext sau khi đã đăng ký InputMap.

Nếu bạn đang dùng InputRemappingListeners để nhận thông báo về các sự kiện ánh xạ lại, hãy đăng ký InputRemappingListener trước khi đăng ký InputMappingProvider, nếu không, trò chơi của bạn có thể bỏ lỡ các sự kiện quan trọng trong thời gian ra mắt.

Mẫu sau đây trình bày cách khởi tạo 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
    }
}

Dọn dẹp

Huỷ đăng ký thực thể InputMappingProvider và mọi thực thể InputRemappingListener khi trò chơi của bạn chuyển sang trạng thái đóng, mặc dù SDK Đầu vào có thể tránh được tình trạng rò rỉ tài nguyên nếu bạn không làm vậy:

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
    }
}

Kiểm thử

Bạn có thể kiểm thử quá trình triển khai SDK Đầu vào bằng cách mở thủ công lớp phủ để xem trải nghiệm của người chơi hoặc thông qua adb shell để xác minh và kiểm thử tự động.

Trình mô phỏng Google Play Games trên máy tính kiểm tra tính chính xác của bản đồ đầu vào đối với các lỗi thường gặp. Đối với các trường hợp như trùng lặp mã nhận dạng duy nhất, sử dụng các bản đồ đầu vào khác nhau hoặc không tuân thủ các quy tắc ánh xạ lại (nếu tính năng ánh xạ lại ở trạng thái bật), lớp phủ sẽ hiện một thông báo lỗi như bên dưới: Lớp phủ SDK Đầu vào.

Xác minh quá trình triển khai SDK Đầu vào bằng adb ở dòng lệnh. Để tải bản đồ đầu vào hiện tại, hãy sử dụng lệnh adb shell sau (thay thế MY.PACKAGE.NAME bằng tên trò chơi của bạn):

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

Bạn sẽ thấy kết quả tương tự như sau nếu đăng ký thành công 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
}

Bản địa hoá

SDK Đầu vào không sử dụng hệ thống bản địa hoá của Android. Do đó, bạn phải cung cấp các chuỗi đã bản địa hoá khi gửi InputMap. Bạn cũng có thể dùng hệ thống bản địa hoá của công cụ phát triển trò chơi.

Proguard

Khi dùng Proguard để thu nhỏ trò chơi, hãy thêm các quy tắc sau vào tệp cấu hình proguard để đảm bảo SDK không bị loại bỏ khỏi gói cuối cùng của bạn:

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

Các bước tiếp theo

Sau khi tích hợp SDK Đầu vào với trò chơi của mình, bạn có thể tiếp tục thực hiện mọi yêu cầu còn lại của Google Play Games trên máy tính. Để biết thêm thông tin, hãy xem bài viết Bắt đầu dùng Google Play Games trên máy tính.