头图

您好!在我的博客当中,我将持续挑选一些优质的国外技术文章进行翻译,如果文章内容翻译有误,欢迎在评论区指正,感谢:)

本文是Nakama系列的第二篇,第一篇的链接如下:https://segmentfault.com/a/1190000044502161

以下是原文链接:Tutorial: Making a Multiplayer Game with Nakama and Unity: Part 2/3 – The Knights of Unity

3.1 身份验证

在之前的文章当中,我们关注于如何设置 Nakama 并让它的所有组件。在这篇文章中我们将继续关注和多人游戏开发相关技术:身份验证。它帮助我们接受并发送数据至服务端;

3.1.1 会话认证

会话认证(Session Authentication)
Nakama 提供的几乎所有和服务器通信的方法,都需要用户先启动并维护一条与服务器的会话。在我们开启一条会话之前,我们需要先创建 Nakama。客户端需要包含一系列有用的方法,从而将数据发送至服务端。
以下是一段用于建立客户端到服务端连接的代码。

// NakamaSessionManager.cs

/// <summary> 
/// 用于建立客户端到服务端连接.
/// 包含与Nakama的服务器通信所需的有用方法列表
/// Contains a list of usefull methods required to communicate with Nakama server.
/// 不要直接使用它, 通过 <see cref="Client"/> 替代.
/// </summary>
private Client _client;

...

/// <summary> 
/// Used to establish connection between the client and the server.
/// Contains a list of usefull methods required to communicate with Nakama server.
/// </summary>
public Client Client
{
    get
    {
        if (_client == null)
        {
            // "defaultkey" should be changed when releasing the app
            // see https://heroiclabs.com/docs/install-configuration/#socket
            _client = new Client("defaultkey", _ipAddress, _port, false);
        }
        return _client;
    }
}

在将 Client 准备好后,当我们想要和服务端进行验证时,我们可以使用以下几种方法来验证 Nakama 设备:

  1. 使用 unique ID,将其分配给每个设备;
  2. 使用 Email 以及密码;
  3. 使用你的 Facebook、Google、Game Center 或者 Steam 账号;
  4. 自定义你的验证系统;

异步验证与异常判断
因为这个 Jolly Rogers Demo 的游戏架构,最合适的方式是使用设备 ID 加上 Facebook 账号验证来完成验证。以下是 NakamaSessionManager.cs 的代码;

//NakamaSessionManager.cs
 
/// <summary>
/// 通过ID验证一个新的会话. 如果这是第一次验证.
/// 当前设备已创建新账户.
/// </summary>
/// <returns>在每次call服务器成功的时候返回true.</returns>
private async Task<AuthenticationResponse> AuthenticateDeviceIdAsync()
{
    try
    {
        Session = await Client.AuthenticateDeviceAsync(_deviceId, null, false);
        Debug.Log("Device authenticated with token:" + Session.AuthToken);
        return AuthenticationResponse.Authenticated;
    }
    catch (ApiResponseException e)
    {
        if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
        {
            Debug.Log("Couldn't find DeviceId in database, creating new user; message: " + e);
            return await CreateAccountAsync();
        }
        else
        {
            Debug.LogError("An error has occured reaching Nakama server; message: " + e);
            return AuthenticationResponse.Error;
        }
    }
    catch (Exception e)
    {
        Debug.LogError("Counldn't connect to Nakama server; message: " + e);
        return AuthenticationResponse.Error;
    }
}

如你所见,当前 AuthenticateDeviceIdAsync() 方法展示了以上的异步任务实现方式:不适用 events 以及 callbacks,而是使用 Tasks。当我们使用 await 标识了一个需要使用的异步函数后,我们总是同时需要在当前的函数前使用一个 async ,Unity 将会创建一个新的线程并且一直阻塞当前进程,直到 Task 返回,结束线程阻塞状态。在这种方式下,主线程不会因此而阻塞,它允许我们在发送消息至服务器、消息在服务器进行处理的同时,可以正常进行游戏。

在网络上进行通信并不总是能成功的,因此当我们 calling 这个 AuthenticateDeviceAsync 方法时可能会抛出异常。为了确定在错误发生时候的错误情况,在报错时时我们需要检查错误状态码以及错误信息——在某些情况下,当前的异常不是由一些网络信号产生的,但是我们的服务器依然收到了不合法的数据。此时我们需要检查这个错误是否是 HttpStatusCode.NotFound , 在某些情况下,这个服务器会告诉我们当前用户尝试验证一个不存在的账号,并提示其需要在第一次使用时新建一个账号。

3.1.2 会话创建与验证设置

//NakamaSessionManager.cs
 
/// <summary>
/// 在Nakama服务器上创建新的账号ID:<see cref="_deviceId"/>.
/// </summary>
/// <returns>在账号成功创建时返回真.</returns>
private async Task<AuthenticationResponse> CreateAccountAsync()
{
    try
    {
        Session = await Client.AuthenticateDeviceAsync(_deviceId, null, true);
        return AuthenticationResponse.NewAccountCreated;
        }
    catch (Exception e)
    {
        Debug.LogError("Couldn't create account using DeviceId; message: " + e);
        return AuthenticationResponse.Error;
    }
}

在两种情况下,我们会使用 Client.AuthenticateDeviceAsync:它和 AuthenticateDeviceIdAsync 方法只有第三个参数不同,在 CreateAccountAsync 中第三个参数被传入为真,如果没有账号被绑定到当前的设备 ID,创建一个新的账号并且绑定发送者 ID。我们可以让服务器在第一次调用时创建一个新的账号,然后这样我们就可以在用户第一次登录时,实现一些自定义的逻辑,比如显示头像选择面板之类的功能。

