メインコンテンツまでスキップ

オンラインサブシステムを使用してプレゼンスを設定する - プレゼンスの基本 - (Unreal Engine モジュール)

Last updated on February 4, 2026

注釈:本資料はAI技術を用いて翻訳されています。

サブシステムを展開する

このチュートリアルでは、AGS オンラインサブシステム (OSS) を使用してプレイヤーのプレゼンスステータスを設定および取得する方法を説明します。Byte Wars プロジェクトには、PresenceEssentialsSubsystem という名前のゲームインスタンスサブシステムがすでに作成されています。このサブシステムには、プレイヤーのプレゼンスステータスを設定および取得するための完全な実装が含まれています。ただし、このチュートリアルでは、そのサブシステムのスターターバージョンを使用して、関数をゼロから実装します。

スターターパックの内容

このチュートリアルに従うために、PresenceEssentialsSubsystem_Starter という名前のスターターサブシステムクラスが用意されています。このクラスは リソース セクションで入手でき、以下のファイルで構成されています。

  • ヘッダーファイル: /Source/AccelByteWars/TutorialModules/Social/PresenceEssentials/PresenceEssentialsSubsystem_Starter.h
  • CPP ファイル: /Source/AccelByteWars/TutorialModules/Social/PresenceEssentials/PresenceEssentialsSubsystem_Starter.cpp

PresenceEssentialsSubsystem_Starter クラスには、いくつかのヘルパーも含まれています。

  • AGS OSS インターフェースの宣言セット:

    • GetPresenceInterface(): AGS Presence インターフェースを返す関数。このインターフェースを使用して、プレイヤーのプレゼンスを取得および設定します。
    • GetFriendsInterface(): AGS Friends インターフェースを返す関数。このインターフェースを使用して、フレンドリストまたはブロックされたプレイヤーリストが更新されたときにプレイヤーのプレゼンスを更新します。
    • GetIdentityInterface(): AGS Identity インターフェースを返す関数。このインターフェースを使用して、プレイヤーがログインしたときにプレイヤーのプレゼンスステータスを初期化します。
    • GetSessionInterface(): AGS Session インターフェースを返す関数。このインターフェースを使用して、ゲームセッションメンバーリストが更新されたときにプレイヤーのプレゼンスを更新します。
    • GetOnlineSession(): Byte Wars オンラインセッションラッパーを返す関数。これを使用して、パーティーやマッチメイキングステータスなど、プレゼンスステータスにプレイヤーのゲームアクティビティを設定します。
    protected:
    // ...
    FOnlinePresenceAccelBytePtr GetPresenceInterface() const;
    TSharedPtr<FOnlineFriendsAccelByte, ESPMode::ThreadSafe> GetFriendsInterface() const;
    TSharedPtr<FOnlineIdentityAccelByte, ESPMode::ThreadSafe> GetIdentityInterface() const;
    TSharedPtr<FOnlineSessionV2AccelByte, ESPMode::ThreadSafe> GetSessionInterface() const;
    UAccelByteWarsOnlineSessionBase* GetOnlineSession() const;
  • ログインしているプレイヤーを取得するヘルパー関数。これを使用して、後でプレイヤーのプレゼンスステータスを設定します。

    FUniqueNetIdPtr UPresenceEssentialsSubsystem_Starter::GetPrimaryPlayerUserId()
    {
    if (!GetGameInstance())
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to get current logged in player's User Id. GameInstance is invalid."));
    return nullptr;
    }

    const APlayerController* PC = GetGameInstance()->GetFirstLocalPlayerController();
    if (!PC)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to get current logged in player's User Id. PlayerController is invalid."));
    return nullptr;
    }

    const ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
    if (!LocalPlayer)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to get current logged in player's User Id. LocalPlayer is invalid."));
    return nullptr;
    }

    return LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId();
    }

