在Visual Studio 2019中为EOS设置C#解决方案

Epic Games技术客户经理Rajen Kishna |
2021年9月28日
本系列的前一篇文章中,我讲解了如何注册并访问Epic Games开发者门户以开始使用Epic在线服务(EOS)。建立账户后,我将带你设置Visual Studio 2019并使用C#创建一个WPF解决方案,我们将通过该解决方案探索全部EOS服务。在学习过程中,请务必牢记上一篇文章中的说明。
 

安装Visual Studio 2019

如果你还没有安装Visual Studio 2019,请访问https://visualstudio.microsoft.com/下载。微软为个人和某些组织提供了免费的社区版本,请滚动到底部确认你是具备该资格。我们需要根据所使用的编程语言,选择一些工作负荷和组件:
 
  • C++
    • 桌面和移动平台 > 使用C++的桌面开发
      • 在右边栏的可选项中勾选<em>MSVC v141 - VS2017 C++ x64/x86构建工具(v14.16)</em>,EOS SDK示例会用到这些构建工具。
    • 游戏 > 使用C++的游戏开发
  • C#
    • 桌面和移动平台 > .NET桌面开发
VS2019 Workloads
Visual Studio 2019工作负荷
 
VS2019 MSVC141 Component
Visual Studio 2019工作负荷
 

安装上面列出的所有工作负荷和组件,这样我们才能编译并运行EOS SDK C++和C#示例。

下载SDK

在安装Visual Studio的同时,你可以从开发者门户下载SDK。如无另外说明,我将在本系列文章中使用SDK 1.14.1。
 
  1. 访问https://dev.epicgames.com/portal/登录开发者门户。
  2. 选择左侧菜单中的“SDK”。在这里你可以下载适用于C、C#、iOS或Android的SDK,并查看产品概览(包括SDK所需凭证的快速概览)和SDK更改日志。
  3. 在“SDK版本”下拉菜单中选择“C SDK”,单击“下载”。对于C# SDK或其他想要探索的平台,也是执行同样的操作。在本系列中,我们主要使用C# SDK,但你也可以将相同的概念应用到其他平台。
  4. 保存各个SDK,并解压到便于访问的位置。

每个SDK文件夹都包含3个子文件夹:Samples、SDK和ThirdPartyNotices。Samples子文件夹包含该平台的解决方案和/或项目,C SDK提供的示例集最为全面。SDK子文件夹包含二进制文件、库、头文件和SDK本身的源代码,另外还有一个Tools文件夹,它包含开发者身份验证工具文件解密工具反作弊完整性工具以及可重新分发文件。在ThirdPartyNotices子文件夹中可以找到一份文本文件,内含SDK所使用的第三方软件声明。

编译SDK示例

下载SDK并安装Visual Studio后,我们可以通过编译示例来快速验证所有内容是否已设置正确,这些示例同时也是我们研究特定功能的参考。
 
  1. 首先,在Visual Studio 2019中打开C SDK Samples文件夹中的Samples.sln文件。
  2. 打开该解决方案文件后,按Ctrl+Shift+B构建所有项目。此时,你可能会看到每个项目都弹出以下错误消息:

Error    MSB8036    The Windows SDK version 10.0.17763.0 was not found. Install the required version of Windows SDK or change the SDK version in the project property pages or by right-clicking the solution and selecting "Retarget solution".
 
  1. 要解决这些错误,右键单击解决方案资源管理器中的解决方案节点,并选择“重定解决方案目标”。在这里,你可以选择已安装的Windows SDK版本(我的是10.0.19041.0),也可以视需要将平台工具集升级到v142版本。因为我们已在安装过程中安装了该工具集的v141版本,所以我不进行升级。
VS2019 Retarget Solution
为C SDK示例重定解决方案目标
 
  1. 重定解决方案目标后,就应该能顺利使用Ctrl+Shift+B进行构建。
在运行示例之前,我们需要输入凭证。在C SDK示例项目中,可以在每个项目的Source\\SampleConstants.h文件中输入凭证。在开发者门户中的“SDK”页面上,单击产品旁边的“获取凭证”按钮,可以快速找到所有这些值。
 
