使用基于C#的EOS SDK

介绍EOS的C#版SDK

除了C SDK外,Epic在线服务 (EOS)还提供C# SDK。本文档将帮助你了解使用C# SDK将EOS与你的项目集成,并介绍这两者之间的一些实现差异。无论你使用哪种SDK,核心功能保持不变,C# SDK在设计上存在以下差异:

  • C# SDK遵循C#最佳实践和以对象为中心的方法,例如使用句柄对象而不是C样式句柄管理异步操作。

  • 命名规范与常用的C#模式一致。例如,C SDK中的 EOS_Auth_Login 可以作为 Epic.OnlineServices.Auth.AuthInterface.Login 访问。

  • C SDK中的数据结构需要有基于宏的API版本号,以便确保兼容性;这些值预先在C# SDK中填充。

EOS C# SDK包括面向桌面和移动平台(如Windows、Linux、Mac、iOS和Android)的库。用于受限平台(例如Xbox、Nintendo Switch和 PlayStation)的其他C# SDK版本在获得批准后提供。要访问受限SDK和相关文档,你必须配置你的组织访问权限,并且在第一方批准后,该文档将成为你的SDK下载的一部分。

如需更多相关信息和可用性信息,请询问你的技术客户经理。

C# SDK依赖性

对于C# SDK,你必须具备……

  • .NET框架3.5或更高版本或与API兼容的等效版本

  • 以前的版本可能可行,但尚未经过测试

对于C# SDK示例,你必须具备……

  • .NET核心3.1

  • Visual Studio 2019或更高版本

入门指南

在你开始之前,你需要下载C# SDK,并在Epic的开发人员门户中设置产品。你应该在 开发人员门户产品设置 中提供以下产品信息供SDK使用:

  • 产品ID

  • 沙盒ID

  • 部署ID

  • 客户端ID

  • 客户端密钥

当你开始集成EOS SDK时,你可以使用这些值创建平台界面,平台界面是访问所有其他EOS SDK界面必需的。你拥有这些信息后,你可以继续将SDK与你的产品集成。

集成

  1. 将C# SDK源文件包括在你的项目中。

    • (Unity用户) 将这些文件(包括核心(Core)和已生成(Generated)文件夹)放到你的 资产(Assets) 文件夹中。

  2. 确保你的应用可以访问目标平台的相应库二进制文件。

    • (Unity用户) 将相应的库二进制文件复制到你目标平台的 资产(Assets) 文件夹中。

    • 例如,Windows x64集成将使用 EOSSDK-Win64-Shipping.dll

  3. C# SDK将确定面向 Epic.OnlineServices.Config 的库二进制名称。为了方便起见,几个平台都预先配置,以指向正确的名称。

    • 如果你的目标平台尚未预先配置,请对配置和项目符号进行相应更改,避免构建错误。例如,如果面向Windows x64,你可能需要设置 EOS_PLATFORM_WINDOWS_64,如果面向Windows x86,你需要设置 EOS_PLATFORM_WINDOWS_32

  4. (Unity用户) 创建新脚本以控制SDK。对于此示例,我们使用名称 EOSSDKComponent

    新脚本组件

  5. (Unity用户) 要在必要时创建组件,请选择 添加组件(Add Component) 将其指定给实体。出于演示目的,我们将其添加到 主摄像机(Main Camera)

    添加组件

  6. 在此组件中编写你的EOS SDK代码。参见我们的示例代码了解示例。

接入SDK

设置产品并集成C# SDK后,你可以开始编写代码。你需要初始化SDK,定期调用 更新函数 方法,确保SDK可以执行,回调可以运行。当应用完成时,将其关闭。

管理SDK的生命周期

SDK的生命周期包括三个主要部分:初始化、更新函数(正常操作)和关闭。

初始化

