透過 C++ 使用 Google Play 遊戲服務進行驗證

您可以使用 Google Play Games 服務,讓玩家登入以 Firebase 建構並以 C++ 編寫的 Android 遊戲。如要透過 Firebase 使用 Google Play Games 服務登入功能,請先透過 Google Play Games 登入玩家,並在登入時要求 OAuth 2.0 授權碼。接著,將授權碼傳遞至 PlayGamesAuthProvider,產生 Firebase 憑證,即可用來向 Firebase 驗證。

事前準備

如要使用 Firebase Authentication,請先完成下列步驟:

  • 註冊 C++ 專案,並設定使用 Firebase。

    如果 C++ 專案已使用 Firebase,則專案已註冊並設定 Firebase。

  • Firebase C++ SDK 新增至 C++ 專案。

請注意,將 Firebase 新增至 C++ 專案時,您需要在Firebase控制台和開啟的 C++ 專案中執行工作 (例如從控制台下載 Firebase 設定檔,然後移至 C++ 專案)。

設定 Firebase 專案

  1. 指定應用程式的 SHA-1 指紋 (如果尚未指定)。

    1. Firebase 控制台中,依序前往「 設定」 >「一般」分頁

    2. 向下捲動至「您的應用程式」資訊卡,選取 Android 應用程式,然後在「SHA 憑證指紋」欄位中新增 SHA-1 指紋。

    您可以使用 gradle signingReport 指令取得簽署憑證的 SHA 雜湊:

    ./gradlew signingReport

    如要瞭解如何取得應用程式的 SHA 指紋,請參閱「驗證用戶端」一文。

  2. 啟用 Google Play Games 做為登入資訊提供者:

    1. Firebase 控制台中,依序前往「安全性」 >「驗證」

    2. 產生並取得專案的網路伺服器用戶端 ID 和用戶端密鑰:

      1. 在「登入方式」分頁中,啟用「Google」登入供應商。

      2. Google 登入供應商複製網路伺服器用戶端 ID 和密鑰。

    3. 在「登入方法」分頁中,啟用Play Games登入供應商,並指定專案的網路伺服器用戶端 ID 和用戶端密鑰 (您在上一個步驟中取得)。

使用 Firebase 應用程式資訊設定 Play Games services

  1. Google Play 管理中心中,開啟 Google Play 應用程式或建立應用程式。

  2. 在「拓展」部分,依序點選 Play Games services >「設定與管理」>「設定」

  3. 按一下「是,我的遊戲使用了 Google API」,從清單中選取 Firebase 專案,然後按一下「使用」

  4. Play Games services 設定頁面中,按一下「新增憑證」

    1. 選取「遊戲伺服器」類型。
    2. 在「OAuth client」(OAuth 用戶端) 欄位中,選取專案的網路用戶端 ID。請務必使用啟用 Play Games 登入時指定的用戶端 ID。
    3. 儲存變更。
  5. Play Games services 設定頁面中,再次按一下「新增憑證」

    1. 選取「Android」類型。
    2. 在「OAuth client」(OAuth 用戶端) 欄位中,選取專案的 Android 用戶端 ID。 (如果沒有看到 Android 用戶端 ID,請務必在 Firebase 控制台中設定遊戲的 SHA-1 指紋。)
    3. 儲存變更。
  6. 在「測試人員」頁面中,新增所有需要在遊戲發布至 Play Store 前登入遊戲的使用者電子郵件地址。

將 Play 遊戲登入功能整合至遊戲

您必須先整合 Google Play 遊戲登入功能,才能讓玩家登入遊戲。

如要為 C++ Android 專案新增 Play 遊戲登入支援功能,最簡單且建議的方法是使用 Google 登入 C++ SDK