また、/Source/AccelByteWars/TutorialModules/Social/PresenceEssentials/PresenceEssentialsModels.h にモデルファイルがあります。このファイルには以下のヘルパーが含まれています。

  • ログとメッセージを出力するための文字列定数。

    #define TEXT_PRESENCE_ONLINE NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_online", "Online")
    #define TEXT_PRESENCE_OFFLINE NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_offline", "Offline")

    #define TEXT_PRESENCE_LAST_ONLINE_YEARS NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_last_online_years_ago", "Last Online Years Ago")
    #define TEXT_PRESENCE_LAST_ONLINE_MONTHS NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_last_online_months_ago", "Last Online {0} Month(s) Ago")
    #define TEXT_PRESENCE_LAST_ONLINE_DAYS NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_last_online_days_ago", "Last Online {0} Day(s) Ago")
    #define TEXT_PRESENCE_LAST_ONLINE_HOURS NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_last_online_hours_ago", "Last Online {0} Hour(s) Ago")
    #define TEXT_PRESENCE_LAST_ONLINE_MINUTES NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_last_online_minutes_ago", "Last Online {0} Minute(s) Ago")
    #define TEXT_PRESENCE_LAST_ONLINE_AWHILE NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_last_online_while_ago", "Last Online a While Ago")

    #define TEXT_PRESENCE_LEVEL_MAINMENU NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_level_main_menu", "In Main Menu")
    #define TEXT_PRESENCE_LEVEL_GAMEPLAY NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_level_gameplay", "In Match")
    #define TEXT_PRESENCE_ACTIVITY_PARTY NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_activity_party", "In Party")
    #define TEXT_PRESENCE_ACTIVITY_MATCHMAKING NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_activity_matchmaking", "Matchmaking")
    #define TEXT_PRESENCE_ACTIVITY_LOBBY NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_activity_lobby", "Lobby")
    #define TEXT_PRESENCE_ACTIVITY_GAMEPLAY NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_activity_gameplay", "Game Mode: {0}")
    #define TEXT_PRESENCE_ACTIVITY_UNKNOWN NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "presence_activity_unknown", "Unknown Activity")
  • 最終オンライン日時を読みやすいテキストに変換する関数。

    inline static FString GetLastOnline(const FDateTime LastOnline) 
    {
    // Only check last online within a year.
    const FDateTime CurrentTime = FDateTime::UtcNow();
    if (CurrentTime.GetYear() != LastOnline.GetYear())
    {
    return TEXT_PRESENCE_LAST_ONLINE_YEARS.ToString();
    }

    // Check last online in months.
    if (CurrentTime.GetMonth() > LastOnline.GetMonth())
    {
    const int32 Months = CurrentTime.GetMonth() - LastOnline.GetMonth();
    return FText::Format(TEXT_PRESENCE_LAST_ONLINE_MONTHS, Months).ToString();
    }

    // Check last online in days.
    if (CurrentTime.GetDay() > LastOnline.GetDay())
    {
    const int32 Days = CurrentTime.GetDay() - LastOnline.GetDay();
    return FText::Format(TEXT_PRESENCE_LAST_ONLINE_DAYS, Days).ToString();
    }

    // Check last online in hours.
    if (CurrentTime.GetHour() > LastOnline.GetHour())
    {
    const int32 Hours = CurrentTime.GetHour() - LastOnline.GetHour();
    return FText::Format(TEXT_PRESENCE_LAST_ONLINE_HOURS, Hours).ToString();
    }

    // Check last online in minutes.
    if (CurrentTime.GetMinute() > LastOnline.GetMinute())
    {
    const int32 Minutes = CurrentTime.GetMinute() - LastOnline.GetMinute();
    return FText::Format(TEXT_PRESENCE_LAST_ONLINE_MINUTES, Minutes).ToString();
    }

    return TEXT_PRESENCE_LAST_ONLINE_AWHILE.ToString();
    }

プレゼンスの取得を実装する

このセクションでは、プレイヤーのプレゼンスを取得する関数を実装します。

  1. PresenceEssentialsSubsystem_Starter クラスのヘッダーファイルを開き、以下の関数を宣言します。

    public:
    // ...
    void GetPresence(const FUniqueNetIdPtr UserId, bool bForceQuery, const FOnPresenceTaskComplete& OnComplete = FOnPresenceTaskComplete());
    protected:
    void OnGetPresenceComplete(const class FUniqueNetId& UserId, const bool bWasSuccessful, const FOnPresenceTaskComplete OnComplete);
  2. 次に、上記で言及した関数を定義します。PresenceEssentialsSubsystem_Starter クラスの CPP ファイルを開き、まず GetPresence() 関数を定義します。以下のコードは、キャッシュからプレイヤーのプレゼンスデータを取得します。キャッシュに存在しない場合は、バックエンドにクエリを実行します。

    void UPresenceEssentialsSubsystem_Starter::GetPresence(const FUniqueNetIdPtr UserId, bool bForceQuery, const FOnPresenceTaskComplete& OnComplete)
    {
    FOnlinePresenceAccelBytePtr PresenceInterface = GetPresenceInterface();
    if (!PresenceInterface)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Cannot get presence. Presence interface is invalid."));
    OnComplete.ExecuteIfBound(false, nullptr);
    return;
    }

    const FUniqueNetIdAccelByteUserPtr UserABId = StaticCastSharedPtr<const FUniqueNetIdAccelByteUser>(UserId);
    if (!UserABId)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Cannot get presence. User Id is invalid."));
    OnComplete.ExecuteIfBound(false, nullptr);
    return;
    }

    if(!bForceQuery)
    {
    // Try get the presence from cache.
    TSharedPtr<FOnlineUserPresence> OutPresence = nullptr;
    PresenceInterface->GetCachedPresence(UserABId.ToSharedRef().Get(), OutPresence);
    if (TSharedPtr<FOnlineUserPresenceAccelByte> ABPresence = StaticCastSharedPtr<FOnlineUserPresenceAccelByte>(OutPresence))
    {
    UE_LOG_PRESENCEESSENTIALS(Log, TEXT("Success to get presence for user: %s"), *UserABId->GetAccelByteId());
    OnComplete.ExecuteIfBound(true, ABPresence);
    return;
    }
    }

    // If the presence is not available on cache, then query it.
    PresenceInterface->QueryPresence(
    UserABId.ToSharedRef().Get(),
    IOnlinePresence::FOnPresenceTaskCompleteDelegate::CreateUObject(this, &ThisClass::OnGetPresenceComplete, OnComplete));
    }
  3. 最後に、OnGetPresenceComplete() 関数を定義します。この関数は、渡されたデリゲートを呼び出して、プレゼンス取得プロセスが成功したかどうかを通知します。

    void UPresenceEssentialsSubsystem_Starter::OnGetPresenceComplete(const FUniqueNetId& UserId, const bool bWasSuccessful, const FOnPresenceTaskComplete OnComplete)
    {
    const FUniqueNetIdAccelByteUserRef UserABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(UserId.AsShared());
    if (!UserABId->IsValid())
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Failed to get presence. User Id is invalid."));
    OnComplete.ExecuteIfBound(false, nullptr);
    return;
    }

    FOnlinePresenceAccelBytePtr PresenceInterface = GetPresenceInterface();
    if (!PresenceInterface)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Failed to get presence for user: %s. Presence interface is invalid."), *UserABId->GetAccelByteId());
    OnComplete.ExecuteIfBound(false, nullptr);
    return;
    }

    if (!bWasSuccessful)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Failed to get presence for user: %s. Operation failed."), *UserABId->GetAccelByteId());
    OnComplete.ExecuteIfBound(false, nullptr);
    return;
    }

    TSharedPtr<FOnlineUserPresence> OutPresence;
    PresenceInterface->GetCachedPresence(UserABId.Get(), OutPresence);

    TSharedPtr<FOnlineUserPresenceAccelByte> ABPresence = StaticCastSharedPtr<FOnlineUserPresenceAccelByte>(OutPresence);
    if (!ABPresence)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Failed to get presence for user: %s. Invalid presence type."), *UserABId->GetAccelByteId());
    OnComplete.ExecuteIfBound(false, nullptr);
    return;
    }

    UE_LOG_PRESENCEESSENTIALS(Log, TEXT("Success to get presence for user: %s"), *UserABId->GetAccelByteId());

    OnComplete.ExecuteIfBound(true, ABPresence);
    }

