返回博客

如何解决 Epic Online Services Unreal Blueprint 登录失败与 Achievement 错误

发布于 2026年7月9日
如何解决 Epic Online Services Unreal Blueprint 登录失败与 Achievement 错误

概要

本文详细分析了在 Unreal Engine 中集成 Epic Online Services 时,由 asynchronous 执行流和 EAID 与 PUID 双重 identity 系统不匹配导致的 Blueprint 登录及 Achievement 写入错误。文章提供了正确的异步 Blueprint 执行序列以及可直接用于生产环境的 C++ 实现代码,以确保在验证 identity 之后再安全地查询和更新 Achievement 数据。此外,文章还介绍了 `DefaultEngine.ini` 文件的正确配置方法,并探讨了如何应对 overlay 焦点丢失问题以及使用统一跨平台 Backend 服务(如 horizOn)来简化多平台集成。

您编译了 Unreal Engine 游戏的 shipping build,从 Epic Games Launcher 启动游戏,并点击自定义登录按钮。然而什么都没有发生,或者游戏客户端直接卡死,您的 logs 中也充满了令人费解的 authentication 失败提示。更糟糕的是,您的 Achievement 解锁节点运行完毕却未抛出任何可见错误,但 Epic Games Store overlay 中玩家的个人资料界面依然空空如也。在集成商店 API 时,这些错误极其常见。幸运的是,通过纠正您的 asynchronous 执行流并理解 Subsystem 如何映射用户 identity,这些问题都可以得到解决。

理解 EOS Authentication 流水线:EAID vs. PUID

通过 Unreal Engine 的 Online Subsystem (OSS) 集成 Epic Online Services (EOS) 需要处理一个双重 identity 系统。Epic Account ID (EAID) 由 Auth Interface 管理。它在商店层面验证用户身份,从而启用社交 overlay、好友列表以及 Epic Games Store 库检查等功能。

相反,Product User ID (PUID) 则由 Connect Interface 管理。PUID 是一个特定于游戏的标识符,用于将用户连接到 Backend 游戏服务。这些服务包括 Matchmaking、Stats、云存档和 Achievements。

为什么 EAID 与 PUID 不匹配会破坏 Achievements

一个常见的 failure mode 是开发人员成功通过 Auth Interface 完成了 authentication(生成了 EAID),但 Subsystem 未能登录到 Connect Interface。由于 Achievements 是由 Connect Interface 使用 PUID 进行管理的,因此在没有解析出 PUID 的情况下写入 Achievement 进度会导致 EOS_InvalidUser 错误。

在没有商店 wrapper 的情况下启动的游戏中,用户通常使用 credentials 进行登录,并在中心化用户表中进行验证。在像 Epic Games Store 这样的现代商店生态系统中,identity 是联合的。当您的游戏启动时,它会从 launcher 接收到一个加密 ticket。然后,游戏将此 ticket 传递给商店的 authentication 服务器。SDK 会返回一个 Epic Account ID,作为该用户在平台上所有游戏中的“通行证”。

然而,为了保障隐私和跨平台联玩(cross-play)的安全,Epic 不允许像 Achievements 这样的游戏特定功能直接读取 Epic Account ID。相反,游戏必须初始化 Connect Interface,该接口执行二次交换以获取 Product User ID。这种分离可确保即使您的游戏被破解,玩家的全局 Epic Account 凭据也不会泄露。没有 PUID,您的游戏实际上是在未通过 authentication 的访客状态下运行,从而阻断了对 EGS 功能的访问。如果您的 credentials 配置或 client policy 在 Epic Developer Portal 中缺少正确的权限,则此关联将会失败。Identity 接口会因为 Epic 账号有效而报告登录成功,但游戏服务层却无法写入 Achievement 数据,因为根本没有关联过 PUID。

异常 Blueprint 登录序列的深度剖析

许多开发人员在遇到登录问题时,往往会将他们的 Blueprint 执行引脚(execution pins)进行线性连接。将执行引脚从 Login 节点直接拖入 Query AchievementsWrite Achievement Progress 节点将始终失败。

Authentication 请求必须发送到 Epic Backend,因此它们是 asynchronous 的。引擎会在极短的时间内(在下一个 frame)继续执行 Blueprint 蓝图,这远远早于 Epic 服务器返回用户 credentials 的时间。在此阶段,玩家'的 Unique Net ID 仍为 null。

“缺失节点”不匹配:Async 执行中的 Race Conditions

如果您在 login 节点之后立即调用 Achievement 节点,Subsystem 会因为缓存的用户状态为空而抛出静默错误。正确的 workflow 要求将自定义事件绑定到 Subsystem 的 asynchronous delegates。

此外,开发人员经常漏掉在写入 Achievements 之前对其进行缓存的关键步骤。在没有先向服务器查询当前 schema 的情况下,您是无法修改 Achievement 进度的。如果在未先调用 Query Achievements 的情况下直接调用 Write Achievements,会导致静默失败或在 console log 中报错 EOS_InvalidUser,因为本地缓存的 Achievement map 大小为 0。