如要使用 Google 登入 C++ SDK 在遊戲中新增 Play 遊戲登入功能,請完成下列步驟:

  1. 複製或下載 Google 登入 Unity 外掛程式存放區,其中也包含 C++ SDK。

  2. 使用 Android Studio 或 gradlew build,建構 staging/native/ 目錄中的專案。

    建構作業會將輸出內容複製到名為 google-signin-cpp 的目錄。

  3. 在遊戲的原生程式碼 make 檔案中加入 Google 登入 C++ SDK:

    CMake

    在頂層 CMakeLists.txt 檔案中:

    set(GSI_PACKAGE_DIR "/path/to/google-signin-cpp")
    add_library(lib-google-signin-cpp STATIC IMPORTED) set_target_properties(lib-google-signin-cpp PROPERTIES IMPORTED_LOCATION     ${GSI_PACKAGE_DIR}/lib/${ANDROID_ABI}/libgoogle-signin-cpp.a )
    ...
    target_link_libraries(     ...     lib-google-signin-cpp)

    ndk-build

    Android.mk 檔案中:

    include $(CLEAR_VARS)
    LOCAL_MODULE := google-signin-cpp
    GSI_SDK_DIR := /path/to/google-signin-cpp
    LOCAL_SRC_FILES := $(GSI_SDK_DIR)/lib/$(TARGET_ARCH_ABI)/libgoogle-signin-cpp.a
    LOCAL_EXPORT_C_INCLUDES := $(GSI_SDK_DIR)/include
    include $(PREBUILT_STATIC_LIBRARY)

  4. 接著,加入 C++ SDK 必要的 Java 輔助元件。

    如要這麼做,請在專案層級的 build.gradle 檔案中,將 SDK 建構輸出目錄新增為本機存放區:

    allprojects {
        repositories {
            // ...
            flatDir {
                dirs 'path/to/google-signin-cpp'
            }
        }
    }
    

    在模組層級的 build.gradle 檔案中,將輔助元件宣告為依附元件:

    dependencies {
        implementation 'com.google.android.gms:play-services-auth:21.5.1'
        // Depend on the AAR built with the Google Sign-in SDK in order to add
        // the Java helper classes, which are used by the C++ library.
        compile(name:'google-signin-cpp-release', ext:'aar')
    }
    
  5. 接著,在遊戲中設定 GoogleSignIn 物件,以便使用 Play Games 登入功能並擷取伺服器授權碼:

    #include "google_signin.h"
    #include "future.h"
    
    using namespace google::signin;
    
    // ...
    
    GoogleSignIn::Configuration config = {};
    config.web_client_id = "YOUR_WEB_CLIENT_ID_HERE";
    config.request_id_token = false;
    config.use_game_signin = true;
    config.request_auth_code = true;
    
    GoogleSignIn gsi = GoogleSignIn(GetActivity(), GetJavaVM());
    gsi.Configure(config);
    
  6. 最後,呼叫 SignIn(),讓玩家登入 Play 遊戲:

    Future<GoogleSignIn::SignInResult> &future = gsi.SignIn();
    

    SignIn() 傳回的 Future 解決時,您可以從結果取得伺服器授權碼:

    if (!future.Pending()) {
        const GoogleSignIn::StatusCode status =
                static_cast<GoogleSignIn::StatusCode>(future.Status());
        if (status == GoogleSignIn::kStatusCodeSuccess) {
            // Player successfully signed in to Google Play! Get auth code to
            //   pass to Firebase
            const GoogleSignIn::SignInResult result =
                    static_cast<GoogleSignIn::SignInResult>(future.Result());
            const char* server_auth_code = result.User.GetServerAuthCode();
        }
    }
    

使用 Firebase 進行驗證

玩家透過 Play 遊戲登入後,您可以使用授權碼向 Firebase 進行驗證。

  1. 玩家使用 Play Games 成功登入後,請取得玩家帳戶的授權碼。

  2. 接著,將 Play 遊戲服務的授權碼換成 Firebase 憑證,並使用 Firebase 憑證驗證玩家:

    firebase::auth::Credential credential =
        firebase::auth::PlayGamesAuthProvider::GetCredential(server_auth_code);
    firebase::Future<firebase::auth::AuthResult> result =
        auth->SignInAndRetrieveDataWithCredential(credential);
    
  3. 如果您的程式有定期執行的更新迴圈 (例如每秒 30 或 60 次),您可以透過 Auth::SignInAndRetrieveDataWithCredentialLastResult 檢查每次更新的結果:

    firebase::Future<firebase::auth::AuthResult> result =
        auth->SignInAndRetrieveDataWithCredentialLastResult();
    if (result.status() == firebase::kFutureStatusComplete) {
      if (result.error() == firebase::auth::kAuthErrorNone) {
        firebase::auth::AuthResult auth_result = *result.result();
        printf("Sign in succeeded for `%s`\n",
               auth_result.user.display_name().c_str());
      } else {
        printf("Sign in failed with error '%s'\n", result.error_message());
      }
    }

    或者,如果您的程式是以事件為導向,您可能會偏好在 Future 上註冊回呼

