[UE5] Lyra改造計画⑤-2 OnlineSubsystemを使ってEOSでStats(統計データ) / Achievement(実績)を使う その1

Post 2023年7月2日日曜日

C++ Epic Online Services GASでオンラインシリーズ Lyra改造 UE5 Unreal Engine オンラインゲーム

 前回の続き。



お約束
この記事作成にあたって使用した主なUnreal Engine バージョンUE 5.1.1

本日のゴール

EOSでStatとAchievementを実装する


StatsとAchievementの関連性

Statsが決められた数字に達した時にAchievementが与えられます。

例えば勝利数が100になったら、「100勝」というAchievementを与えたり、敵の撃破数が千人になったら「一騎当千」というAchievementを与えたり。

こんなわけで、Statsが標準で対応している型はint32です。(どんなわけや)


Statsを作る

EOSのDeveloper PortalでまずStatsを作ります。


データの種類を選ぶ欄がありますが、基本はSUMで大丈夫です。

SUM-> 後ほどアップロードした数字を 追加 していくモード。決して減ることがないStatsはこれでいいです。(バトルした回数など)

LATEST -> 最新のデータを保存するSTATS。例えば最後にログインした日など。INTに変換しないといけませんが。

MIN -> 一番低いデータを保存するSTATS。MAXは最大。


最も重要なこと。STATSの名前は必ず、全て大文字にしましょう。

「Test」はだめで「TEST」ならOKです。大文字でないと、エラーが出ます。数字は入れてもOKです。


EOS実装のおさらい

①オンラインサブシステムを取り出すOnline::GetSubsystem(this->GetWorld());

②オンラインサブシステムから各ポインタを取り出す (①を省略して直接取り出しても良い)

③取り出したポインタから、各関数を呼び出して機能を実装する


Statsの研究

Statsを取り出すポインタを検索すると、どうやら下記のようです。

https://docs.unrealengine.com/5.2/en-US/API/Plugins/OnlineSubsystem/IOnlineSubsystem/GetStatsInterface/

ポインタ↓

  1. IOnlineStatsPtr GetStatsInterface() const

そして、上記ポインタを取り出すためのファンクションは下記↓

  1. IOnlineSubsystem::GetStatsInterface()

組み合わせると、下記のような感じになります。

  1. IOnlineStatsPtr StatRef = Online::GetStatsInterface(GetWorld());

ここから、ファンクションを取り出せばいいという流れは前回の通りです。


実際にVisual Studioに入れると、候補が出てくるので、そこから調べることができます。


それっぽいのはGetStats / QueryStats / UpdateStatsですかね?

(ていうかこんなアナログな調べ方が本当に正しいのか、わからんですが。。。みんなこんな調べ方してるんですかね?)


ということで、UpdateStatsを呼び出して定義に飛んでやります。するとこんな定義が書いてあります。

  1. virtual void UpdateStats(
  2. const FUniqueNetIdRef LocalUserId, 
  3. const TArray<FOnlineStatsUserUpdatedStats>& UpdatedUserStats, 
  4. const FOnlineStatsUpdateStatsComplete& Delegate
  5. ) override;

ちなみに私は上記を見ても何を引数に入れればいいのかちんぷんかんぷんです。(C++分からないので)

手探りでやっていくしかない。(オイ)

多分ですけど、今までの流れからも①ユーザーID ②アップデートするStatsの対象(FOnlineStatsUserUpdatedStats) ③デリゲート関数(FOnlineStatsUpdateStatsComplete&)を引数として入れてやればいいはず。



実際のコードを見てみる

過程は吹っ飛ばして結果だけ見ていきましょう。

まず必要なライブラリをロードしておきます。下記の一番下の行をCommonSessionSubsystem.hの最初の方に追加します。

  1. #if COMMONUSER_OSSV1
  2. #include "OnlineSubsystemTypes.h"
  3. #include "Interfaces/OnlineSessionInterface.h"
  4. #include "Public/OnlineSessionSettings.h"
  5. #include "Interfaces/OnlineStatsInterface.h" //<-----Add

同じファイルに下記を追加。「ChangeStats」という関数を作ります。

  1. /** StatChange */
  2. UFUNCTION(BlueprintCallable, Category = Session)
  3. void ChangeStats(FString StatsName, int32 StatsNumber);
  4. void OnChangeStatsComplete(const FOnlineError& ErrorDaze); 

CommonSessionSubsystem.cppに下記を追加。「ChangeStats」という関数を実装します。

  1. void UCommonSessionSubsystem::ChangeStats( FString StatsName, int32 StatsNumber)
  2. {
  3.     UE_LOG(LogTemp, Warning, TEXT("Stats Update Start Daze"));
  4.     IOnlineIdentityPtr IdentityPointerRef = Online::GetIdentityInterface(GetWorld());
  5.     check(IdentityPointerRef);
  6.     IOnlineStatsPtr StatRef = Online::GetStatsInterface(GetWorld());
  7.     check(StatRef);
  8.     FOnlineStatsUserUpdatedStats StatsDaze = FOnlineStatsUserUpdatedStats(IdentityPointerRef->GetUniquePlayerId(0).ToSharedRef());
  9.     StatsDaze.Stats.Add(StatsName, FOnlineStatUpdate(StatsNumber, FOnlineStatUpdate::EOnlineStatModificationType::Unknown));
  10.     StatRef->UpdateStats(IdentityPointerRef->GetUniquePlayerId(0).ToSharedRef(), {StatsDaze}, FOnlineStatsUpdateStatsComplete::CreateUObject(this, &UCommonSessionSubsystem::OnChangeStatsComplete));
  11. }
  12. void UCommonSessionSubsystem::OnChangeStatsComplete(const FOnlineError& ErrorDaze)
  13. {
  14.     if (ErrorDaze.bSucceeded) {
  15.         UE_LOG(LogTemp, Warning, TEXT("Stats Update Complete Yooo %s"), *ErrorDaze.ToLogString());
  16.     }
  17.     else {
  18.         UE_LOG(LogTemp, Warning, TEXT("Stats Update Failed!!!"));
  19.     }
  20. }