平台界面是SDK的进入点,你需要此界面的实例才能使用EOS。要创建一个实例,请使用一些关于你应用的基本信息调用 Epic.OnlineServices.Platform.PlatformInterface.Initialize,然后使用你从开发人员门户获取的数值调用 Epic.OnlineServices.Platform.PlatformInterface.Create,以便获取 Epic.OnlineServices.Platform.PlatformInterface 实例。存储此实例,你将需要它与SDK交互。 例如,要执行身份验证,你首先需要一个 Epic.OnlineServices.Auth.AuthInterface 实例,然后,你可以通过在 Epic.OnlineServices.Platform.PlatformInterface 实例上调用 GetAuthInterface() 检索此实例。

你仅可初始化SDK一次;随后调用 Epic.OnlineServices.Platform.PlatformInterface.Initialize 将返回 AlreadyInitialized 故障代码。我们推荐在应用启动时初始化SDK,在应用关闭前不要释放它。

更新函数

为了让SDK运行,你必须定期在平台界面实例上调用 Epic.OnlineServices.Platform.PlatformInterface.Tick。无需每帧都进行这些调用,但是应该频繁调用;合理频率为每100毫秒一次,但是你可以根据需求调整具体频率。SDK回调只能在你调用 更新函数 时运行,因此你所有的异步操作都依赖于它。

关闭

不再需要SDK时(通常是应用关闭时),可以调用 Epic.OnlineServices.Platform.PlatformInterface.Release 关闭它,从而释放平台界面实例,然后调用 Epic.OnlineServices.Platform.PlatformInterface.Shutdown 完成关闭过程。此过程是最终的;Epic.OnlineServices.Platform.PlatformInterface.Release 会将SDK置于完成状态,在此之后,你无法获取另一平台界面句柄或重新初始化SDK。出于此原因,我们建议在应用关闭前不要关闭EOS SDK。

对于一些编辑器环境(包括Unity),在编辑器启动期间加载外部库(如EOS),并且在编辑器关闭之前不要卸载。在这些情况下,你不应该在编辑器运行会话结束时调用 Epic.OnlineServices.Platform.PlatformInterface.ReleaseEpic.OnlineServices.Platform.PlatformInterface.Shutdown,因为在未重启编辑器的情况下,你将无法在任何未来会话中成功初始化SDK。此外,由于这些编辑器环境使用一个SDK连续实例,因此在运行会话结束之前开始的操作可能会在下一会话中完成并触发回调。

结果

大多数回调数据结构和一些返回值使用 Epic.OnlineServices.Result 传递SDK操作的结果。确保用它来处理错误并确保操作按预期执行。

日志

SDK通过内部界面输出有用的调试信息。要启用此功能,请尽早设置 Epic.OnlineServices.Logging.LoggingInterface,最好在初始化SDK后立即设置,方法是使用指示你要求的细节级别的参数调用 Epic.OnlineServices.Logging.LoggingInterface.SetLogLevel,然后使用回调函数调用 Epic.OnlineServices.Logging.LoggingInterface.SetCallback,以便接收日志信息。此功能可以提供关于内部操作的深度信息,有助于确定你可能遇到的意外行为的根源。

非托管内存

在C SDK中,有些API(如 EOS_Leaderboards_CopyLeaderboardDefinitionByIndex)传出必须手动释放的数据结构体。在C# SDK中,你无需担心这些结构体的发布问题,因为这些结构体由封装器的集结代码自动处理。

在C SDK中,某些API(例如 EOS_Presence_CreatePresenceModification)会传出句柄,让你可以在SDK库拥有的内存中设置数据。这些句柄必须手动释放。在C# SDK中,这些句柄由底层类型为 Epic.OnlineServices.Handle 的对象表示,并包含可让你设置非托管数据的函数。它们还包含一个Release函数,完成后你必须手动调用该函数。

自定义内存委托

在一些平台(如主机)上,SDK要求你自行实现分配、重新分配和释放函数。因为SDK将非常高频地调用这些函数,所以能够直接访问SDK而无需通过托管代码中的委托进行路由是非常高效的。

使用 Epic.OnlineServices.Platform.InitializeOptions 初始化平台时,你可以选择将这些函数传递到SDK。推荐你:

  1. 创建原生库并实现3种内存函数。

  2. 对于3种内存函数中的任一种,实现将指针返回到内存函数的导出函数。

  3. 在C#中初始化平台之前,调用导出函数,并将返回的IntPtr值设置到选项结构体。

