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

您可以使用 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 Games 作为登录提供方:

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

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

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

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

    3. 登录方法标签页中,启用 Play Games 登录提供方,然后指定您项目的 Web 服务器客户端 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 客户端字段中,选择您的项目的 Web 客户端 ID。请确保此 ID 就是您在启用 Play Games 登录功能时指定的客户端 ID。
    3. 保存更改。
  5. Play Games services 配置页面上,再次点击添加凭据

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

将 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:21.2.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();