正确的 Asynchronous Blueprint 流程

要解决此问题,您必须将 login 触发器与 Achievement 更新分离开来。第一步,调用 login 节点并指定正确的 authentication 凭据。

第二步,将事件绑定到 identity 接口的 On Login Complete delegate。一旦该事件触发并返回成功状态,获取用户的 Unique Net ID。

第三步,将此 ID 传入 Query Achievements 节点。最后,将事件绑定到 On Query Achievements Complete delegate,并且仅在此回调中调用 Write Achievement Progress

在 C++ 中实现健壮的 EOS Login 与 Achievement 控制器

为了干净利落地处理这些 asynchronous 操作,强烈建议在 C++ 中实现 authentication 循环。这能直接访问 IOnlineSubsystem 及其接口。

以下是一个完整且生产环境可用的 C++ login 和 Achievement 处理器实现:

#include "OnlineSubsystem.h"
#include "Interfaces/OnlineIdentityInterface.h"
#include "Interfaces/OnlineAchievementsInterface.h"
#include "OnlineSubsystemUtils.h"

void UEOSLoginHandler::LoginToEpicGames(APlayerController* PlayerController)

{
    if (!PlayerController) return;

    IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get(TEXT("EOS"));
    if (!Subsystem)
    {
        UE_LOG(LogTemp, Warning, TEXT("EOS Subsystem not found. Make sure the plugin is enabled."));
        return;
    }

    IOnlineIdentityPtr Identity = Subsystem->GetIdentityInterface();
    if (Identity.IsValid())
    {
        FOnlineAccountCredentials Credentials;
        Credentials.Type = TEXT("ExchangeCode");
        Credentials.Id = TEXT("");
        Credentials.Token = TEXT(""); // The launcher passes this via command line arguments

        Identity->AddOnLoginCompleteDelegate_Handle(0, FOnLoginCompleteDelegate::CreateUObject(this, &UEOSLoginHandler::OnLoginComplete));
        Identity->Login(0, Credentials);
    }
}

void UEOSLoginHandler::OnLoginComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error)
{
    if (!bWasSuccessful)
    { 
        UE_LOG(LogTemp, Error, TEXT("Epic Online Services Login Failed. Error: %s"), *Error);
        return;
    }

    UE_LOG(LogTemp, Log, TEXT("EOS Login Successful. ID resolved: %s"), *UserId.ToString());

    IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get(TEXT("EOS"));
    IOnlineAchievementsPtr Achievements = Subsystem->GetAchievementsInterface();
    if (Achievements.IsValid())
    { 
        Achievements->QueryAchievements(UserId, FOnQueryAchievementsCompleteDelegate::CreateUObject(this, &UEOSLoginHandler::OnQueryAchievementsComplete));
    }
}

void UEOSLoginHandler::OnQueryAchievementsComplete(const FUniqueNetId& PlayerId, bool bWasSuccessful)
{
    if (!bWasSuccessful)
    {
        UE_LOG(LogTemp, Warning, TEXT("Failed to query achievements from Epic Developer Portal."));
        return;
    }

    UE_LOG(LogTemp, Log, TEXT("Achievements queried successfully. Now writing progress."));

    IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get(TEXT("EOS"));
    IOnlineAchievementsPtr Achievements = Subsystem->GetAchievementsInterface();
    if (Achievements.IsValid())
    {
        FOnlineAchievementsWriteRef WriteObject = MakeShared<FOnlineAchievementsWrite>();
        WriteObject->SetFloatStat(TEXT("ACH_FIRST_BLOOD"), 100.0f);

        Achievements->WriteAchievements(PlayerId, WriteObject, FOnAchievementsWrittenDelegate::CreateUObject(this, &UEOSLoginHandler::OnAchievementsWritten));
    }
}

void UEOSLoginHandler::OnAchievementsWritten(const FUniqueNetId& PlayerId, bool bWasSuccessful)
{
    if (bWasSuccessful)
    {
        UE_LOG(LogTemp, Log, TEXT("Achievement successfully unlocked on Epic Games Store!"));
    }
    else
    {
        UE_LOG(LogTemp, Error, TEXT("Failed to write achievement progress. Check sandbox mappings."));
    }
}

该控制器使用顺序 callbacks。它确保仅在 identity 接口成功解析了 Epic Account ID 和 Product User ID 之后,才会访问 Achievement 接口。

为 EOS 配置 DefaultEngine.ini 文件

如果您的 Config/DefaultEngine.ini 文件中的配置不正确,Blueprint login 节点将无法完成初始化。Online Subsystem EOS 插件需要特定的参数来绑定到您的游戏客户端。

使用以下配置模板以确保您的 Subsystem 正确初始化:

[OnlineSubsystem]
DefaultPlatformService=EOS

[OnlineSubsystemEOS]
bEnabled=true
bUseEpicConnect=true
ProductId=your_product_id_here
SandboxId=your_sandbox_id_here
DeploymentId=your_deployment_id_here
ClientId=your_client_id_here
ClientSecret=your_client_secret_here