以下示例代码展示了原生库的结构:

void* Allocate(size_t InSizeInBytes, size_t InAlignment)
{
}

void* Reallocate(void* InPointer, size_t InSizeInBytes, size_t InAlignment)
{
}

void Release(void* InPointer)
{
}

typedef void* (*AllocateFunctionPointer)(size_t InSizeInBytes, size_t InAlignment);
extern "C" __declspec(dllexport) AllocateFunctionPointer GetAllocateFunctionPointer();
{
    return Allocate;
}

typedef void* (*ReallocateFunctionPointer)(void* InPointer, size_t InSizeInBytes, size_t InAlignment);
extern "C" __declspec(dllexport) ReallocateFunctionPointer GetReallocateFunctionPointer()
{
    return Reallocate;
}

typedef void (*ReleaseFunctionPointer)(void* InPointer);
extern "C" __declspec(dllexport) ReleaseFunctionPointer GetReleaseFunctionPointer()
{
    return Release;
}

以下示例代码展示了C#绑定的内容:

[DllImport("MyNativeLibrary.dll")]
private static extern IntPtr GetAllocateFunctionPointer();

[DllImport("MyNativeLibrary.dll")]
private static extern IntPtr GetReallocateFunctionPointer();

[DllImport("MyNativeLibrary.dll")]
private static extern IntPtr GetReleaseFunctionPointer();

线程处理

SDK当前不是线程安全的。我们建议所有对SDK的调用来自你应用的主线程。此时,我们建议不要使用 异步等待线程任务 或类似模式。

EOS覆层

要在你的应用中使用

[EOS覆层](EpicGamesStore/TechFeaturesConfig/EOSOverlay)
,请在创建任何图形设备前完成以下操作:

  1. 将SDK库加载到内存

  2. 使用EOS_Initialize初始化

  3. 使用EOS_Platform_Create创建平台

在Unity中,你实现此目的的方法之一是创建低级别原生渲染插件。至少,你需要:

  1. 创建名称以 GfxPlugin 为前缀的原生库。

  2. 添加名为 UnityPluginLoad(void*) 的导出函数,用于加载SDK库并成功调用EOS_InitializeEOS_Platform_Create

  3. 添加导出函数,在需要时将创建的平台界面句柄返回给C#,在那里它可以用于构造新的 Epic.OnlineServices.Platform.PlatformInterface 实例。

Unity编辑器等编辑器环境不支持EOS覆层。建议你在编辑器环境中明确禁用覆层,以便防止意外行为。

对于Unity用户最佳实践,我们建议独立构建控制低级别原生渲染插件内中SDK的生命周期,编辑器构建应该排除插件,以便控制具有动态绑定的MonoBehaviour内SDK的生命周期。

动态绑定

通常,C# SDK使用 extern DllImport 绑定。然而,你可能需要使用动态绑定。

要开始使用动态绑定:

  1. 定义 EOS_DYNAMIC_BINDINGS

  2. 动态加载SDK库,并检索库句柄

  3. 应用会话开始时,调用 Epic.OnlineServices.Bindings.Hook 传入库句柄和函数指针加载程序委托。

  4. 应用会话结束时,调用 Epic.OnlineServices.Bindings.Unhook 并释放库进行清理。

例如,你可能在Windows上定义了以下外部函数:

[DllImport("Kernel32.dll")]
private static extern IntPtr LoadLibrary(string lpLibFileName);

[DllImport("Kernel32.dll")]
private static extern int FreeLibrary(IntPtr hLibModule);

[DllImport("Kernel32.dll")]
private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

然后开始使用SDK,并用以下内容加载库和挂接动态绑定:

var libraryPath = $"Assets/{Config.LibraryName}";

var libraryPointer = LoadLibrary(libraryPath);
if (libraryPointer == IntPtr.Zero)
{
    throw new Exception($"Failed to load library {libraryPath}");
}

Bindings.Hook(libraryPointer, GetProcAddress);

应用完成后,用以下内容取消挂接动态绑定和释放库:

Bindings.Unhook();
FreeLibrary(libraryPointer);