一括プレゼンス取得を実装する

このセクションでは、複数のプレイヤーのプレゼンスをクエリおよび取得する関数を実装します。

  1. PresenceEssentialsSubsystem_Starter クラスのヘッダーファイルを開き、以下の関数を宣言します。

    public:
    // ...
    void BulkQueryPresence(const FUniqueNetIdPtr UserId, const TArray<FUniqueNetIdRef>& UserIds);
  2. 次に、一括プレゼンスクエリが完了したときに処理するデリゲートとコールバック関数を宣言します。このデリゲートは、チュートリアルの次のセクションで UI を更新するために使用します。

    public:
    // ...
    FOnBulkQueryPresenceComplete* GetOnBulkQueryPresenceCompleteDelegates()
    {
    return &OnBulkQueryPresenceCompleteDelegates;
    }
    protected:
    // ...
    void OnBulkQueryPresenceComplete(const bool bWasSuccessful, const FUserIDPresenceMap& Presences);
    private:
    // ...
    FOnBulkQueryPresenceComplete OnBulkQueryPresenceCompleteDelegates;
  3. 次に、上記で言及した関数を定義します。PresenceEssentialsSubsystem_Starter クラスの CPP ファイルを開き、まず BulkQueryPresence() 関数を定義します。以下のコードは、キャッシュからプレイヤーのプレゼンスリストを取得します。キャッシュに情報が見つからない場合は、バックエンドにプレゼンスをクエリします。

    void UPresenceEssentialsSubsystem_Starter::BulkQueryPresence(const FUniqueNetIdPtr UserId, const TArray<FUniqueNetIdRef>& UserIds)
    {
    FUserIDPresenceMap CachedPresences;

    FOnlinePresenceAccelBytePtr PresenceInterface = GetPresenceInterface();
    if (!PresenceInterface)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Cannot bulk query presence. Presence interface is invalid."));
    OnBulkQueryPresenceComplete(false, CachedPresences);
    return;
    }

    if (!UserId || UserIds.IsEmpty())
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Cannot bulk query presence. User Ids are invalid."));
    OnBulkQueryPresenceComplete(false, CachedPresences);
    return;
    }

    // Try to collect cached presences.
    for (const FUniqueNetIdRef& TargetUserId : UserIds)
    {
    const FUniqueNetIdAccelByteUserRef TargetUserABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(TargetUserId);
    if (!TargetUserABId->IsValid())
    {
    return;
    }

    TSharedPtr<FOnlineUserPresence> OutPresence;
    PresenceInterface->GetCachedPresence(TargetUserABId.Get(), OutPresence);

    if (const TSharedPtr<FOnlineUserPresenceAccelByte> ABPresence = StaticCastSharedPtr<FOnlineUserPresenceAccelByte>(OutPresence))
    {
    CachedPresences.Add(TargetUserABId->GetAccelByteId(), ABPresence.ToSharedRef());
    }
    else
    {
    break;
    }
    }

    // The cached presences are complete, return the cached presence.
    if (CachedPresences.Num() == UserIds.Num())
    {
    OnBulkQueryPresenceComplete(true, CachedPresences);
    return;
    }

    // There are some missing cached presences, try to query.
    PresenceInterface->BulkQueryPresence(UserId.ToSharedRef().Get(), UserIds);
    }
  4. 同じファイルで、コールバック関数を定義します。この関数は、先ほど作成したデリゲートをブロードキャストして、一括プレゼンスクエリプロセスが成功したかどうかを通知します。

    void UPresenceEssentialsSubsystem_Starter::OnBulkQueryPresenceComplete(const bool bWasSuccessful, const FUserIDPresenceMap& Presences)
    {
    UE_LOG_PRESENCEESSENTIALS(Log, TEXT("%s to bulk query presences. Presences found: %d"), bWasSuccessful ? TEXT("Success") : TEXT("Failed"), Presences.Num());

    OnBulkQueryPresenceCompleteDelegates.Broadcast(bWasSuccessful, Presences);
    }
  5. 次に、一括プレゼンスクエリが完了したときに呼び出されるように、コールバック関数をバインドします。これは、サブシステムが初期化されたときに最初に呼び出される Initialize() 関数に以下のコードを追加することで実行できます。

    void UPresenceEssentialsSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
    {
    // ...
    // Listen to presence events.
    if (FOnlinePresenceAccelBytePtr PresenceInterface = GetPresenceInterface())
    {
    // ...
    PresenceInterface->AddOnBulkQueryPresenceCompleteDelegate_Handle(FOnBulkQueryPresenceCompleteDelegate::CreateUObject(this, &ThisClass::OnBulkQueryPresenceComplete));
    }
    }
  6. 最後に、サブシステムが非初期化されたときにコールバック関数をアンバインドする必要があります。これは、以下のコードを追加することで実現できます。

    void UPresenceEssentialsSubsystem_Starter::Deinitialize()
    {
    // ...
    if (FOnlinePresenceAccelBytePtr PresenceInterface = GetPresenceInterface())
    {
    // ...
    PresenceInterface->ClearOnBulkQueryPresenceCompleteDelegates(this);
    }
    }