在 Future 上註冊回呼

部分程式的 Update 函式每秒會呼叫 30 或 60 次。舉例來說,許多遊戲都採用這種模式。這些程式可以呼叫 LastResult 函式,輪詢非同步呼叫。 不過,如果您的程式是以事件驅動,可能比較適合註冊回呼函式。Future 完成後,系統會呼叫回呼函式。
void OnCreateCallback(const firebase::Future<firebase::auth::User*>& result,
                      void* user_data) {
  // The callback is called when the Future enters the `complete` state.
  assert(result.status() == firebase::kFutureStatusComplete);

  // Use `user_data` to pass-in program context, if you like.
  MyProgramContext* program_context = static_cast<MyProgramContext*>(user_data);

  // Important to handle both success and failure situations.
  if (result.error() == firebase::auth::kAuthErrorNone) {
    firebase::auth::User* user = *result.result();
    printf("Create user succeeded for email %s\n", user->email().c_str());

    // Perform other actions on User, if you like.
    firebase::auth::User::UserProfile profile;
    profile.display_name = program_context->display_name;
    user->UpdateUserProfile(profile);

  } else {
    printf("Created user failed with error '%s'\n", result.error_message());
  }
}

void CreateUser(firebase::auth::Auth* auth) {
  // Callbacks work the same for any firebase::Future.
  firebase::Future<firebase::auth::AuthResult> result =
      auth->CreateUserWithEmailAndPasswordLastResult();

  // `&my_program_context` is passed verbatim to OnCreateCallback().
  result.OnCompletion(OnCreateCallback, &my_program_context);
}
如果偏好使用 lambda,回呼函式也可以是 lambda。
void CreateUserUsingLambda(firebase::auth::Auth* auth) {
  // Callbacks work the same for any firebase::Future.
  firebase::Future<firebase::auth::AuthResult> result =
      auth->CreateUserWithEmailAndPasswordLastResult();

  // The lambda has the same signature as the callback function.
  result.OnCompletion(
      [](const firebase::Future<firebase::auth::User*>& result,
         void* user_data) {
        // `user_data` is the same as &my_program_context, below.
        // Note that we can't capture this value in the [] because std::function
        // is not supported by our minimum compiler spec (which is pre C++11).
        MyProgramContext* program_context =
            static_cast<MyProgramContext*>(user_data);

        // Process create user result...
        (void)program_context;
      },
      &my_program_context);
}

後續步驟

使用者首次登入後,系統會建立新的使用者帳戶,並連結至 Play 遊戲 ID。這個新帳戶會儲存在 Firebase 專案中,可用於識別專案中每個應用程式的使用者。

在遊戲中,您可以從 firebase::auth::User 物件取得使用者的 Firebase UID:

firebase::auth::User user = auth->current_user();
if (user.is_valid()) {
  std::string playerName = user.displayName();

  // The user's ID, unique to the Firebase project.
  // Do NOT use this value to authenticate with your backend server,
  // if you have one. Use firebase::auth::User::Token() instead.
  std::string uid = user.uid();
}

在 Firebase 即時資料庫和 Cloud Storage 安全性規則中,您可以從 auth 變數取得已登入使用者的專屬使用者 ID,並用來控管使用者可存取的資料。

如要取得使用者的 Play 遊戲玩家資訊或存取 Play 遊戲服務,請使用 Google Play 遊戲服務 C++ SDK 提供的 API。

如要登出使用者,請呼叫 SignOut()

auth->SignOut();