有些平台有平台专用API。这些平台有自己的绑定类。

要使用平台专用API,除了 Epic.OnlineServices.Bindings 基本类,你必须挂接相应的平台专用绑定类。

自1.12版本起,C# SDK的默认行为是要求对编辑器环境(例如Unity编辑器)进行动态绑定。这样可以启用SDK库的按需加载和卸载,而且每次在编辑器中运行游戏时可以初始化SDK。下面提供了在Unity中使用动态绑定的更详细示例代码。

示例代码和项目

这里提供了各种 C# SDK 示例,以便演示不同的Epic在线服务(EOS)功能的实现方法,例如验证、展示、购物、以及语音。此外,我们还提供了示例代码,以便设置平台、登录,以及实现Unity的EOS SDK组件。

示例代码

本节中的示例代码旨在演示并帮助你熟悉C# SDK。

设置平台

    // 根据需要设置这些值。有关更多信息,参见开发人员门户文档。
    string productName = "MyCSharpApplication";
    string productVersion = "1.0";
    string productId = "";
    string sandboxId = "";
    string deploymentId = "";
    string clientId = "";
    string clientSecret = "";

    var initializeOptions = new Epic.OnlineServices.Platform.InitializeOptions()
    {
        ProductName = productName,
        ProductVersion = productVersion
    };

    var initializeResult = Epic.OnlineServices.Platform.PlatformInterface.Initialize(initializeOptions);
    if (initializeResult != Epic.OnlineServices.Result.Success)
    {
        throw new System.Exception("Failed to initialize platform: " + initializeResult);
    }

    // SDK输出很多用于调试的信息。
    // 确保尽早设置日志记录界面:在初始化之后。
    Epic.OnlineServices.Logging.LoggingInterface.SetLogLevel(Epic.OnlineServices.Logging.LogCategory.AllCategories, Epic.OnlineServices.Logging.LogLevel.VeryVerbose);
    Epic.OnlineServices.Logging.LoggingInterface.SetCallback((Epic.OnlineServices.Logging.LogMessage logMessage) =>
    {
        System.Console.WriteLine(logMessage.Message);
    });

    var options = new Epic.OnlineServices.Platform.Options()
    {
        ProductId = productId,
        SandboxId = sandboxId,
        DeploymentId = deploymentId,
        ClientCredentials = new Epic.OnlineServices.Platform.ClientCredentials()
        {
            ClientId = clientId,
            ClientSecret = clientSecret
        }
    };

    var platformInterface = Epic.OnlineServices.Platform.PlatformInterface.Create(options);
    if (platformInterface == null)
    {
        throw new System.Exception("Failed to create platform");
    }

登录

    var loginCredentialType = Epic.OnlineServices.Auth.LoginCredentialType.AccountPortal;
    /// 这些字段对应于<see cref="Epic.OnlineServices.Auth.Credentials.Id" />和<see cref="Epic.OnlineServices.Auth.Credentials.Token" />,
    /// 它们的用途因登录类型而异。有关更多信息,参见<see cref="Epic.OnlineServices.Auth.Credentials" />
    /// 和身份验证界面文档。
    string loginCredentialId = null;
    string loginCredentialToken = null;

    var authInterface = platformInterface.GetAuthInterface();
    if (authInterface == null)
    {
        throw new System.Exception("Failed to get auth interface");
    }

    var loginOptions = new Epic.OnlineServices.Auth.LoginOptions()
    {
        Credentials = new Epic.OnlineServices.Auth.Credentials()
        {
            Type = loginCredentialType,
            Id = loginCredentialId,
            Token = loginCredentialToken
        }
    };

    // 确保按一定间隔调用平台更新函数,否则以下调用将永远不会回调。
    authInterface.Login(loginOptions, null, (Epic.OnlineServices.Auth.LoginCallbackInfo loginCallbackInfo) =>
    {
        if (loginCallbackInfo.ResultCode == Epic.OnlineServices.Result.Success)
        {
            System.Console.WriteLine("Login succeeded");
        }
        else if (Epic.OnlineServices.Common.IsOperationComplete(loginCallbackInfo.ResultCode))
        {
            System.Console.WriteLine("Login failed: " + loginCallbackInfo.ResultCode);
        }
    });

