Epic 商店游戏发布流程+Unity SDK接入教程

作者:hont
2023-12-29
8 6 0

背景

大约是 2023 年 2 月底,Epic 开放了开发者自由发布游戏的权限,和 Steam 一样,缴纳 100 美元即可发布,且对开发引擎没有限制,使用 Unity 开发的游戏也可以发布(用 UE 开发会有减免)。看到消息后,我尝试将之前的旧项目发布上去,没想到流程如此繁琐,历经 N 个下班后的夜晚终于将流程走通。

如果你也在尝试接入,这篇文章或可做个参考,有任何不清楚的地方,可直接在 indienova 私信我。

1.如何开始

在 Epic 商店底部点击“分发”按钮,即可进入 Epic 开发者相关操作页面。

此时通常会有一系列注册流程,不再详述。在这之后,需要创建组织并创建产品,大致如下:


税务审查

如果是新账户,系统会提示填写税务审查资料,网上有些朋友因地址填写问题审核失败,我也因地址填写问题被打回一次,后来按照英文格式详细填写一遍就通过审核了。

2.发布管理

接下来就可以进行商店页的编辑操作。Epic 后台与 Steam 不同,它分为 3 个部分,所有的编辑操作都在 Dev 部分进行,随后推送到 Stage 即可提交审核,审核完毕可随时推送至 Live。

页面编辑部分没有太多难点,此处略过。


日期设置

如果要开卖而不是积累愿望单,需检查“商品”-“发行日期”,改为“特定”,否则你的游戏只能看到页面,无法进入售卖审核环节(坑啊)。

3.程序包提交

3.1 BuildPatch Tool

Epic 游戏包的提交通过 BuildPatch Tool 工具进行,可在后台面板此处进行下载。

使用该工具与 Steam 程序包提交方式类似,但需要手动编写批处理文件并指定各种 ID。例如,我们可以在这个目录进行一些文件创建:

打开压缩包附带的 PDF 文档,里面有如何提交这些参数的说明:

bat 文件可编写如下:

BuildPatchTool.exe -OrganizationId="" -ProductId="" -ArtifactId="" -ClientId="" -ClientSecret="" -mode=UploadBinary -BuildRoot="./Release_Epic" -BuildVersion="1001" -AppLaunch="yourApp.exe" -AppArgs="" -FileAttributeList="" -FileIgnoreList=""

最后,将程序包和 bat 文件都编辑好,大致如下:


下面讲讲这些 ID 如何获取。


3.2 BuildPatch Tool 的各类 ID

ProductID 可在产品设置(SDK 下载与凭证)中找到:

OrganizationID 在“组织”->“设置”这里:

ArtifactID 在“构建与二进制文件”处:

最后的 ClientID 和密钥在 BPT(不在 SDK 下载与凭证那一栏,很坑)那里:


3.3 提交应用

运行 bat 没有问题后,就会提交到后台,然后需要在后台“构建与二进制文件”页面,创建对应构建,点击“分配平台”,设置新的版本 ID 即可生效:


3.4 商品设置

还需要在“商品详情”->“构建设置”处配置构建对应的文件夹。

最后这部分可能还有一些疏漏,因为撰文时已经是提交完成的状态,没法复现。请大家自行查询文档完成剩余步骤。

4.杂项

4.1 账户服务

继续流程。还需要开通账户服务,点击右侧“创建应用程序”即可。

创建后,“许可”和“已关联客户端”部分很简单,但“品牌设置”这一项比较麻烦,需要创建官方网站。但看网上一些朋友的分享,好像不设置也不影响游戏上架。

不过这一步我是完成了操作的,创建工作室官网并绑定品牌设置的流程如下:

1.在域名的 txt 地址解析处粘贴上 Epic 的一串验证码,验证域名。域名用非.com 后缀也可,所以购买那种 1 块钱的域名就可以解决。

2.不管后台是什么样的,主机需要支持 https(对,仔细看会发现要 https 开头的网站),并且需要一个主页网页和一个 PrivacyPolic 隐私政策网页。隐私政策随便找一个别的产品的作为模板,改下就行。

我配置好后的页面长这样:


4.2 产品设置-客户端策略

接下来设置客户端策略,以便为玩家开通成就权限等。

Metrics 必须勾选,否则无法统计后台数据;unlockAchievementForLocalUser 必须勾选,否则影响到成就接入(添加新的策略时选择特殊策略模板即可勾选)。

5.成就接入

5.1 创建成就

如果你的游戏在 Steam 平台(或别的平台)有成就,就必须在 Epic 上也接入成就,否则无法通过审核。接下来讲讲操作。

在“游戏服务”->“成就”处可以查看成就,点击“创建成就”,这时会提示“添加统计信息”,无需勾选。

下一步,成就 ID 就是你在 Unity 里需要填入解锁的 ID,和 Steam 一样:


5.2 分配成就奖励

