啟用伺服器端存取 Google Play 遊戲服務

如果您的遊戲使用後端伺服器,建議您使用 Google 登入驗證玩家,並安全地將玩家身分傳遞至後端伺服器。這也能讓遊戲以安全的方式擷取玩家身分資料和其他資料,避免在透過裝置傳遞的過程中可能遭到竄改的風險。

在這種情況下,遊戲會提示玩家照常登入 Google Play 遊戲服務。玩家成功登入後,GoogleSignInAccount 物件就會包含一組一次性代碼 (稱為「伺服器驗證碼」),用戶端會將這組代碼傳遞至伺服器。接著,在伺服器上交換伺服器驗證碼,以取得伺服器可用以呼叫 Google Play 遊戲服務 API 的 OAuth 2.0 權杖。

如需在遊戲中加入登入功能的其他指引,請參閱「Android 遊戲登入」。

如需詳細的程式碼範例,說明如何使用 Google 登入功能驗證玩家,請參閱 GitHub 上的 clientserverskeleton 範例

離線存取必須採取以下步驟:

  1. 在 Google Play 管理中心:建立遊戲伺服器的憑證。憑證的 OAuth 用戶端類型為「網站」。
  2. 在 Android 應用程式:在登入時要求取得伺服器憑證的伺服器驗證碼,然後將該驗證碼傳遞至伺服器。
  3. 在遊戲伺服器:使用 Google 驗證服務交換 OAuth 存取權杖的伺服器驗證碼,然後使用此驗證碼呼叫 Play 遊戲服務 REST API

事前準備

您必須先在 Google Play 管理中心中新增遊戲 (如「設定 Google Play 遊戲服務」所述),才能將 Google 登入功能整合至遊戲。

為遊戲建立相關的伺服器端網頁應用程式

Google Play 遊戲服務無法提供網路遊戲的後端支援,但是會提供 Android 遊戲伺服器的後端伺服器支援。

如果要在伺服器端應用程式中使用 Google Play 遊戲服務的 REST API,請按照下列步驟操作:

  1. Google Play 管理中心的「已連結的應用程式」部分,為遊戲建立相關聯的網頁應用程式。請注意,launch_url 不會用於此流程,因此可以留空。
  2. 如要取得應用程式的憑證資訊,請按照下列步驟操作:
    1. 在 Google Play 管理中心的遊戲中,按一下「遊戲詳細資料」
    2. 捲動至「API 控制台專案」專區,然後點選 API 控制台專案的連結。
    3. 在 Google API 控制台的「API 和服務」>「憑證」畫面中,下載網頁應用程式的 client_secret.json 檔案,並儲存至伺服器可存取的位置。記下憑證的用戶端 ID,以供日後參考。
  3. 重新啟動伺服器端應用程式,讓應用程式準備好接受遊戲用戶端應用程式的要求。

在用戶端執行登入作業

GoogleSignInClient 類別是擷取目前已登入的玩家帳戶的主要進入點,如果玩家尚未在裝置上登入應用程式,則可讓玩家登入。

如要建立登入用戶端,請按照下列步驟操作:

  1. 透過 GoogleSignInOptions 物件建立登入用戶端。如要設定登入功能,您必須在 GoogleSignInOptions.Builder 中指定 GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN
  2. 您也必須指定遊戲需要後端伺服器的驗證碼,方法是呼叫 GoogleSignInOptions.Builder.requestServerAuthCode() 方法,並將伺服器的用戶端 ID 做為參數。您稍後會在後端伺服器上擷取驗證碼,以取得存取權杖,如「取得伺服器驗證碼」一節所述。
  3. 呼叫 GoogleSignIn.getClient() 方法,並傳入先前設定的選項。如果呼叫成功,Google 登入 API 會傳回 GoogleSignInClient 的例項。
  4. 取得 GoogleSignInClient 例項後,您應繼續從活動的 onResume() 靜默登入玩家,如「執行靜默登入」一文所述。

範例如下:

private static final int RC_SIGN_IN = 9001;
private GoogleSignInClient mGoogleSignInClient;

private void startSignInForAuthCode() {

  // Client ID for your backend server.
  String webClientId = getString(R.string.webclient_id);

  GoogleSignInOptions signInOption = new
      GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
      .requestServerAuthCode(webClientId)
      .build();

  GoogleSignInClient signInClient = GoogleSignIn.getClient(this, signInOption);
  Intent intent = signInClient.getSignInIntent();
  startActivityForResult(intent, RC_SIGN_IN);
}

取得伺服器驗證碼

如要擷取遊戲在後端伺服器取得存取權杖使用的伺服器驗證碼,請在 Google 登入成功登入玩家後傳回的 GoogleSignInAccount 物件上呼叫 getServerAuthCode() 方法。

範例如下:

// Auth code to send to backend server.
private String mServerAuthCode;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  if (requestCode == RC_SIGN_IN) {
    GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
    if (result.isSuccess()) {
      mServerAuthCode = result.getSignInAccount().getServerAuthCode();
    } else {
      String message = result.getStatus().getStatusMessage();
      if (message == null || message.isEmpty()) {
        message = getString(R.string.signin_other_error);
      }
      new AlertDialog.Builder(this).setMessage(message)
          .setNeutralButton(android.R.string.ok, null).show();
    }
  }
}