VS2019 SDK Credentials
“SDK”页面上的“获取凭证”按钮


或者,你可以找到你的产品,并单击左侧菜单中的“产品设置”来找到这些值。你需要产品ID、沙盒ID、部署ID、客户端ID和客户端口令。

接下来,我们来编译C# SDK示例。

  1. 首先,在Visual Studio 2019中打开C# SDK Samples文件夹中的Samples.sln文件。
  2. 这里的一切都应该已准备就绪,按Ctrl+Shift+B可构建所有项目。

所有C#示例项目都会共享相同的凭证,这些凭证位于Common项目的Settings.cs中。

设置C#解决方案

现在一切都已经安装和测试完毕,可以开始设置我们自己的C#解决方案了。
  1. 打开一个新的Visual Studio 2019实例,并选择“创建新项目”。
  2. 在顶部的“搜索模板(Alt+S)”搜索框中键入“wpf”,然后单击“WPF应用程序[c#] [Windows] [桌面]”条目。单击“下一步”继续。
  3. 提供项目名称(例如EOSCSharpSample)、位置和解决方案名称(默认为项目名称)。单击“下一步”继续。
  4. 选择.NET Core 3.1(长期支持)作为目标框架,然后单击“创建”以完成解决方案的创建。
  5. 在“解决方案资源管理器”中右键单击解决方案节点,然后单击“在文件资源管理器中打开文件夹”。创建一个名为“SDK”的新文件夹,并将下载的C# SDK的“SDK\\Source”子文件夹复制到此处,我们接下来需要将这些文件包含到项目中。
  6. 为了包含我们刚刚复制的SDK源代码和包装器文件,我们要修改项目文件。在“解决方案资源管理器”中右键单击项目节点,然后单击“编辑项目文件”。
  7. 将以下XML代码复制到XML文件中“Project”节点的某处,靠近现有的“PropertyGroup”节点:

<ItemGroup>
    <Compile Include="..\SDK\Source\Core\**">
        <Link>SDK\Core\%(RecursiveDir)%(Filename)%(Extension)</Link>
    </Compile>
    <Compile Include="..\SDK\Source\Generated\**">
        <Link>SDK\Generated\%(RecursiveDir)%(Filename)%(Extension)</Link>
    </Compile>
</ItemGroup>

 
  1. 接下来,我们需要添加一个条件编译符号,以向SDK告知我们的目标平台。在“解决方案资源管理器”中右键单击项目节点,然后单击“属性”。
  2. 单击左侧的“构建”选项卡,并在“条件编译符号”文本框中输入<em>EOS_PLATFORM_WINDOWS_32</em>(或<em>EOS_PLATFORM_WINDOWS_64</em>)我们需要与平台目标相匹配,因此从下拉菜单中选择<em>x86</em>(或<em>x64</em>)。
  3. 最后,我们需要将SDK二进制文件包含到应用程序中。为此,在下载的C# SDK的“SDK\\Bin”子文件夹中,将EOSSDK-Win32-Shipping.dll(或EOSSDK-Win64-Shipping.dll)文件直接复制到解决方案资源管理器中的项目中。
  4. 在“解决方案资源管理器”中单击添加的DLL,并将“复制到输出目录”属性更改为“如果较新则复制”。
  5. 按Ctrl+Shift+B构建解决方案,确保所有内容可编译,否则修复所有错误。

简要的模型-视图-视图模型解释器

为了保持清晰和模块化,我将使用模型-视图-视图模型架构模式来设置解决方案并构建每项服务的功能。即使你以前从未用过MVVM,也不用担心,我将尽量通俗易懂地说明。我在下面对将用到的概念进行了简要的总结:
 
  • 命令——我们将向视图中的按钮绑定的功能,用于执行代码
  • 转换器——根据需要将一种类型(如字符串)转换为另一种类型(如布尔值)的辅助功能,例如将空字符串转换为布尔值false
  • 辅助器——常规辅助功能,例如INotifyPropertyChanged(当视图模型中的基础值发生变化时帮助更新UI)的实现
  • 服务——我们的主要功能类
  • 视图模型——我们将在视图中使用的值和命令实例的容器
  • 视图——我们呈现给用户的UI组件

当我们实现这些概念时,将阐明它们的功能。

EOS SDK的初始化

作为设置解决方案的最后一步,我们将实现EOS SDK的初始化代码,这样我们就可以在后续文章中继续实现其他EOS服务。

EOS的功能被分组到接口中,接口为每项服务的功能提供了逻辑分组。EOS中的主接口称为平台接口,它提供对所有其他接口的访问。因此,我们必须首先初始化SDK并创建平台接口的实例,然后就能在应用程序中使用该实例了。
 
  1. 首先向项目的根目录添加一个名为ApplicationSettings.cs的新类。请确保为每个设置添加自己的SDK凭证。
public class ApplicationSettings
{
    public string ProductId { get; private set; } = "";

    public string ClientId { get; private set; } = "";
    public string ClientSecret { get; private set; } = "";

    public string SandboxId { get; private set; } = "";

    public string DeploymentId { get; private set; } = "";

    public PlatformInterface PlatformInterface { get; set; }

    public void Initialize(Dictionary<string, string> commandLineArgs)
    {
        // Use command line arguments if passed
        ProductId = commandLineArgs.ContainsKey("-productid") ? commandLineArgs.GetValueOrDefault("-productid") : ProductId;
        SandboxId = commandLineArgs.ContainsKey("-sandboxid") ? commandLineArgs.GetValueOrDefault("-sandboxid") : SandboxId;
        DeploymentId = commandLineArgs.ContainsKey("-deploymentid") ? commandLineArgs.GetValueOrDefault("-deploymentid") : DeploymentId;
        ClientId = commandLineArgs.ContainsKey("-clientid") ? commandLineArgs.GetValueOrDefault("-clientid") : ClientId;
        ClientSecret = commandLineArgs.ContainsKey("-clientsecret") ? commandLineArgs.GetValueOrDefault("-clientsecret") : ClientSecret;
    }
}

 
  1. 该文件将跟踪我们的SDK凭证、主PlatformInterface实例(它是SDK的核心,提供对所有其他服务的访问)和命令行初始化代码。如果你将游戏发布到Epic Games商城,它将通过命令行参数(如用户ID)向你的游戏传递信息。我们将继续使用这个文件来跟踪初始化变量(如身份验证凭证类型和作用域)。
  2. 通过向文件添加相关的using语句来解析所有未知类型。
  3. 接下来,向项目的根目录添加一个名为ApplicationResources.xaml的资源字典(WPF)文件。我们可以暂时让这个文件留空,但是我们需要使用它来添加对转换器的引用,我们将在后续文章中用到这些转换器。打开App.xaml并在这个新的资源字典中添加以下XAML:
<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="/ApplicationResources.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

 
  1. 打开App.xaml.cs并插入以下代码,创建对应用程序设置的静态引用,并解析所有传入的命令行参数:

public partial class App : Application
{
    public static ApplicationSettings Settings { get; set; }

    protected override void OnStartup(StartupEventArgs e)
    {
        // Get command line arguments (if any) to overwrite default settings
        var commandLineArgsDict = new Dictionary<string, string>();
        for (int index = 0; index < e.Args.Length; index += 2)
        {
            commandLineArgsDict.Add(e.Args[index], e.Args[index + 1]);
        }

        Settings = new ApplicationSettings();
        Settings.Initialize(commandLineArgsDict);

        base.OnStartup(e);
    }
}

 
  1. 最后,我们使用MainWindow.xaml.cs来设置和初始化SDK。首先添加一个定时器来模拟游戏Tick。EOS SDK使用这个定时器来触发服务调用的回调方法:

private DispatcherTimer updateTimer;
private const float updateFrequency = 1 / 30f;

 
  1. 为窗口关闭事件添加一个事件句柄,以释放PlatformInterface实例并完成关闭过程。

public MainWindow()
{
    InitializeComponent();

    Closing += MainWindow_Closing;
}

private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    App.Settings.PlatformInterface.Release();
    App.Settings.PlatformInterface = null;

    _ = PlatformInterface.Shutdown();
}

 
  1. 我们定义一个InitializeApplication()函数,当MainWindow被实例化时,我们会调用该函数,以设置SDK。

