お世話になっております。
<br/>
現在、Unreal Engine 5.6.1 を使用して、マルチプラットフォーム向けのタイトルを開発しております。
OnlineSubsystem プラグインを利用した Steam版の実績の進捗率(プログレスバー連動)実装について、
仕様の確認と正しい実装方法をご教示いただきたく質問をお送りしました。
<br/>
【発生している現象】
例えば「敵を100人倒す」というような、進捗(数値)を伴う実績を実装しております。
FOnlineAchievementsSteam::WriteAchievements を使用して現在の進捗値を送信したところ、
解禁条件を満たしていないにもかかわらず、Steam側で該当の実績が即座に解禁されてしまいました。
<br/>
同時に開発しているPS5,Xboxの実装においては
同関数に実績名と現在の進捗値を渡すことで、
正常に各プラットフォーム側の実績プログレスのみが更新される挙動となっております。
<br/>
【エンジンコードの確認結果】
OnlineSubsystemSteam モジュールのソースコードに関して、下記の2点を確認しました。
<br/>
①FOnlineAchievementsSteam::WriteAchievements
進捗率の値にかかわらず、内部で無条件に SteamUserStats()->SetAchievement() が呼び出され
実績が即時強制解除される処理になっていると見受けられます。
<br/>
②FOnlineSubsystemSteam::GetStatsInterface
①で望む挙動が実現ができなかったため、Statsインターフェースから直接更新を試みましたが
OnlineSubsystemSteam.cpp
GetStatsInterface() は明示的に nullptr を返すことを確認いたしました。
<br/>
【質問】
上記2点を試みましたが、進捗率の処理が実現できませんでした。
Steam版において
「実績を即時解除させず、純粋に進捗率のみを更新・同期し、Steam側のプログレスバーを正しく動作させる」ために
推奨される実現方法をご教示いただけないでしょうか?
<br/>
お手数をおかけし申し訳ありませんが、よろしくお願いいたします。
お世話になっております。
結論としまして、Steam版では IOnlineAchievements::WriteAchievements を進捗更新用途に使わないのが正しいです。UEのOnlineSubsystemSteam 実装では、値が 100 未満でも SetAchievement() を呼ぶため、実績解除APIとして扱われます。現時点では、ご確認の2点は最新のエンジン(UE5.7のソース上)でも同じとなっており、WriteAchievements は値の大小を見ずに SetAchievement 側へ進み、GetStatsInterface() はSteamでは未実装となっておりました。現行の実装だと、WriteAchievements 後の非同期処理はStoreStats()で保存しているだけで、進捗値を Steam の achievement progress として解釈する処理はありません。Steam側で進捗を持たせる場合は、UEのAchievements インターフェースではなくSteamworks の stat/progress API を直接利用することになります。
OnlineAchievementsInterfaceSteam.cpp
// do not unlock it now, but after a successful write
#if !UE_BUILD_SHIPPING
float Value = 0.0f;
It.Value().GetValue(Value);
if (Value <= 0.0f)
{
UE_LOG_ONLINE_ACHIEVEMENTS(Verbose, TEXT("Resetting achievement '%s'"), *AchievementId);
SteamUserStats()->ClearAchievement(TCHAR_TO_UTF8(*AchievementId));
}
else
{
#endif // !UE_BUILD_SHIPPING
UE_LOG_ONLINE_ACHIEVEMENTS(Verbose, TEXT("Setting achievement '%s'"), *AchievementId);
SteamUserStats()->SetAchievement(TCHAR_TO_UTF8(*AchievementId));
また、GetStatsInterface() も Steam OSS では公開されていません。
IOnlineStatsPtr FOnlineSubsystemSteam::GetStatsInterface() const
{
return nullptr;
}
推奨実装はSteam版のみ Steamworks API を直接利用し、実績IDではなくSteamworks 側で設定した"進捗用 Stat"を更新する方法です。以下はその一例ですが、Steamworks Partner 側では、実績 ACH_KILL_100 に対して Progress Stat として STAT_KILL_ENEMIES を紐づけてください。SetStat + StoreStats が進捗同期、IndicateAchievementProgress は通知用途、SetAchievement は達成時のみ呼ぶ、という感じになるかと思います。
SteamUserStats()->SetStat("STAT_KILL_ENEMIES", CurrentKills);
SteamUserStats()->StoreStats();
if (CurrentKills < 100)
{
SteamUserStats()->IndicateAchievementProgress("ACH_KILL_100", CurrentKills, 100);
}
else
{
SteamUserStats()->SetAchievement("ACH_KILL_100");
SteamUserStats()->StoreStats();
}
Steamworks API を利用する際は、OnlineSubsystemSteam が Steam API の初期化を担当しているため、ゲーム側で SteamAPI_Init() / SteamAPI_Shutdown() を重ねて呼ばないようにする必要がある点にはご留意ください。
ご返答ありがとうございます。
>Steam側で進捗を持たせる場合は、UEのAchievements インターフェースではなくSteamworks の stat/progress API を直接利用することになります。
ご教示いただいた方法でSteam版は進捗状況の更新するようにし、
問題なく実装することができました。
ご対応いただき、ありがとうございました。
今後ともよろしくお願いいたします。