Unity EOSSDK组件

    // 此代码用于演示目的,并不代表理想实践。
    using Epic.OnlineServices;
    using Epic.OnlineServices.Auth;
    using Epic.OnlineServices.Logging;
    using Epic.OnlineServices.Platform;
    using System;
    using System.Runtime.InteropServices;
    using UnityEngine;

    public class EOSSDKComponent : MonoBehaviour
    {
        // 根据需要设置这些值。有关更多信息,参见开发人员门户文档。
        public string m_ProductName = "MyUnityApplication";
        public string m_ProductVersion = "1.0";
        public string m_ProductId = "";
        public string m_SandboxId = "";
        public string m_DeploymentId = "";
        public string m_ClientId = "";
        public string m_ClientSecret = "";
        public LoginCredentialType m_LoginCredentialType = LoginCredentialType.AccountPortal;
        /// 这些字段对应于<see cref="Credentials.Id" />和<see cref="Credentials.Token" />,
        /// 它们的用途因登录类型而异。有关更多信息,参见<see cref="Credentials" />
        /// 和身份验证界面文档。
        public string m_LoginCredentialId = null;
        public string m_LoginCredentialToken = null;

        private static PlatformInterface s_PlatformInterface;
        private const float c_PlatformTickInterval = 0.1f;
        private float m_PlatformTickTimer = 0f;

        // 如果我们处于编辑器中,我们应该在运行会话之间动态加载和卸载SDK。
        // 这样每次在编辑器中运行游戏时我们就可以初始化SDK。
    #if UNITY_EDITOR
        [DllImport("Kernel32.dll")]
        private static extern IntPtr LoadLibrary(string lpLibFileName);

        [DllImport("Kernel32.dll")]
        private static extern int FreeLibrary(IntPtr hLibModule);

        [DllImport("Kernel32.dll")]
        private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

        private IntPtr m_LibraryPointer;
    #endif

        private void Awake()
        {
    #if UNITY_EDITOR
            var libraryPath = "Assets/" + Config.LibraryName;

            m_LibraryPointer = LoadLibrary(libraryPath);
            if (m_LibraryPointer == IntPtr.Zero)
            {
                throw new Exception("Failed to load library" + libraryPath);
            }

            Bindings.Hook(m_LibraryPointer, GetProcAddress);
    #endif
        }

        private void OnApplicationQuit()
        {
            if (s_PlatformInterface != null)
            {
                s_PlatformInterface.Release();
                s_PlatformInterface = null;
                PlatformInterface.Shutdown();
            }

    #if UNITY_EDITOR
            if (m_LibraryPointer != IntPtr.Zero)
            {
                            Bindings.Unhook();

                                        // 闲置,直到模块ref计数为0
                while (FreeLibrary(m_LibraryPointer) != 0) { }
                m_LibraryPointer = IntPtr.Zero;
            }
    #endif
        }

        void Start()
        {
            var initializeOptions = new InitializeOptions()
            {
                ProductName = m_ProductName,
                ProductVersion = m_ProductVersion
            };

            var initializeResult = PlatformInterface.Initialize(initializeOptions);
            if (initializeResult != Result.Success)
            {
                throw new Exception("Failed to initialize platform: " + initializeResult);
            }

            // SDK输出很多用于调试的信息。
            // 确保尽早设置日志记录界面:在初始化之后。
            LoggingInterface.SetLogLevel(LogCategory.AllCategories, LogLevel.VeryVerbose);
            LoggingInterface.SetCallback((LogMessage logMessage) => Debug.Log(logMessage.Message));

            var options = new Options()
            {
                ProductId = m_ProductId,
                SandboxId = m_SandboxId,
                DeploymentId = m_DeploymentId,
                ClientCredentials = new ClientCredentials()
                {
                    ClientId = m_ClientId,
                    ClientSecret = m_ClientSecret
                }
            };

            s_PlatformInterface = PlatformInterface.Create(options);
            if (s_PlatformInterface == null)
            {
                throw new Exception("Failed to create platform");
            }

            var loginOptions = new LoginOptions()
            {
                Credentials = new Credentials()
                {
                    Type = m_LoginCredentialType,
                    Id = m_LoginCredentialId,
                    Token = m_LoginCredentialToken
                }
            };

            // 确保按一定间隔调用平台更新函数,否则不会回调此内容。
            s_PlatformInterface.GetAuthInterface().Login(loginOptions, null, (LoginCallbackInfo loginCallbackInfo) =>
            {
                if (loginCallbackInfo.ResultCode == Result.Success)
                {
                    Debug.Log("Login succeeded");
                }
                else if (Common.IsOperationComplete(loginCallbackInfo.ResultCode))
                {
                    Debug.Log("Login failed: " + loginCallbackInfo.ResultCode);
                }
            });
        }

        // 回调运行需要按一定间隔调用更新函数。
        private void Update()
        {
            if (s_PlatformInterface != null)
            {
                m_PlatformTickTimer += Time.deltaTime;

                if (m_PlatformTickTimer >= c_PlatformTickInterval)
                {
                    m_PlatformTickTimer = 0;
                    s_PlatformInterface.Tick();
                }
            }
        }
    }