private void InitializeApplication()
{
    var initializeOptions = new InitializeOptions()
    {
        ProductName = "EOSCSharpSample",
        ProductVersion = "1.0.0"
    };

    var result = PlatformInterface.Initialize(initializeOptions);
    Debug.WriteLine($"Initialize: {result}");

    _ = LoggingInterface.SetLogLevel(LogCategory.AllCategories, LogLevel.Info);
    _ = LoggingInterface.SetCallback((LogMessage message) => Debug.WriteLine($"[{message.Level}] {message.Category} - {message.Message}"));

    var options = new Options()
    {
        ProductId = App.Settings.ProductId,
        SandboxId = App.Settings.SandboxId,
        ClientCredentials = new ClientCredentials()
        {
            ClientId = App.Settings.ClientId,
            ClientSecret = App.Settings.ClientSecret
        },
        DeploymentId = App.Settings.DeploymentId,
        Flags = PlatformFlags.DisableOverlay,
        IsServer = false
    };

    PlatformInterface platformInterface = PlatformInterface.Create(options);

    if (platformInterface == null)
    {
        Debug.WriteLine($"Failed to create platform. Ensure the relevant settings are set.");
    }

    App.Settings.PlatformInterface = platformInterface;

    updateTimer = new DispatcherTimer(DispatcherPriority.Render)
    {
        Interval = new TimeSpan(0, 0, 0, 0, (int)(updateFrequency * 1000))
    };

    updateTimer.Tick += (sender, e) => Update(updateFrequency);
    updateTimer.Start();
}


