背景
大约是 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 审核。
到这里,整个发布流程告一段落,与大家共勉~


暂无关于此文章的评论。