使用 Google Play 游戏服务进行身份验证 (C++)

您可以使用 Google Play 游戏服务让玩家登录到在 Firebase 上构建且以 C++ 编写的 Android 游戏。要使用 Google Play 游戏服务进行 Firebase 登录,请首先通过 Google Play 游戏让玩家登录,并在执行此操作时请求一个 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 指纹,请在 Firebase 控制台的设置页面中执行此操作。

    您可以使用 Gradle signingReport 命令获取签名证书的 SHA 哈希:

    ./gradlew signingReport

  2. 启用 Google Play 游戏作为登录提供方:

    1. 在 Firebase 控制台中,打开 Authentication 部分。

    2. 生成并获取您项目的 Web 服务器客户端 ID 和客户端密钥:

      1. 登录方法标签页中,启用 Google 登录提供方。

      2. Google 登录提供方复制 Web 服务器客户端 ID 和密钥。

    3. 登录方法标签页中,启用 Play 游戏登录提供方,然后指定您项目的 Web 服务器客户端 ID 和客户端密钥(即您在上一步复制的内容)。

为 Play 游戏服务配置您的 Firebase 应用信息

  1. Google Play 管理中心中,打开您的 Google Play 应用或创建一个应用。

  2. 在“拓展”部分,依次点击 Play 游戏服务 > 设置和管理 > 配置

  3. 点击是,我的游戏已经使用了 Google API,从列表中选择您的 Firebase 项目,然后点击使用

  4. 在 Play 游戏服务配置页面上,点击添加凭据

    1. 选择游戏服务器类型。
    2. OAuth 客户端字段中,选择您的项目的 Web 客户端 ID。请确保此 ID 就是您在启用 Play 游戏登录功能时指定的客户端 ID。
    3. 保存更改。
  5. 在 Play 游戏服务配置页面上,再次点击添加凭据

    1. 选择 Android 类型。
    2. OAuth 客户端字段中,选择项目的 Android 客户端 ID。(如果您没有看到自己的 Android 客户端 ID,请务必在 Firebase 控制台中设置游戏的 SHA-1 指纹。)
    3. 保存更改。
  6. 测试人员页面上,添加所有需要能够登录该游戏的用户的电子邮件地址,然后再将游戏发布到 Play 商店。

将 Play 游戏登录服务集成到您的游戏中

您必须先集成 Google Play 游戏登录服务,然后才能够让玩家登录游戏。

推荐使用 Google 登录 C++ SDK 为 C++ Android 项目添加 Play 游戏登录服务支持,这是最简单的方法。

要使用 Google 登录 C++ SDK 将 Play 游戏登录服务添加到您的游戏中,请执行以下操作:

  1. 克隆或下载 Google 登录 Unity 插件库,其中也包含 C++ SDK。

  2. 使用 Android Studio 或 gradlew build 构建 staging/native/ 目录中包含的项目。

    构建操作会将其输出内容复制到名为 google-signin-cpp 的目录中。

  3. 将 Google 登录 C++ SDK 添加到您游戏的原生代码 make 文件中:

    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:20.7.0'
        // 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 游戏登录服务以及检索服务器身份验证代码:

    #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 游戏成功登录后,为该玩家的帐号获取一个身份验证代码。

  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。
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 Realtime Database 和 Cloud Storage 安全规则中,您可以从 auth 变量获取已登录用户的唯一用户 ID,然后用其控制用户可以访问哪些数据。

要获取用户的 Play 游戏玩家信息或使用 Play 游戏服务,可使用 Google Play 游戏服务 C++ SDK 提供的 API。

如需将用户退出登录,请调用 SignOut()

auth->SignOut();