示例项目

这些示例包含在你下载的C# SDK中,其中包含以下库和应用。

通用(Common) 库包含SDK代码以及其他有用的功能。

WpfCommon 库包含所有WPF示例公用的功能。

VoiceCommon 库包含所有语音示例通用的功能。

应用

SimpleAuth是一款WPF应用,用于演示如何为登录用户提供验证和Presence相关的功能。

SimpleOverlayPurchasing是一款托管的DirectX11应用程序,用于演示如何启动和结束内容流程(通过游戏内覆层)。

VoiceServer是一款应用,用于展示如何实现一个RESTful API服务,以便充当语音客户端和EOS之间的可信任语音服务器。

VoiceClient是一个WPF应用,用于演示如何连接语音房间、切换音频设备、发送音频、客户端禁言、服务器禁言、以及踢出等功能。

准备工作

要允许示例,你首先要安装:

  • .NET Core 3.1

  • Visual Studio 2019 或更高版本

你还必须在 Settings.cs 中设置以下与你应用相关的数值:

  • ProductId

  • SandboxId

  • DeploymentId

  • ClientId

  • ClientSecret

你还可以设置 LoginType。根据登录类型, IdToken 字段的用法可能略有不同。更多详情,请参见Auth.CredentialsAuth Interface

出于演示目的,示例应用使用 Epic Account Services 来验证本地用户。这要求用于初始化SDK的客户端凭证已经指定给采用了EOS的应用。

在进行用户验证时,所演示的SDK功能可以与所有受支持的身份提供方一起使用。

SimpleAuth

SimpleAuth 是一款示例应用,用于演示如何为登录用户提供验证和Presence相关的功能.

登录

首先,你会看到一个登录界面。你可以选择你想使用的登录类型。我们推荐你使用AccountPortal类型来直接通过账户门户登录。或者,使用Developer类型,使用你通过Developer Auth工具指定的凭证来登录。

SimpleAuth_LogIn.png

查看你的状态

登录后,你会看到一个页面(展示你的当前登录状态)以及一个表格(允许你更改状态)。它包含在线状态相关的状态、富文本、以及与你当前状态相关的数据输入项。

SimpleAuth_ViewPresence.png

OverlayPurchasing

此功能仅适用于在Epic游戏商城上架的产品。你必须是商城伙伴,并且你必须已经在商城中设置了价格。

为了确保覆层界面能够工作,你需要完成以下其中一项工作:

  • 安装Epic Games启动程序并至少运行过一次。

  • 下载了EOSOVH dll文件并将注册键 HKEY_CURRENT_USER\Software\Epic Games\EOS\OverlayPath 设置成dll所在的目录。

启动购买流程

示例会自动执行登录流程以及必要的Ecom函数,以便将覆层导引至购买流程。请留意输出内容,确保没有问题。

OverlayPurchase_InitiateFlow.png