在伺服器上交換伺服器驗證碼以取得存取權杖

將伺服器驗證碼傳送至後端伺服器,以交換取得存取權和更新權杖。請使用存取權杖代表玩家呼叫 Google Play 遊戲服務 API,然後可選擇是否儲存更新權杖,方便在存取權杖過期時取得新的存取權杖。

下列程式碼片段展示如何以 Java 程式設計語言實作伺服器端程式碼,交換伺服器驗證碼以取得存取權杖。這會使用 clientserverskeleton 範例應用程式

/**
 * Exchanges the authcode for an access token credential.  The credential
 * is the associated with the given player.
 *
 * @param authCode - the non-null authcode passed from the client.
 * @param player   - the player object which the given authcode is
 *                 associated with.
 * @return the HTTP response code indicating the outcome of the exchange.
 */
private int exchangeAuthCode(String authCode, Player player) {
try {

    // The client_secret.json file is downloaded from the Google API
    // console.  This is used to identify your web application.  The
    // contents of this file should not be shared.
    //
    File secretFile = new File("client_secret.json");

    // If we don't have the file, we can't access any APIs, so return
    // an error.
    if (!secretFile.exists()) {
        log("Secret file : " + secretFile
                .getAbsolutePath() + "  does not exist!");
        return HttpServletResponse.SC_FORBIDDEN;
    }

    GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(
            JacksonFactory.getDefaultInstance(), new
            FileReader(secretFile));

    // Extract the application id of the game from the client id.
    String applicationId = extractApplicationId(clientSecrets
            .getDetails().getClientId());

    GoogleTokenResponse tokenResponse =
            new GoogleAuthorizationCodeTokenRequest(
            HTTPTransport,
            JacksonFactory.getDefaultInstance(),
            "https://oauth2.googleapis.com/token",
            clientSecrets.getDetails().getClientId(),
            clientSecrets.getDetails().getClientSecret(),
            authCode,
            "")
            .execute();

    log("hasRefresh == " + (tokenResponse.getRefreshToken() != null));
    log("Exchanging authCode: " + authCode + " for token");
    Credential credential = new Credential
            .Builder(BearerToken.authorizationHeaderAccessMethod())
            .setJsonFactory(JacksonFactory.getDefaultInstance())
            .setTransport(HTTPTransport)
            .setTokenServerEncodedUrl("https://www.googleapis.com/oauth2/v4/token")
            .setClientAuthentication(new HttpExecuteInterceptor() {
                @Override
                public void intercept(HttpRequest request)
                        throws IOException {
                        }
            })
            .build()
            .setFromTokenResponse(tokenResponse);

    player.setCredential(credential);

    // Now that we have a credential, we can access the Games API.
    PlayGamesAPI api = new PlayGamesAPI(player, applicationId,
            HTTPTransport, JacksonFactory.getDefaultInstance());

    // Call the verify method, which checks that the access token has
    // access to the Games API, and that the player id used by the
    // client matches the playerId associated with the accessToken.
    boolean ok = api.verifyPlayer();

    // Call a Games API on the server.
    if (ok) {
        ok = api.updatePlayerInfo();
        if (ok) {
            // persist the player.
            savePlayer(api.getPlayer());
        }
    }

    return ok ? HttpServletResponse.SC_OK :
            HttpServletResponse.SC_INTERNAL_SERVER_ERROR;

  } catch (IOException e) {
    e.printStackTrace();
  }
  return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
}

如要進一步瞭解如何代表已登入的玩家從後端伺服器存取 Google API,請參閱「啟用伺服器端存取權」。

處理玩家登出

如要讓玩家登出遊戲,請在 GoogleSignInClient 上呼叫 signOut() 方法。如需程式碼片段範例,請參閱「讓玩家登出」。

從伺服器呼叫 REST API

如需可用的 API 呼叫的完整說明,請參閱「Google Play 遊戲服務的 REST API」。

以下是幾個實用的 REST API 呼叫範例,歡迎參考:

球員

  • 想要取得已登入玩家的 ID 和設定檔資料嗎?以 'me' 做為 ID 呼叫 Players.get

六人行

請務必參閱「好友」指南,進一步瞭解好友功能。

成就

請務必詳閱「成就」指南,進一步瞭解成就。

排行榜

請務必參閱「排行榜」指南,進一步瞭解排行榜。

  • 想要取得遊戲中所有計分板的清單嗎?呼叫 Leaderboards.list
  • 玩家是否已完成遊戲?您可以將玩家分數提交至 Scores.submit,瞭解該分數是否為新的最高分記錄。
  • 想顯示排行榜嗎?從 Scores.list 取得資料,並向使用者顯示。
  • 使用 Scores.listWindow 找出接近使用者最高分的分數。
  • 如要進一步瞭解特定排行榜中的玩家得分 (舉例來說,如果玩家的排名是所有玩家中的前 12%),請呼叫 Scores.get
  • 您是否在對遊戲進行偵錯?請嘗試從 Management API 呼叫 Scores.reset,重設玩家在特定排行榜的所有分數