让我来解释一下:
  • 通常,EOS服务的调用包括实例化一个Options类,该类将被传递给你正在调用的函数。在本例中,我们将InitializeOptions实例化并将其传递给PlatformInterface的Initialize()函数。
  • 这个特定函数会立即返回一个结果,但服务调用通常会接受一个回调函数作为参数,该参数将在函数完成时被触发。在下一篇探讨身份验证的文章中,我们将看到一个这样的例子。
  • 接下来,我们将使用日志接口来定义接收日志的方式。在示例应用程序中,我们把所有日志消息都输出到了调试输出窗口中,但你也可以设置将日志写入文件或云服务。
  • 然后我们通过PlatformInterface.Create重复同样的“Options->调用”模式,以创建PlatformInterface,并将实例存储在App.Settings中,便于以后在整个应用程序中检索。
  • 最后,我们将定时器的间隔设置为updateFrequency(在本例中为1/30秒),设置Tick事件句柄,并启动它。一般情况下,应将间隔设置为与游戏Tick速率相等,以确保API回调响应及时。注意,SDK不是线程安全的,所有SDK调用都应该由初始化它的同一线程进行。
 
  1. 定时器Tick事件句柄调用PlatformInterface的Tick方法,确保能够成功调用所有异步回调。

private void Update(float updateFrequency)
{
    App.Settings.PlatformInterface?.Tick();
}

 
  1. 最后,在MainWindow构造函数中添加对InitializeApplication()的调用

现在,你应该能够编译并运行你的应用程序,并在调试输出窗口中看到表明初始化成功的日志消息:

Initialize: Success
[Info] LogEOSOverlay - Overlay will not load, because it was explicitly disabled when creating the platform
[Info] LogEOSAntiCheat - [AntiCheatClient] Anti-cheat client not available. Verify that the game was started with the correct launcher if you intend to use it.
[Info] LogEOS - Updating Platform SDK Config, Time: 0.368525
[Info] LogEOS - SDK Config Platform Update Request Successful, Time: 0.821195
[Info] LogEOSAnalytics - Start Session (User: ...)
[Warning] LogEOSAnalytics - EOS SDK Analytics disabled for route [1].
[Info] LogEOS - Updating Product SDK Config, Time: 0.866974
[Info] LogEOSAnalytics - Start Session (User: ...)
[Info] LogEOS - SDK Config Product Update Request Successful, Time: 1.350656
[Info] LogEOS - SDK Config Data - Watermark: 82749226
[Info] LogEOS - ScheduleNextSDKConfigDataUpdate - Time: 1.350656, Update Interval: 325.699646

构建MVVM架构

在本文的最后,我们需要进一步构建MVVM架构,以便在之后的文章中轻松加入额外功能:
  1. 首先,在项目的根目录中创建一个名为Helpers的文件夹。
  2. 我们将在此处创建两个辅助器类,以帮助我们实现MVVM。我不打算介绍它们的功能,你可以轻松地在其他文章中找到详细介绍。创建一个名为BindableBase.cs的新类:

public abstract class BindableBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Equals(storage, value)) return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = PropertyChanged;
        if (eventHandler != null)
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
    }
}

 
  1. 然后创建一个名为CommandBase.cs的类:

public class CommandBase : ICommand
{
    public virtual bool CanExecute(object parameter)
    {
        throw new NotImplementedException();
    }

    public virtual void Execute(object parameter)
    {
        throw new NotImplementedException();
    }

    public event EventHandler CanExecuteChanged;
    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, new EventArgs());
    }
}

 
  1. 在创建这两个类之后,在项目的根目录下创建一个名为ViewModels的文件夹。我们在这里存储ViewModels和ViewModelLocator类以引用它们。首先创建一个名为MainViewModel.cs的类:

public class MainViewModel : BindableBase
{
    private string _statusBarText;
    public string StatusBarText
    {
        get { return _statusBarText; }
        set { SetProperty(ref _statusBarText, value); }
    }

    public MainViewModel()
    {
    }
}

 
  1. 然后创建一个名为ViewModelLocator.cs的类:

public static class ViewModelLocator
{
    private static MainViewModel _main;
    public static MainViewModel Main
    {
        get { return _main ??= new MainViewModel(); }
    }
}

 
  1. 接下来,我们创建一些转换器帮助实现UI的自动化。在项目的根目录中创建一个名为Converters的文件夹,并添加一个名为StringToBooleanConverters.cs的类:

[Bindable(BindableSupport.Default)]
public class StringToBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null || !(value is string))
        {
            return false;
        }

        string stringValue = value as string;

        return !string.IsNullOrWhiteSpace(stringValue.Trim());
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

 
  1. 添加另一个名为StringToVisibilityConverter.cs的类:

[Bindable(BindableSupport.Default)]
public class StringToVisibilityConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null || !(value is string))
        {
            return Visibility.Collapsed;
        }

        string stringValue = value as string;

        return string.IsNullOrWhiteSpace(stringValue.Trim()) ? Visibility.Collapsed : (object)Visibility.Visible;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

 
  1. 最后打开项目根目录下的ApplicationResources.xaml,并替换为下面的XAML。注意,如果你的项目名称不是EOSCSharpSample,则需要更改xmlns:c="…"旁的命名空间。

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:c="clr-namespace:EOSCSharpSample.Converters">

    <c:StringToVisibilityConverter x:Key="StringToVisibilityConverter" />
    <c:StringToBooleanConverter x:Key="StringToBooleanConverter" />

</ResourceDictionary>

获取代码

你可以在下面找到本文的代码。请严格遵守设置C#解决方案部分的步骤5和步骤9,将SDK添加到解决方案中,并编辑ApplicationSettings.cs以包含你的SDK凭证。

在下一篇文章中,我们将开始使用账户服务,通过Epic账户进行登录。请查看本系列首篇文章中的系列目录,以快速了解本系列所有文章。
 

    你的成功就是我们的成功

    Epic秉承开放、整合的游戏社区理念。通过免费向所有人提供在线服务,我们致力于帮助更多开发者服务好他们自己的玩家社区。