与其在每次打开游戏的时候都进行一次身份验证,更建议在程序第一次运行时或者会话到期之后进行身份验证。我们可以保存当前会话的身份码通过 PlayerPrefs 技术,并在每次用户登录时恢复它。

//NakamaSessionManager.cs
 
/// <summary>
/// Stores Nakama session authentication token in player prefs
/// </summary>
private void StoreSessionToken()
{
    if (Session == null)
    {
        Debug.LogWarning("Session is null; cannot store in player prefs");
    }
    else
    {
        PlayerPrefs.SetString("nakama.authToken", Session.AuthToken);
    }
}

默认的会话持续时间设置为60秒:这适合于重视安全性的应用程序。然而,游戏行业并不总是需要这么多的安全保障,因此有些工作室会将会话的持续时间延长至数周、数月甚至数年。我们可以使用 YAML 配置文件来扩展它,该文件应该包含在你绑定的 volumes 文件中,这个文件的配置在 Part1 中已讲解。

3.2 Facebook 验证

Nakama for Unity 插件并没有附带适用 Facebook 的 SDK 或者任何其他的外部 API,因此我们需要自行下载 Facebook for Unity 的插件。在我们使用 Facebook 账号进行身份验证之前,我们需要先引导用户完成与 Facebook 的连接。

//NakamaSessionManager.cs
 
/// <summary>
/// 初始化与Facebook的连接
/// </summary>
/// <param name="handler">在Facebook授权后调用.</param>
public void ConnectFacebook(Action<FacebookResponse> handler)
{
    if (FB.IsInitialized == false)
    {
        FB.Init(() => InitializeFacebook(handler));
    }
    else
    {
        InitializeFacebook(handler);
    }
}

/// <summary>
/// Invoked by <see cref="FB.Init(InitDelegate, HideUnityDelegate, string)"/> callback.
/// 尝试使用Facebook帐户登录,并使用Nakama服务器来验证用户
/// </summary>
/// <param name="handler">Invoked after Facebook authorisation.</param>
private void InitializeFacebook(Action<FacebookResponse> handler)
{
    FB.ActivateApp();
    
    List<string> permissions = new List<string>();
    permissions.Add("public_profile");
    
    FB.LogInWithReadPermissions(permissions, async result =>
    {
        FacebookResponse response = await ConnectFacebookAsync(result);
        handler?.Invoke(response);
    });
}

/// <summary>
/// 将当前的Nakama账号和Facebook连接.
/// </summary>
private async Task<FacebookResponse> ConnectFacebookAsync(ILoginResult result)
{
    FacebookResponse response = await LinkFacebookAsync(result);
    if (response != FacebookResponse.Linked)
    {
        return response;
    }
    . . .
}

因为 Facebook SDK 是外部插件,因此我们需要处理不同的身份验证可能的错误情况。比如,当用户取消了 Facebook 的登录时,和 Facebook 的连接将会失败,或将给定的 Facebook 令牌分配给另一个帐户。

//NakamaSessionManager.cs
 
/// <summary>
/// 尝试使用Facebook的帐户验证当前用户;如果在Nakama中没发现使用过的facebook账号.
/// 在数据库中创建新的Nakama用户帐户
/// 连接到提供Facebook帐户连接的帐户.
/// </summary>
private async Task<FacebookResponse> LinkFacebookAsync(ILoginResult result = null)
{
    if (FB.IsLoggedIn == true)
    {
        string token = AccessToken.CurrentAccessToken.TokenString;
        try
        {
            // 此行代码为Nakama插件所提供,
            await Client.LinkFacebookAsync(Session, token, true);
            return FacebookResponse.Linked;
        }
        catch (ApiResponseException e)
        {
            if (e.StatusCode == System.Net.HttpStatusCode.Conflict)
            {
                return FacebookResponse.Conflict;
            }
            else
            {
                Debug.LogWarning("An error has occured reaching Nakama server; message: " + e);
                return FacebookResponse.Error;
            }
        }
        catch (Exception e)
        {
            Debug.LogWarning("An error has occured while connection with Facebook; message: " + e);
            return FacebookResponse.Error;
        }
    }
    else
    {
        if (result == null)
        {
            Debug.Log("Facebook not logged in. Call ConnectFacebook first");
            return FacebookResponse.NotInitialized;
        }
        else if (result.Cancelled == true)
        {
            Debug.Log("Facebook login canceled");
            return FacebookResponse.Cancelled;
        }
        else if (string.IsNullOrWhiteSpace(result.Error) == false)
        {
            Debug.Log("Facebook login failed with error: " + result.Error);
            return FacebookResponse.Error;
        }
        else
        {
            Debug.Log("Facebook login failed with no error message");
            return FacebookResponse.Error;
        }
    }
}

以上代码主要用于处理不同情况下产生的对应的异常。

3.3 总结

通过与用户进行身份认证,可以保证在和服务器的会话结束之后,依然可以和服务器进行正确的连接。对于简单的访问,我们可以使用 Unity 提供的 PlayerPrefs 来存储并恢复用于身份验证的 Token。在下一小节当中,我们将会讲解 Nakama 所提供的其他服务器功能、如何实现它们,以及相关的注意事项。


博尔赫斯的面孔
1 声望0 粉丝

向着黄昏与自由迁徙