プレゼンスステータスの設定を実装する

このセクションでは、個々のプレイヤーのプレゼンスを設定して、オンラインステータスとゲームアクティビティを示す関数を実装します。

  1. PresenceEssentialsSubsystem_Starter クラスのヘッダーファイルを開き、以下の関数を宣言します。

    public:
    // ...
    void SetPresenceStatus(const FUniqueNetIdPtr UserId, const FString& Status, const FOnPresenceTaskComplete& OnComplete = FOnPresenceTaskComplete());
    protected:
    // ...
    void OnSetPresenceStatusComplete(const class FUniqueNetId& UserId, const bool bWasSuccessful, const FOnPresenceTaskComplete OnComplete);
  2. 次に、上記で言及した関数を定義します。PresenceEssentialsSubsystem_Starter クラスの CPP ファイルを開き、まず SetPresenceStatus() 関数を定義して、プレイヤーのオンラインプレゼンスステータスとステータス文字列を設定します。

    void UPresenceEssentialsSubsystem_Starter::SetPresenceStatus(const FUniqueNetIdPtr UserId, const FString& Status, const FOnPresenceTaskComplete& OnComplete)
    {
    FOnlinePresenceAccelBytePtr PresenceInterface = GetPresenceInterface();
    if (!PresenceInterface)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Cannot set presence status. Presence interface is invalid."));
    OnComplete.ExecuteIfBound(false, nullptr);
    return;
    }

    if (!UserId)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Cannot set presence status. User Id is invalid."));
    OnComplete.ExecuteIfBound(false, nullptr);
    return;
    }

    FOnlineUserPresenceStatus PresenceStatus;
    PresenceStatus.StatusStr = Status;
    PresenceStatus.State = EOnlinePresenceState::Type::Online;

    PresenceInterface->SetPresence(
    UserId.ToSharedRef().Get(),
    PresenceStatus,
    IOnlinePresence::FOnPresenceTaskCompleteDelegate::CreateUObject(this, &ThisClass::OnSetPresenceStatusComplete, OnComplete));
    }
  3. 次に、OnSetPresenceStatusComplete() 関数を定義します。この関数は、渡されたデリゲートを呼び出して、プレゼンス設定プロセスが成功したかどうかを通知します。

    void UPresenceEssentialsSubsystem_Starter::OnSetPresenceStatusComplete(const FUniqueNetId& UserId, const bool bWasSuccessful, const FOnPresenceTaskComplete OnComplete)
    {
    const FUniqueNetIdAccelByteUserRef UserABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(UserId.AsShared());
    if (!UserABId->IsValid())
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Failed to set presence status. User Id is invalid."));
    OnComplete.ExecuteIfBound(false, nullptr);
    return;
    }

    FOnlinePresenceAccelBytePtr PresenceInterface = GetPresenceInterface();
    if (!PresenceInterface)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Failed to set presence status for user: %s. Presence interface is invalid."), *UserABId->GetAccelByteId());
    OnComplete.ExecuteIfBound(false, nullptr);
    return;
    }

    TSharedPtr<FOnlineUserPresence> OutPresence;
    PresenceInterface->GetCachedPresence(UserABId.Get(), OutPresence);

    const TSharedPtr<FOnlineUserPresenceAccelByte> ABPresence = StaticCastSharedPtr<FOnlineUserPresenceAccelByte>(OutPresence);
    if (bWasSuccessful && ABPresence)
    {
    UE_LOG_PRESENCEESSENTIALS(Log, TEXT("Success to set presence status for user: %s"), *UserABId->GetAccelByteId());
    OnComplete.ExecuteIfBound(true, ABPresence);
    }
    else
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Failed to set presence status for user: %s. Operation failed."), *UserABId->GetAccelByteId());
    OnComplete.ExecuteIfBound(false, nullptr);
    }
    }

プレゼンス更新受信時の処理

このセクションでは、受信したプレイヤーのプレゼンス更新を処理する関数を実装します。このイベントを管理して、後で UI で更新されたプレゼンスを表示するために使用される情報をブロードキャストします。

  1. PresenceEssentialsSubsystem_Starter クラスのヘッダーファイルを開き、プレゼンス更新が受信されたときに処理するデリゲートとコールバック関数を宣言します。このデリゲートは、チュートリアルの次のセクションで UI を更新するために使用します。

    public:
    // ...
    FOnPresenceReceived* GetOnPresenceReceivedDelegates()
    {
    return &OnPresenceReceivedDelegates;
    }
    protected:
    // ...
    void OnPresenceReceived(const class FUniqueNetId& UserId, const TSharedRef<FOnlineUserPresence>& Presence);
    private:
    // ...
    FOnPresenceReceived OnPresenceReceivedDelegates;
  2. 次に、PresenceEssentialsSubsystem_Starter クラスの CPP ファイルを開き、OnPresenceReceived() 関数を定義します。この関数が呼び出されると、先ほど作成したデリゲートをブロードキャストして、新しいプレゼンス更新が受信されたことを通知します。

    void UPresenceEssentialsSubsystem_Starter::OnPresenceReceived(const FUniqueNetId& UserId, const TSharedRef<FOnlineUserPresence>& Presence)
    {
    const FUniqueNetIdAccelByteUserRef UserABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(UserId.AsShared());
    if (!UserABId->IsValid())
    {
    UE_LOG_PRESENCEESSENTIALS(Log, TEXT("Received presence update for but is null"));
    return;
    }

    UE_LOG_PRESENCEESSENTIALS(Log, TEXT("Received presence update for user: %s"), *UserABId->GetAccelByteId());

    OnPresenceReceivedDelegates.Broadcast(UserABId.Get(), Presence);
    }
  3. 次に、プレイヤーのプレゼンス更新が受信されたときに呼び出されるように、コールバック関数をバインドする必要があります。これは、サブシステムが初期化されたときに最初に呼び出される Initialize() 関数に以下のコードを追加することで実行できます。

    void UPresenceEssentialsSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
    {
    // ...
    // Listen to presence events.
    if (FOnlinePresenceAccelBytePtr PresenceInterface = GetPresenceInterface())
    {
    PresenceInterface->AddOnPresenceReceivedDelegate_Handle(FOnPresenceReceivedDelegate::CreateUObject(this, &ThisClass::OnPresenceReceived));
    // ...
    }
    }
  4. 最後に、サブシステムが非初期化されたときにコールバック関数をアンバインドする必要があります。これは、以下のコードを追加することで実行できます。

    void UPresenceEssentialsSubsystem_Starter::Deinitialize()
    {
    // ...
    if (FOnlinePresenceAccelBytePtr PresenceInterface = GetPresenceInterface())
    {
    PresenceInterface->ClearOnPresenceReceivedDelegates(this);
    // ...
    }
    }