和 Steam 不同的是,Epic 创建的成就要分配满 1000 经验值的奖励。


5.3 成就测试

那么,测试游戏的时候怎么知道自己已经获得了成就呢?在 Epic 库中,可以将预览模式设置为 Dev 以进行查看。


5.4 推送

最后留意 2 件事情:成就在 Live 面板是否存在,是否点击“推送到台面”。我记得“推送到台面”按钮要经过审核才会出现。

6.Unity SDK 接入

6.1 安装 Unity 版本 SDK

终于来到 Unity SDK 接入的部分,官网封装得很烂,我使用的是网络上开源的 Unity 封装版本:

https://github.com/PlayEveryWare/eos_plugin_for_unity_upm

注意:一定要用导入 Git 包的功能导入到 Unity Package Manager,不行自己挂一个梯子。我就是用第一次直接下载的包,导致文件不一致报错了。


6.2 配置

在入口场景新建一个 GameObject 挂载 EOSManager 和登录:

登录脚本 EpicLogin 的出处:https://blog.csdn.net/final5788/article/details/128202742

脚本内容如下:

public class EpicLogin : MonoBehaviour
{
    private ProductUserId _productUserId;

    private void Start()
    {
        DontDestroyOnLoad(gameObject);

        Login();
    }

    public void Login()
    {
        var token = string.Empty;
        string[] commandArgs = Environment.GetCommandLineArgs();
        foreach (var commandArg in commandArgs)
        {
            if (commandArg.Contains("AUTH_PASSWORD"))
            {
                var args = commandArg.Split('=');
                if (args.Length >= 2)
                {
                    token = args[1];
                }
            }
        }
        EOSManager.Instance.StartLoginWithLoginTypeAndToken(LoginCredentialType.AccountPortal, null, token, callbackInfo =>
        {
            if (callbackInfo.ResultCode != Epic.OnlineServices.Result.Success)
            {
                LoginWithPersistentMode();
            }
            else
            {
                StartLoginWithLoginTypeAndTokenCallback(callbackInfo);
            }
        });
    }

    public void LoginWithPersistentMode()
    {
        EOSManager.Instance.StartPersistentLogin((Epic.OnlineServices.Auth.LoginCallbackInfo callbackInfo) =>
        {
            if (callbackInfo.ResultCode != Epic.OnlineServices.Result.Success)
            {
                LoginWithLoginTypeAndToken();
            }
            else
            {
                StartLoginWithLoginTypeAndTokenCallback(callbackInfo);
            }
        });
    }

    private void LoginWithLoginTypeAndToken()
    {
        EOSManager.Instance.StartLoginWithLoginTypeAndToken(
            Epic.OnlineServices.Auth.LoginCredentialType.AccountPortal, ExternalCredentialType.Epic, null, null,
            loginResult =>
            {
                EOSManager.Instance.StartConnectLoginWithEpicAccount(loginResult.LocalUserId, (Epic.OnlineServices.Connect.LoginCallbackInfo connectLoginCallbackInfo) =>
                {
                    if (connectLoginCallbackInfo.ResultCode == Result.Success)
                    {
                        _productUserId = connectLoginCallbackInfo.LocalUserId;
                    }
                    else if (connectLoginCallbackInfo.ResultCode == Result.InvalidUser)
                    {
                        // ask user if they want to connect; sample assumes they do
                        EOSManager.Instance.CreateConnectUserWithContinuanceToken(connectLoginCallbackInfo.ContinuanceToken, (Epic.OnlineServices.Connect.CreateUserCallbackInfo createUserCallbackInfo) =>
                        {
                            EOSManager.Instance.StartConnectLoginWithEpicAccount(loginResult.LocalUserId, (Epic.OnlineServices.Connect.LoginCallbackInfo retryConnectLoginCallbackInfo) =>
                            {
                                if (retryConnectLoginCallbackInfo.ResultCode == Result.Success)
                                {
                                    _productUserId = retryConnectLoginCallbackInfo.LocalUserId;
                                }

                            });
                        });
                    }
                    else
                    {

                    }
                });

            });
    }


    private void StartConnectLoginWithLoginCallbackInfo(LoginCallbackInfo loginCallbackInfo)
    {
        EOSManager.Instance.StartConnectLoginWithEpicAccount(loginCallbackInfo.LocalUserId, (Epic.OnlineServices.Connect.LoginCallbackInfo connectLoginCallbackInfo) =>
        {
            if (connectLoginCallbackInfo.ResultCode == Result.Success)
            {
                _productUserId = connectLoginCallbackInfo.LocalUserId;

            }
            else if (connectLoginCallbackInfo.ResultCode == Result.InvalidUser)
            {
                EOSManager.Instance.CreateConnectUserWithContinuanceToken(connectLoginCallbackInfo.ContinuanceToken, (Epic.OnlineServices.Connect.CreateUserCallbackInfo createUserCallbackInfo) =>
                {
                    EOSManager.Instance.StartConnectLoginWithEpicAccount(loginCallbackInfo.LocalUserId, (Epic.OnlineServices.Connect.LoginCallbackInfo retryConnectLoginCallbackInfo) =>
                    {
                        if (retryConnectLoginCallbackInfo.ResultCode == Result.Success)
                        {
                            _productUserId = retryConnectLoginCallbackInfo.LocalUserId;
                        }
                    });
                });
            }
        });
    }

