Using the C# SDK

介绍EOS的C#版SDK

阅读时间15分钟

除了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) 文件夹中。
  1. 确保你的应用可以访问目标平台的相应库二进制文件。
  • (Unity用户) 将相应的库二进制文件复制到你目标平台的 资产(Assets) 文件夹中。
  • 例如,Windows x64集成将使用 EOSSDK-Win64-Shipping.dll
  1. C# SDK将确定面向 Epic.OnlineServices.Config 的库二进制名称。为了方便起见,几个平台都预先配置,以指向正确的名称。
  • 如果你的目标平台尚未预先配置,请对配置和项目符号进行相应更改,避免构建错误。例如,如果面向Windows x64,你可能需要设置 EOS_PLATFORM_WINDOWS_64,如果面向Windows x86,你需要设置 EOS_PLATFORM_WINDOWS_32
  1. (Unity用户) 创建新脚本以控制SDK。对于此示例,我们使用名称 EOSSDKComponent

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

    添加组件
  3. 在此组件中编写你的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覆层,请在创建任何图形设备前完成以下操作:

  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组件

Note: The code below requires SDK 1.15 or above.

// 此代码用于演示目的,并不代表理想实践。
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(ref initializeOptions);
if (initializeResult != Result.Success)
{
throw new Exception("Failed to initialize platform: " + initializeResult);
}
// SDK输出很多用于调试的信息。
// 确保尽早设置日志记录界面:在初始化之后。
LoggingInterface.SetLogLevel(LogCategory.AllCategories, LogLevel.VeryVerbose);
LoggingInterface.SetCallback((ref LogMessage logMessage) => Debug.Log(logMessage.Message));
var options = new Options()
{
ProductId = m_ProductId,
SandboxId =