プレイヤーリスト更新時のプレゼンス処理

プレイヤーのフレンドリスト、ブロックされたプレイヤーリスト、およびゲームセッションメンバーリストが更新されたときは、それらのプレゼンスも更新する必要があります。このセクションでは、その方法を学びます。

  1. PresenceEssentialsSubsystem_Starter クラスのヘッダーファイルを開き、以下の関数を宣言します。

    private:
    // ...
    void OnFriendListChange();
    void OnBlockedPlayerListChange(int32 LocalUserNum, const FString& ListName);
    #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
    void OnSessionParticipantJoined(FName SessionName, const FUniqueNetId& UserId);
    #else
    void OnSessionParticipantChange(FName SessionName, const FUniqueNetId& UserId, bool bJoined);
    #endif
  2. 次に、PresenceEssentialsSubsystem_Starter クラスの CPP ファイルを開き、まず OnFriendListChange() 関数を定義します。この関数は、フレンドのユーザー ID を収集し、前のセクションで作成した関数を使用してそれらのプレゼンスを一括クエリします。

    void UPresenceEssentialsSubsystem_Starter::OnFriendListChange()
    {
    FOnlineFriendsAccelBytePtr FriendsInterface = GetFriendsInterface();
    if (!FriendsInterface)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to query friends' presences. Friends interface is invalid."));
    return;
    }

    const FUniqueNetIdPtr UserId = GetPrimaryPlayerUserId();
    if (!UserId)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to query friends' presences. Current logged-in player's User Id is invalid."));
    return;
    }

    // Get cached friend list.
    TArray<TSharedRef<FOnlineFriend>> OutFriendList;
    if (!FriendsInterface->GetFriendsList(0, TEXT(""), OutFriendList))
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to query friends' presences. Cannot get cached friend list."));
    return;
    }
    if (OutFriendList.IsEmpty())
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to query friends' presences. No friends found."));
    return;
    }

    // Collect their user IDs.
    TArray<FUniqueNetIdRef> FriendIds;
    for (const TSharedRef<FOnlineFriend>& Friend : OutFriendList)
    {
    FriendIds.Add(Friend->GetUserId());
    }

    // Query friends' presences.
    UE_LOG_PRESENCEESSENTIALS(Log, TEXT("Querying friends' presences."));
    BulkQueryPresence(UserId, FriendIds);
    }
  3. 次に、OnBlockedPlayerListChange() 関数を定義します。同様に、この関数はブロックされたプレイヤーのユーザー ID を収集し、それらのプレゼンスを一括クエリします。

    void UPresenceEssentialsSubsystem_Starter::OnBlockedPlayerListChange(int32 LocalUserNum, const FString& ListName)
    {
    FOnlineFriendsAccelBytePtr FriendsInterface = GetFriendsInterface();
    if (!FriendsInterface)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to query blocked players' presences. Friends interface is invalid."));
    return;
    }

    const FUniqueNetIdPtr UserId = GetPrimaryPlayerUserId();
    if (!UserId)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to query blocked player' presences. Current logged-in player's User Id is invalid."));
    return;
    }

    // Get cached blocked player list.
    TArray<TSharedRef<FOnlineBlockedPlayer>> OutBlockedPlayerList;
    if (!FriendsInterface->GetBlockedPlayers(UserId.ToSharedRef().Get(), OutBlockedPlayerList))
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to query blocked players' presences. Cannot get cached blocked player list."));
    return;
    }
    if (OutBlockedPlayerList.IsEmpty())
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to query blocked players' presences. No blocked player found."));
    return;
    }

    // Collect their user IDs.
    TArray<FUniqueNetIdRef> BlockedPlayerIds;
    for (const TSharedRef<FOnlineBlockedPlayer>& BlockedPlayer : OutBlockedPlayerList)
    {
    BlockedPlayerIds.Add(BlockedPlayer->GetUserId());
    }

    // Query blocked players' presences.
    UE_LOG_PRESENCEESSENTIALS(Log, TEXT("Querying blocked players' presences."));
    BulkQueryPresence(UserId, BlockedPlayerIds);
    }
  4. OnSessionParticipantJoined() および OnSessionParticipantChange() 関数を定義します。この関数は、プレイヤーがアクティブなゲームセッションから退出または参加したときに呼び出されます。これが発生すると、この関数はプレイヤーのプレゼンスを更新します。

    #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
    void UPresenceEssentialsSubsystem_Starter::OnSessionParticipantJoined(FName SessionName, const FUniqueNetId& UserId)
    {
    UE_LOG_PRESENCEESSENTIALS(Log, TEXT("Update presence when session participant change"));
    GetPresence(UserId.AsShared(), true);
    }
    #else
    void UPresenceEssentialsSubsystem_Starter::OnSessionParticipantChange(FName SessionName, const FUniqueNetId& UserId, bool bJoined)
    {
    if(!bJoined)
    {
    UE_LOG_PRESENCEESSENTIALS(Log, TEXT("Update presence when session participant change"));
    GetPresence(UserId.AsShared(),true);
    }
    }
  5. 次に、プレイヤーリストが更新されたときに呼び出されるように、上記の関数をバインドする必要があります。これは、サブシステムが初期化されたときに最初に呼び出される Initialize() 関数に以下のコードを追加することで実行できます。

    void UPresenceEssentialsSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
    {
    // ...
    // Update presence when friend list, blocked player list, and session member list changed.
    if (FOnlineFriendsAccelBytePtr FriendsInterface = GetFriendsInterface())
    {
    FriendsInterface->AddOnFriendsChangeDelegate_Handle(0, FOnFriendsChangeDelegate::CreateUObject(this, &ThisClass::OnFriendListChange));
    FriendsInterface->AddOnBlockListChangeDelegate_Handle(0, FOnBlockListChangeDelegate::CreateUObject(this, &ThisClass::OnBlockedPlayerListChange));
    }
    if (FOnlineSessionV2AccelBytePtr SessionInterface = GetSessionInterface())
    {
    #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
    SessionInterface->AddOnSessionParticipantJoinedDelegate_Handle(FOnSessionParticipantJoinedDelegate::CreateUObject(this, &ThisClass::OnSessionParticipantJoined));
    #else
    SessionInterface->AddOnSessionParticipantsChangeDelegate_Handle(FOnSessionParticipantsChangeDelegate::CreateUObject(this, &ThisClass::OnSessionParticipantChange));
    #endif
    }
    // ...
    }
  6. 最後に、サブシステムが非初期化されたときにコールバック関数をアンバインドする必要があります。これは、以下のコードを追加することで実行できます。

    void UPresenceEssentialsSubsystem_Starter::Deinitialize()
    {
    // ...
    if (FOnlineFriendsAccelBytePtr FriendsInterface = GetFriendsInterface())
    {
    FriendsInterface->ClearOnFriendsChangeDelegates(0, this);
    FriendsInterface->ClearOnBlockListChangeDelegates(0, this);
    }
    if (FOnlineSessionV2AccelBytePtr SessionInterface = GetSessionInterface())
    {
    #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
    SessionInterface->ClearOnSessionParticipantJoinedDelegates(this);
    #else
    SessionInterface->ClearOnSessionParticipantsChangeDelegates(this);
    #endif
    }
    // ...
    }