っ何が何だかな感じですが、5-8行目はいつものポインタの取得と、チェックです。

10-13行目が何?って感じですが

FOnlineStatsUserUpdatedStatsこれがスタッツの上書きには必要なので、これを作ります。

このFOnlineStatsUserUpdatedStatsには、いつものユーザー情報(TSharedのFUniqueID)と、スタッツの名前、スタッツに書き込む情報が要ります。

11行目がそれです。末尾のUnknownはSumとかにできます。UnknownにしておくとEOS側でそれぞれのStatsに対して登録した設定で書き込みされるみたいです。

※Sumだと書き込んだ数字を足します

EOSのStatsの新規作成で出てくるこれですこれ。

13行目、これで書き込んでます。

書き込み結果をどうやって、確認するのか?これが分からなくてえらい苦労しました。(公式に聞いてしまいました、回答来て神です。)



まずはDeveloper PortalのStatsのページを開きますと、このような不穏なボタンがあり、こちらをクリックすると

このようにユーザーIDを入れる画面が出てくるので、ここに入れて検索してやれば、ちゃんとデータがあるかわかります。
言っちゃなんですが…これ絶対にたどり着かないでしょ…。


Getの方も実装していく。

CommonSessionSubsystem.hの#include郡の下に、下記を追加。Getが完了したことを表すデリゲートの準備です。

  1. DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FStatsDataStream, FString, StatsName, int32, StatsValue);
  2. DECLARE_DYNAMIC_MULTICAST_DELEGATE(FFaliledToLoadData);

CommonSessionSubsystem.hに下記を追加

  1. /** StatChange */
  2. UFUNCTION(BlueprintCallable, Category = Session)
  3. void ChangeStats(FString StatsName, int32 StatsNumber);
  4. void OnChangeStatsComplete(const FOnlineError& ErrorDaze); 
  5. /** StatGet */
  6. UFUNCTION(BlueprintCallable, Category = Session)
  7. void GetStatsValue(APlayerController* JoiningPlayer, APlayerController* TargetPlayer, FString StatsName);
  8. UPROPERTY(BlueprintAssignable, Category = Session)
  9. FStatsDataStream GottenStatsData;

  10. void OnQueryPlayerStatsComplete(const FOnlineError& ErrorDaze, const TArray<TSharedRef<const FOnlineStatsUserStats>>& UsersStatsResults);


CommonSessionSubsystem.cppに下記を追加

  1. void UCommonSessionSubsystem::GetStatsValue(APlayerController* JoiningPlayer, APlayerController* TargetPlayer, FString StatsName)
  2. {
  3.     UE_LOG(LogTemp, Warning, TEXT("Get Stats Starts"));
  4.     check(JoiningPlayer);
  5.     ULocalPlayer* LocalPlayer = JoiningPlayer->GetLocalPlayer();
  6.     ULocalPlayer* Target = TargetPlayer->GetLocalPlayer();
  7.     IOnlineStatsPtr StatRef = Online::GetStatsInterface(GetWorld());
  8.     check(StatRef);
  9.     StatRef->QueryStats(LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId().ToSharedRef(), {Target->GetPreferredUniqueNetId().GetUniqueNetId().ToSharedRef()}, { StatsName }, FOnlineStatsQueryUsersStatsComplete::CreateUObject(this, &UCommonSessionSubsystem::OnQueryPlayerStatsComplete));
  10.     
  11. }
  12. void UCommonSessionSubsystem::OnQueryPlayerStatsComplete(const FOnlineError& ErrorDaze, const TArray<TSharedRef<const FOnlineStatsUserStats>>& UsersStatsResults)
  13. {
  14.     if (ErrorDaze.WasSuccessful()) {
  15.         UE_LOG(LogTemp, Warning, TEXT("Stats Acquire"));
  16.         for (auto StatsNum : UsersStatsResults) {
  17.             for (auto StoredVaue : StatsNum->Stats) {
  18.                 FString KeyName = StoredVaue.Key;
  19.                 int32 WantValue;
  20.                 StoredVaue.Value.GetValue(WantValue);
  21.                 GottenStatsData.Broadcast(*KeyName, WantValue);
  22.             }
  23.         }
  24.     }
  25.     else {
  26.         UE_LOG(LogTemp, Warning, TEXT("Stats Get Failed!!!"));
  27.     }
  28. }


Getの方も基本は同じなので割愛しますが、

面白いなと思ったのは、スタッツを読むプレイヤーと、スタッツを読まれるプレイヤー(上で言うターゲット)を指定できるんですね。

12行目でQueryStatsを起動してStatsの起動をしてます。GetStatsという関数もあってこっちも使えそうでしたが、なんかうまく動かなかったです。多分Getの方は、Statsの一覧を取得する関数っぽい?

これを呼び出すには、まずどこかのブループリントでイベントディスパッチャー的にデリゲートを呼び出しておいて、



実際にStatsを表示したいタイミングでGet Statsを呼び出します。


今回は、自分のStats Valueを得たいので、Joining PlayerもTarget Playerも自分にしてあります。

今回は、こんな感じで以上です。Achievementは次回かなぁ。。。