    public void StartLoginWithLoginTypeAndTokenCallback(LoginCallbackInfo loginCallbackInfo)
    {
        if (loginCallbackInfo.ResultCode == Epic.OnlineServices.Result.AuthMFARequired)
        {
            // collect MFA
            // do something to give the MFA to the SDK
            print("MFA Authentication not supported in sample. [" + loginCallbackInfo.ResultCode + "]");
        }
        else if (loginCallbackInfo.ResultCode == Result.AuthPinGrantCode)
        {
            ///TODO(mendsley): Handle pin-grant in a more reasonable way
        }
        else if (loginCallbackInfo.ResultCode == Epic.OnlineServices.Result.Success)
        {
            StartConnectLoginWithLoginCallbackInfo(loginCallbackInfo);
        }
        else if (loginCallbackInfo.ResultCode == Epic.OnlineServices.Result.InvalidUser)
        {
            EOSManager.Instance.AuthLinkExternalAccountWithContinuanceToken(loginCallbackInfo.ContinuanceToken,
#if UNITY_SWITCH
                                                                                LinkAccountFlags.NintendoNsaId,
#else
                                                                            LinkAccountFlags.NoFlags,
#endif
                                                                            (Epic.OnlineServices.Auth.LinkAccountCallbackInfo linkAccountCallbackInfo) =>
                                                                            {
                                                                                if (linkAccountCallbackInfo.ResultCode == Result.Success)
                                                                                {
                                                                                    StartConnectLoginWithLoginCallbackInfo(loginCallbackInfo);
                                                                                }
                                                                                else
                                                                                {
                                                                                    print("Error Doing AuthLink with continuance token in. [" + linkAccountCallbackInfo.ResultCode + "]");
                                                                                }
                                                                            });
        }

        else
        {
            print("Error logging in. [" + loginCallbackInfo.ResultCode + "]");
        }

        // Re-enable the login button and associated UI on any error
        if (loginCallbackInfo.ResultCode != Epic.OnlineServices.Result.Success)
        {
            //ConfigureUIForLogin();
        }
    }
}

在 Unity 顶部菜单的 Tools->EosPlugin->Dev Portal Configuration 处配置各类 ID,蓝色框出的区域可以不用配置。所有的配置内容都在 SDK 下载与凭证那一栏,ClietID 也用这一栏的。

出包之后检查有没有 EOSBootstrapper 文件,如果没有就说明 SDK 没装正确:

6.3 成就,Unity 部分

导入 SDK 插件的第一个案例,案例中封装好了成就管理器,会稍微方便些。

成就代码:

public static class EpicAchievementMediator
{
    public static void CompleteAchievement(string achievementID)
    {
        UnlockAchievementsOptions options = new UnlockAchievementsOptions();
        options.AchievementIds = new Epic.OnlineServices.Utf8String[1];
        options.AchievementIds[0] = new Epic.OnlineServices.Utf8String(achievementID);
        EOSManager.Instance.GetEOSAchievementInterface().UnlockAchievements(ref options, null, null);

        EOSManager.Instance.GetOrCreateManager<EOSAchievementManager>().RefreshData();

        try
        {
            EOSManager.Instance.GetOrCreateManager<EOSAchievementManager>().UnlockAchievementManually(achievementID, (ref OnUnlockAchievementsCompleteCallbackInfo info) =>
            {
                if (info.ResultCode == Result.Success)
                {
                    Debug.LogError("UnlockAchievement Succeesful");
                    EOSManager.Instance.GetOrCreateManager<EOSAchievementManager>().RefreshData();
                }

                Debug.LogError("info.ResultCode: " + info.ResultCode);
            });
        }
        catch (System.Exception e)
        {
            Debug.LogError("Errr!   " + e);
        }
    }
}


6.4 打包测试

最后打包、上传、后台更新最新包体 ID,进入 Epic 启动器测试。如果游戏运行时有 Epic Overlay UI 覆盖在游戏之上,并且获得成就也会有 Epic UI 的成就特效,说明基本上成功了,可以丢给 Epic 审核。

到这里,整个发布流程告一段落,与大家共勉~

本文为用户投稿,不代表 indienova 观点。

近期点赞的会员

 分享这篇文章

您可能还会对这些文章感兴趣

参与此文章的讨论

暂无关于此文章的评论。

您需要登录或者注册后才能发表评论

登录/注册