プレイヤーのゲームアクティビティを処理してプレイヤーのプレゼンスステータスを設定する

このセクションでは、プレイヤーのゲームアクティビティに基づいてプレイヤーのプレゼンスステータスを設定する方法を学びます。まず、Byte Wars におけるプレイヤーアクティビティの意味を理解しましょう。ゲームアクティビティは、プレイヤーが現在いるレベルと、プレイヤーが現在行っているアクティビティによって定義されます。以下は、プレイヤーのプレゼンスステータスを設定するために使用するアクティビティのリストです。

アクティビティ説明
In Main Menuプレイヤーはメインメニューにいます。
In Matchプレイヤーはゲームセッションにいます。
In Partyプレイヤーはパーティーにいます。
Matchmakingプレイヤーは現在マッチメイキング中です。
Lobbyプレイヤーはマッチロビーメニューにいます。
Game Mode: <Game Mode Name>プレイヤーは特定のゲームモードでゲームプレイ中です。

それでは、これらのアクティビティでプレイヤーのプレゼンスステータスを設定するいくつかの関数を実装しましょう。

  1. PresenceEssentialsSubsystem_Starter クラスのヘッダーファイルを開き、ログインしているプレイヤーのプレゼンスステータスを保存および更新するためのヘルパー変数と関数を宣言します。

    protected:
    // ...
    void UpdatePrimaryPlayerPresenceStatus();
    protected:
    // ...
    FString LevelStatus;
    FString ActivityStatus;
    private:
    void OnLevelLoaded();
  2. 次に、PresenceEssentialsSubsystem_Starter クラスの CPP ファイルを開き、UpdatePrimaryPlayerPresenceStatus() 関数を定義します。この関数は、先ほど宣言したヘルパー変数に保存されているデータに基づいて、ログインしているプレイヤーのプレゼンスステータスを更新します。

    void UPresenceEssentialsSubsystem_Starter::UpdatePrimaryPlayerPresenceStatus()
    {
    FOnlineIdentityAccelBytePtr IdentityInterface = GetIdentityInterface();
    if (!IdentityInterface)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to update current logged-in player's presence. Identity interface is invalid."));
    return;
    }

    const FUniqueNetIdPtr UserId = GetPrimaryPlayerUserId();
    if (!UserId)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to update current logged-in player's presence. User Id is invalid."));
    return;
    }

    // Abort if not logged-in.
    if (IdentityInterface->GetLoginStatus(UserId.ToSharedRef().Get()) != ELoginStatus::Type::LoggedIn)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to update current logged-in player's presence. Primary player is not logged-in."));
    return;
    }

    /* Only consider player is in a party if the party member is more than one.
    * This is because by the game design, the game always creates a party for the player.*/
    FString PartyStatus;
    if (UAccelByteWarsOnlineSessionBase* OnlineSession = GetOnlineSession())
    {
    PartyStatus = (OnlineSession->GetPartyMembers().Num() > 1) ? TEXT_PRESENCE_ACTIVITY_PARTY.ToString() : FString();
    }

    // Collect and construct presence status in "level - activity, party status" format.
    FString PresenceStatus = LevelStatus;
    if (!ActivityStatus.IsEmpty())
    {
    PresenceStatus += FString(" - ") + ActivityStatus;
    }
    if (!PartyStatus.IsEmpty())
    {
    PresenceStatus += (ActivityStatus.IsEmpty() ? FString(" - ") : FString(", ")) + PartyStatus;
    }

    // Set presence status.
    SetPresenceStatus(UserId, PresenceStatus);
    }
  3. 次に、OnLevelLoaded() 関数を定義します。この関数は、プレイヤーが現在どのレベルにいるかをチェックし、それに応じてプレイヤーのプレゼンスステータスを更新します。

    void UPresenceEssentialsSubsystem_Starter::OnLevelLoaded()
    {
    const UWorld* World = GetWorld();
    if (!World)
    {
    UE_LOG_PRESENCEESSENTIALS(Warning, TEXT("Unable to update level status presence. World is not valid."));
    return;
    }

    AAccelByteWarsGameState* GameState = Cast<AAccelByteWarsGameState>(World->GetGameState());

    /* In online multiplayer, the game state takes some time to replicate after world creation.
    * Once the game state is replicated and initialized, attempt to update the level status. */
    AAccelByteWarsGameState::OnInitialized.RemoveAll(this);
    if (!GameState)
    {
    AAccelByteWarsGameState::OnInitialized.AddUObject(this, &ThisClass::OnLevelLoaded);
    return;
    }

    /* In online multiplayer, the game state may be initialized, but the properties may not be replicated yet.
    * Therefore, listen for the game state's game setup replication to attempt updating the level status. */
    GameState->OnGameSetupChanged.RemoveAll(this);
    GameState->OnGameSetupChanged.AddUObject(this, &ThisClass::OnLevelLoaded);

    if (const AAccelByteWarsMainMenuGameState* MainMenuGameState = Cast<AAccelByteWarsMainMenuGameState>(GameState))
    {
    // Set level status to "In Main Menu".
    if (MainMenuGameState->GetNetMode() == ENetMode::NM_Standalone)
    {
    LevelStatus = TEXT_PRESENCE_LEVEL_MAINMENU.ToString();
    ActivityStatus = FString("");
    }
    /* Already travelled to online session.
    * Set level status to "In Match" and activity status to "In Lobby".*/
    else
    {
    LevelStatus = TEXT_PRESENCE_LEVEL_GAMEPLAY.ToString();
    ActivityStatus = TEXT_PRESENCE_ACTIVITY_LOBBY.ToString();
    }
    }
    else if (const AAccelByteWarsInGameGameState* InGameGameState = Cast<AAccelByteWarsInGameGameState>(GameState))
    {
    // Set level status to "In Match" and activity status to the current game mode type.
    LevelStatus = TEXT_PRESENCE_LEVEL_GAMEPLAY.ToString();
    ActivityStatus = FText::Format(TEXT_PRESENCE_ACTIVITY_GAMEPLAY, InGameGameState->GameSetup.DisplayName).ToString();
    }
    else
    {
    // Fallback, set status to unknown.
    LevelStatus = TEXT_PRESENCE_ACTIVITY_UNKNOWN.ToString();
    ActivityStatus = FString("");
    }

    UpdatePrimaryPlayerPresenceStatus();
    }
  4. 次に、以下のコードブロックを Initialize() 関数に追加します。このコードは、マッチメイキング、レベル変更、パーティーステータス変更などのさまざまなイベントに基づいて、ヘルパー変数を変更することでプレイヤーのプレゼンスステータスを変更します。プレゼンスステータスを更新するために、これらのイベントは先ほど定義した UpdatePrimaryPlayerPresenceStatus() 関数を呼び出します。

    void UPresenceEssentialsSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
    {
    // ...
    // On world loaded, update level status.
    FWorldDelegates::OnPostWorldInitialization.AddWeakLambda(this, [this](UWorld* World, const UWorld::InitializationValues IVS)
    {
    if (!World)
    {
    return;
    }

    World->OnWorldBeginPlay.RemoveAll(this);
    World->OnWorldBeginPlay.AddUObject(this, &ThisClass::OnLevelLoaded);
    });

    if (UAccelByteWarsOnlineSessionBase* OnlineSession = GetOnlineSession())
    {
    // On matchmaking started, set matchmaking status.
    if (FOnMatchmakingResponse* OnStartMatchmakingComplete = OnlineSession->GetOnStartMatchmakingCompleteDelegates())
    {
    OnStartMatchmakingComplete->AddWeakLambda(this, [this](FName SessionName, bool bSucceeded)
    {
    ActivityStatus = bSucceeded ? TEXT_PRESENCE_ACTIVITY_MATCHMAKING.ToString() : FString();
    UpdatePrimaryPlayerPresenceStatus();
    });
    }

    // On matchmaking complete, remove matchmaking status.
    if (FOnMatchmakingResponse* OnMatchmakingComplete = OnlineSession->GetOnMatchmakingCompleteDelegates())
    {
    OnMatchmakingComplete->AddWeakLambda(this, [this](FName SessionName, bool bSucceeded)
    {
    ActivityStatus = FString();
    UpdatePrimaryPlayerPresenceStatus();
    });
    }

    // On matchmaking canceled, remove matchmaking status.
    if (FOnMatchmakingResponse* OnCancelMatchmakingComplete = OnlineSession->GetOnCancelMatchmakingCompleteDelegates())
    {
    OnCancelMatchmakingComplete->AddWeakLambda(this, [this](FName SessionName, bool bSucceeded)
    {
    ActivityStatus = FString();
    UpdatePrimaryPlayerPresenceStatus();
    });
    }

    // On party session updated, update party presence status.
    if (FOnJoinSessionComplete* OnJoinPartyComplete = OnlineSession->GetOnJoinPartyCompleteDelegates())
    {
    OnJoinPartyComplete->AddWeakLambda(this, [this](FName SessionName, EOnJoinSessionCompleteResult::Type Result)
    {
    UpdatePrimaryPlayerPresenceStatus();
    });
    }
    if (FOnDestroySessionComplete* OnLeavePartyComplete = OnlineSession->GetOnLeavePartyCompleteDelegates())
    {
    OnLeavePartyComplete->AddWeakLambda(this, [this](FName SessionName, bool bSucceeded)
    {
    UpdatePrimaryPlayerPresenceStatus();
    });
    }

    // ...
    if (FOnSessionParticipantJoined* OnPartyMemberJoined = OnlineSession->GetOnPartyMemberJoinedDelegates())
    {
    OnPartyMemberJoined->AddWeakLambda(this, [this](FName SessionName, const FUniqueNetId& Member)
    {
    UpdatePrimaryPlayerPresenceStatus();
    });
    }
    if (FOnSessionParticipantLeft* OnPartyMemberLeft = OnlineSession->GetOnPartyMemberLeftDelegates())
    {
    OnPartyMemberLeft->AddWeakLambda(this, [this](FName SessionName, const FUniqueNetId& Member, EOnSessionParticipantLeftReason Reason)
    {
    UpdatePrimaryPlayerPresenceStatus();
    });
    }
    // ...

    if (FOnSessionUpdateReceived* OnPartySessionUpdateReceived = OnlineSession->GetOnPartySessionUpdateReceivedDelegates())
    {
    OnPartySessionUpdateReceived->AddWeakLambda(this, [this](FName SessionName)
    {
    UpdatePrimaryPlayerPresenceStatus();
    });
    }
    if (FOnDestroySessionComplete* OnLeaveSessionComplete = OnlineSession->GetOnLeaveSessionCompleteDelegates())
    {
    OnLeaveSessionComplete->AddWeakLambda(this, [this](FName SessionName, bool bSucceeded)
    {
    UpdatePrimaryPlayerPresenceStatus();
    });
    }
    }

    // Update presence status once the player logged in and connected to lobby.
    if (FOnlineIdentityAccelBytePtr IdentityInterface = GetIdentityInterface())
    {
    IdentityInterface->AddOnConnectLobbyCompleteDelegate_Handle(0,
    FOnConnectLobbyCompleteDelegate::CreateWeakLambda(this, [this](int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error)
    {
    UpdatePrimaryPlayerPresenceStatus();
    }
    ));
    }
    // ...
    }
  5. 最後に、サブシステムが非初期化されたときに、上記で言及したイベントをアンバインドする必要があります。これは、以下のコードを追加することで実行できます。

    void UPresenceEssentialsSubsystem_Starter::Deinitialize()
    {
    // ...
    // Clean-up cache.
    LevelStatus = FString();
    ActivityStatus = FString();

    // Unbind events.
    AAccelByteWarsGameState::OnInitialized.RemoveAll(this);

    FWorldDelegates::OnPostWorldInitialization.RemoveAll(this);
    if (GetWorld())
    {
    GetWorld()->OnWorldBeginPlay.RemoveAll(this);
    }

    if (UAccelByteWarsOnlineSessionBase* OnlineSession = GetOnlineSession())
    {
    if (FOnMatchmakingResponse* OnStartMatchmakingComplete = OnlineSession->GetOnStartMatchmakingCompleteDelegates())
    {
    OnStartMatchmakingComplete->RemoveAll(this);
    }

    if (FOnMatchmakingResponse* OnMatchmakingComplete = OnlineSession->GetOnMatchmakingCompleteDelegates())
    {
    OnMatchmakingComplete->RemoveAll(this);
    }

    if (FOnMatchmakingResponse* OnCancelMatchmakingComplete = OnlineSession->GetOnCancelMatchmakingCompleteDelegates())
    {
    OnCancelMatchmakingComplete->RemoveAll(this);
    }

    if (FOnJoinSessionComplete* OnJoinPartyComplete = OnlineSession->GetOnJoinPartyCompleteDelegates())
    {
    OnJoinPartyComplete->RemoveAll(this);
    }

    if (FOnDestroySessionComplete* OnLeavePartyComplete = OnlineSession->GetOnLeavePartyCompleteDelegates())
    {
    OnLeavePartyComplete->RemoveAll(this);
    }

    // ...
    if (FOnSessionParticipantJoined* OnPartyMemberJoined = OnlineSession->GetOnPartyMemberJoinedDelegates())
    {
    OnPartyMemberJoined->RemoveAll(this);
    }
    if (FOnSessionParticipantLeft* OnPartyMemberLeft = OnlineSession->GetOnPartyMemberLeftDelegates())
    {
    OnPartyMemberLeft->RemoveAll(this);
    }
    // ...

    if (FOnSessionUpdateReceived* OnPartySessionUpdateReceived = OnlineSession->GetOnPartySessionUpdateReceivedDelegates())
    {
    OnPartySessionUpdateReceived->RemoveAll(this);
    }

    if (FOnDestroySessionComplete* OnLeaveSessionComplete = OnlineSession->GetOnLeaveSessionCompleteDelegates())
    {
    OnLeaveSessionComplete->RemoveAll(this);
    }
    }

    if (FOnlineIdentityAccelBytePtr IdentityInterface = GetIdentityInterface())
    {
    IdentityInterface->ClearOnConnectLobbyCompleteDelegates(0, this);
    }
    // ...
    }

リソース