[OnlineSubsystemEOS.Auth]
bUseEpicAccount=true

DefaultEngine.ini 文件的配置起到了连接已编译的游戏二进制文件与 Epic Developer Portal 的桥梁作用。[OnlineSubsystemEOS] 下的参数必须与您的产品设置完全匹配。

一个常见的混淆点是 client credentials 与 sandbox ID 之间的区别。Client ID 和 Client Secret 代表您游戏客户端的凭据,允许其与 EOS Backend 进行通信。Sandbox ID 代表您的客户端正在访问的特定环境(例如 Development、Staging 或 Live)。

如果您的 game client 尝试使用与 Client Credentials policy 不匹配的 Sandbox ID 进行 authentication,则该 authentication 请求将被拒绝。此外,bUseEpicConnect 标志必须设置为 true,以确保 Online Subsystem 在后台自动处理 EAID 到 PUID 的映射。

处理 Overlay 焦点不匹配与平台集成问题

Unreal 的 EOS 插件严重依赖 Epic Games Overlay 来显示通知、处理好友邀请以及执行 OAuth 网页 authentication。当您的游戏客户端使用 AccountPortal 凭据类型触发登录时,操作系统将启动浏览器窗口或打开 overlay。

这种重定向会将焦点从游戏窗口移开。如果您的输入模式未设置为处理焦点丢失,游戏可能会卡死或出现按键粘滞。

对于 PC 开发人员来说,处理由 overlay 引起的输入问题是一个普遍令人头疼的问题。例如,overlay 注入可能会与 raw input 驱动发生冲突,并导致键盘/鼠标状态冻结。为了防止这些 bug 破坏玩家体验,您可以查看我们关于 The Marathon Input Issues Fix: Architecting PC Games to Survive Overlay Conflicts 的分析。

此外,测试期间的初始化失败通常是由于本地配置中的 socket 连接或超时阈值配置错误引起的。如果您正在排查会话启动期间的驱动超时问题,请查看我们关于 Uefn Session Launch Timeout Nightmares: Diagnosing Unreal Engine Network Drivers 的指南。

简化跨平台商店 Backend

手动配置商店 Subsystems、编写 C++ delegate 链以及处理特定于平台的 overlay 焦点问题非常耗时。针对 Steam、EGS 和主机等多个商店重复这些工作,会使您的开发开销翻倍。

自己构建这些内容需要设置 Load Balancers、进行数据库分片以及 SSL 证书管理——这很容易耗费 4 到 6 周的工作量。

使用 horizOn,您将获得一个统一的跨平台 Backend,它对这些商店 API 进行了抽象。您无需处理双重 identity 凭据 (EAID/PUID)、Epic sandboxes 或 Steamworks 初始化代码,只需使用一个 API 即可处理用户个人资料、Achievements、Matchmaking 以及排行榜。

horizOn 作为一个桥梁,可以自动将 Achievements 和玩家进度同步到启动用户的任何商店,从而使您能够专注于核心游戏循环(game loop),而不是商店的 boilerplate 代码。这意味着您的玩家无论使用哪个平台或 launcher,都可以解锁 Achievements、访问云存档并寻找 Matches,而无需您编写单行针对特定商店的 SDK 代码。

Epic Online Services 集成的现代最佳实践

  1. 绝不直接串联 Asynchronous 节点: 始终等待 OnLoginCompleteOnQueryAchievementsComplete delegates 触发,然后再运行诸如写入 stats 或 Achievements 等下游操作。

  2. 在开发期间启用 Verbose Logging: 在您的 DefaultEngine.ini 中的 [Core.Log] 下添加以下行,以追踪隐藏的 EOS 警告:

[Core.Log]
LogOnline=VeryVerbose
LogOnlineGame=VeryVerbose
LogEOSSDK=VeryVerbose
  1. 在 Dev Portal 中将 Achievements 映射 to EOS: 确保在 Epic Games Store (EGS) 下创建的 Achievements 明确映射到您的 Epic Online Services (EOS) Backend。如果它们未被关联,EOS SDK 将无法识别该 Achievement ID。

  2. 在 Shipping Builds 中使用 ExchangeCode: 在本地开发期间,使用带有 Dev Auth Tool 的 Developer 登录类型很方便,但您必须在从 Epic Games Launcher 启动的 shipping builds 中将 credentials 类型切换为 ExchangeCode。使用 launcher 提供的 ExchangeCode auth 类型可以跳过 Web 浏览器 OAuth 重定向,将登录序列耗时从 8-12 秒的人工交互缩短至 500ms 以下的自动化握手。

简化商店集成

解决 Unreal Engine 中的 Epic Online Services 登录和 Achievement 错误归根结底在于理解 async 管道并保持配置文件完美一致。通过管理 identity 接口和游戏服务接口之间的转换,开发人员可以消除静默错误并提供无缝的玩家体验。

准备好扩展您的 Multiplayer Backend 了吗?免费试用 horizOn 或查看 API docs


Source: Epic Games Store (Online Services AND Achievements)