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

パーティセッションを実装する - パーティ入門 - (Unreal Engine モジュール)

Last updated on February 4, 2026

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

オンラインセッションの展開

パーティの作成は、本質的には「パーティ」タイプの新しいセッションを作成することであるため、パーティの管理はオンラインセッションの管理と似ています。このセクションでは、AccelByte Gaming Services (AGS) Online Subsystem (OSS) を使用して基本的なパーティ機能を設定する方法を学習します。

Byte Wars プロジェクトには、PartyOnlineSession という名前のオンラインセッションクラスがすでに作成されています。このクラスは USessionEssentialsOnlineSession クラスの子クラスであり、以前の Byte Wars モジュール (Session Essentials) でセッションを作成するために使用したクラスです。PartyOnlineSession クラスにはパーティ関連の機能が含まれていますが、このチュートリアルでは、パーティ関連の機能をゼロから実装できるように、そのスターターバージョンを使用します。

スターターパックの内容

このチュートリアルに従うために、PartyOnlineSession_Starter という名前のスターターオンラインセッションクラスが用意されています。このクラスは以下のファイルで構成されています:

  • Header ファイル: /Source/AccelByteWars/TutorialModules/PartyEssentials/PartyOnlineSession_Starter.h にあります。
  • CPP ファイル: /Source/AccelByteWars/TutorialModules/PartyEssentials/PartyOnlineSession_Starter.cpp にあります。

PartyOnlineSession_Starter クラスには、いくつかの関数が用意されています。

  • AGS OSS インターフェースから取得するための関数がいくつかあります。最初は UAccelByteWarsOnlineSessionBase::GetABSessionInt() 関数で、AGS セッションインターフェースを取得します。次に、UAccelByteWarsOnlineSessionBase::GetUserInt() 関数で、AGS ユーザーインターフェース (UI) を取得します。これらは後でパーティ機能を実装するために使用します。

    FOnlineSessionV2AccelBytePtr UAccelByteWarsOnlineSessionBase::GetABSessionInt()
    {
    return StaticCastSharedPtr<FOnlineSessionV2AccelByte>(GetSessionInt());
    }
    IOnlineUserPtr UAccelByteWarsOnlineSessionBase::GetUserInt() const
    {
    const UWorld* World = GetWorld();
    if (!ensure(World))
    {
    return nullptr;
    }

    return Online::GetUserInterface(World);
    }
  • プレイヤーがパーティセッションから離脱したときにイベントをトリガーするヘルパー関数。これは後でいくつかのパーティ機能に必要になります。たとえば、新しいパーティを作成または参加するには、プレイヤーは最初に既存のパーティセッションから離脱する必要があります。これを行うには、作成または参加パーティイベントをこの関数にバインドできます。

    void UPartyOnlineSession_Starter::OnLeavePartyToTriggerEvent(FName SessionName, bool bSucceeded, const TDelegate<void(bool bWasSuccessful)> OnComplete)
    {
    // Abort if not a party session.
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    OnComplete.ExecuteIfBound(false);
    return;
    }

    OnComplete.ExecuteIfBound(bSucceeded);
    }
  • 表示名やアバターなど、パーティメンバー情報をクエリするヘルパー関数。

    void UStartupSubsystem::QueryUserInfo(
    const int32 LocalUserNum,
    const TArray<FUniqueNetIdRef>& UserIds,
    const FOnQueryUsersInfoCompleteDelegate& OnComplete)
    {
    // Abort if the user interface is invalid.
    if (!UserInterface)
    {
    UE_LOG_STARTUP(Warning, TEXT("User interface is invalid"))
    GetWorld()->GetTimerManager().SetTimerForNextTick(FTimerDelegate::CreateWeakLambda(this, [this, OnComplete]()
    {
    OnComplete.ExecuteIfBound(
    FOnlineError::CreateError(
    TEXT(""),
    EOnlineErrorResult::RequestFailure,
    TEXT(""),
    FText::FromString(TEXT(""))),
    {});
    }));
    return;
    }

    // Call query right away since the it will check for cache first internally
    if (OnQueryUserInfoCompleteDelegateHandle.IsValid())
    {
    UserInterface->OnQueryUserInfoCompleteDelegates->Remove(OnQueryUserInfoCompleteDelegateHandle);
    OnQueryUserInfoCompleteDelegateHandle.Reset();
    }
    OnQueryUserInfoCompleteDelegateHandle = UserInterface->OnQueryUserInfoCompleteDelegates->AddWeakLambda(
    this, [OnComplete, this](
    int32 LocalUserNum,
    bool bSucceeded,
    const TArray<FUniqueNetIdRef>& UserIds,
    const FString& ErrorMessage)
    {
    OnQueryUserInfoComplete(LocalUserNum, bSucceeded, UserIds, ErrorMessage, OnComplete);
    });

    if (!UserInterface->QueryUserInfo(LocalUserNum, UserIds))
    {
    OnQueryUserInfoComplete(LocalUserNum, false, UserIds, TEXT(""), OnComplete);
    }
    }
    void UStartupSubsystem::OnQueryUserInfoComplete(
    int32 LocalUserNum,
    bool bSucceeded,
    const TArray<FUniqueNetIdRef>& UserIds,
    const FString& ErrorMessage,
    const FOnQueryUsersInfoCompleteDelegate& OnComplete)
    {
    // reset delegate handle
    UserInterface->OnQueryUserInfoCompleteDelegates->Remove(OnQueryUserInfoCompleteDelegateHandle);
    OnQueryUserInfoCompleteDelegateHandle.Reset();

    if (bSucceeded)
    {
    // Retrieve the result from cache.
    TArray<TSharedPtr<FUserOnlineAccountAccelByte>> OnlineUsers;
    for (const FUniqueNetIdRef& UserId : UserIds)
    {
    TSharedPtr<FOnlineUser> OnlineUser = UserInterface->GetUserInfo(0, UserId.Get());
    if (!OnlineUser.IsValid() || !OnlineUser->GetUserId()->IsValid())
    {
    continue;
    }
    OnlineUsers.Add(StaticCastSharedPtr<FUserOnlineAccountAccelByte>(OnlineUser));
    }

    OnComplete.ExecuteIfBound(FOnlineError::Success(), OnlineUsers);
    }
    else
    {
    OnComplete.ExecuteIfBound(
    FOnlineError::CreateError(
    TEXT(""),
    EOnlineErrorResult::RequestFailure,
    TEXT(""),
    FText::FromString(ErrorMessage)),
    {});
    }
    }
  • パーティメンバーの参加、パーティメンバーの離脱、パーティ招待などのパーティイベントの通知をプッシュするために使用できるプロンプトシステムを取得するヘルパー関数。

    UPromptSubsystem* UPartyOnlineSession_Starter::GetPromptSubystem()
    {
    UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
    if (!GameInstance)
    {
    return nullptr;
    }

    return GameInstance->GetSubsystem<UPromptSubsystem>();
    }
  • 後でパーティイベントデリゲートをバインドおよびアンバインドするために使用できる、事前定義された2つのデリゲートがあります。これらの関数は、オンラインセッションが初期化および非初期化されたときにそれぞれ呼び出されます。

    void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
    {
    Super::RegisterOnlineDelegates();

    InitializePartyGeneratedWidgets();
    // ...
    }
    void UPartyOnlineSession_Starter::ClearOnlineDelegates()
    {
    Super::ClearOnlineDelegates();

    DeinitializePartyGeneratedWidgets();
    // ...
    }

パーティユーティリティの実装

このセクションでは、パーティメンバーの表示など、後で使用できるいくつかのパーティユーティリティを実装します。パーティメンバーリストの取得と現在のパーティリーダーの取得を実装します。

  1. まず、いくつかの関数を宣言します。PartyOnlineSession_Starter クラスの Header ファイルを開き、以下のコードを追加します:

    public:
    // ...
    virtual TArray<FUniqueNetIdRef> GetPartyMembers() override;
    virtual FUniqueNetIdPtr GetPartyLeader() override;
    virtual bool IsInParty(const FUniqueNetIdPtr UserId);
    virtual bool IsPartyLeader(const FUniqueNetIdPtr UserId) override;
  2. 次に、PartyOnlineSession_Starter クラスの CPP ファイルを開き、以下のように関数を定義します:

    TArray<FUniqueNetIdRef> UPartyOnlineSession_Starter::GetPartyMembers()
    {
    if (GetABSessionInt())
    {
    const FNamedOnlineSession* PartySession = GetABSessionInt()->GetNamedSession(GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession));
    if (PartySession)
    {
    return PartySession->RegisteredPlayers;
    }
    }

    return TArray<FUniqueNetIdRef>();
    }
    FUniqueNetIdPtr UPartyOnlineSession_Starter::GetPartyLeader()
    {
    if (GetABSessionInt())
    {
    const FNamedOnlineSession* PartySession = GetABSessionInt()->GetNamedSession(GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession));
    if (PartySession)
    {
    const TSharedPtr<FOnlineSessionInfoAccelByteV2> SessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(PartySession->SessionInfo);
    if (!SessionInfo)
    {
    return nullptr;
    }

    return GetABSessionInt()->GetSessionLeaderId(PartySession);
    }
    }

    return nullptr;
    }
    bool UPartyOnlineSession_Starter::IsInParty(const FUniqueNetIdPtr UserId)
    {
    if (!UserId)
    {
    return false;
    }

    const TPartyMemberArray Members = GetPartyMembers();
    for (const auto& Member : Members)
    {
    if (!Member.Get().IsValid())
    {
    continue;
    }

    if (Member.Get() == UserId.ToSharedRef().Get())
    {
    return true;
    }
    }

    return false;
    }
    bool UPartyOnlineSession_Starter::IsPartyLeader(const FUniqueNetIdPtr UserId)
    {
    return GetPartyLeader() && UserId && UserId.ToSharedRef().Get() == GetPartyLeader().ToSharedRef().Get();
    }

パーティ作成の実装

このセクションでは、新しいパーティセッションを作成する機能を実装します。

  1. まず、PartyOnlineSession_Starter クラスの Header ファイルを開きます。次に、パーティセッションテンプレートを定義する新しい変数を作成します。その値が、前のチュートリアル (パーティセッションの設定) で作成したパーティセッションテンプレートと同じであることを確認してください。

    private:
    // ...
    const FString PartySessionTemplate = FString("unreal-party");
  2. 同じファイルで、パーティセッションを作成する関数を宣言します。

    public:
    // ...
    virtual void CreateParty(const int32 LocalUserNum) override;
  3. 次に、パーティ作成プロセスが完了したときに呼び出されるコールバック関数を宣言します。

    protected:
    // ...
    virtual void OnCreatePartyComplete(FName SessionName, bool bSucceeded) override;
  4. 次に、パーティ作成プロセスが完了したときに呼び出されるデリゲートを宣言します。このデリゲートを使用して、パーティが作成されたときにイベントをトリガーできます。たとえば、新しく作成されたパーティのメンバーを表示するために UI イベントをバインドできます。

    public:
    // ...
    virtual FOnCreateSessionComplete* GetOnCreatePartyCompleteDelegates()
    {
    return &OnCreatePartyCompleteDelegates;
    }
    private:
    // ...
    FOnCreateSessionComplete OnCreatePartyCompleteDelegates;
  5. 次に、CreateParty() から始めてこれらの関数を定義します。PartyOnlineSession_Starter クラスの CPP ファイルを開き、以下のコードを追加します。この関数は、新しいパーティセッションを作成する前に、まず既存のパーティセッションから離脱します。

    void UPartyOnlineSession_Starter::CreateParty(const int32 LocalUserNum)
    {
    const FName SessionName = GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession);

    // Abort if the session interface is invalid.
    if (!GetABSessionInt())
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot create a party. The session interface is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnCreatePartyComplete(SessionName, false);
    }));
    return;
    }

    // Always create a new party. Thus, leave any left-over party session first.
    if (GetABSessionInt()->IsInPartySession())
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Party found. Leave old party before creating a new one."));

    if (OnLeaveSessionForTriggerDelegateHandle.IsValid())
    {
    GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForTriggerDelegateHandle);
    OnLeaveSessionForTriggerDelegateHandle.Reset();
    }

    OnLeaveSessionForTriggerDelegateHandle = GetOnLeaveSessionCompleteDelegates()->AddUObject(
    this,
    &ThisClass::OnLeavePartyToTriggerEvent,
    TDelegate<void(bool)>::CreateWeakLambda(this, [this, LocalUserNum, SessionName](bool bWasSuccessful)
    {
    GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForTriggerDelegateHandle);

    if (bWasSuccessful)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to leave old party destroyed. Try creating a new party."));
    CreateParty(LocalUserNum);
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot create a new party. Failed to leave old party."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnCreatePartyComplete(SessionName, false);
    }));
    }
    }
    ));

    LeaveSession(SessionName);
    return;
    }

    // Create a new party session. Override party session template name if applicable.
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Create a new party."));
    CreateSession(
    LocalUserNum,
    SessionName,
    FOnlineSessionSettings(),
    EAccelByteV2SessionType::PartySession,
    UTutorialModuleOnlineUtility::GetPartySessionTemplateOverride().IsEmpty() ?
    PartySessionTemplate : UTutorialModuleOnlineUtility::GetPartySessionTemplateOverride());
    }
  6. 次に、以下のコードを追加して OnCreatePartyComplete() 関数を定義します。この関数は、Header ファイルで先ほど定義した完了デリゲートをブロードキャストします。

    void UPartyOnlineSession_Starter::OnCreatePartyComplete(FName SessionName, bool bSucceeded)
    {
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    if (bSucceeded)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to create a party"));
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to create a party"));
    }

    // Cache the party leader.
    LastPartyLeader = GetPartyLeader();

    // Reset the party member status cache.
    PartyMemberStatus.Empty();

    OnCreatePartyCompleteDelegates.Broadcast(SessionName, bSucceeded);
    }
  7. 次に、パーティ作成プロセスが完了したときに呼び出されるように OnCreatePartyComplete() をバインドする必要があります。これは、オンラインセッションが初期化されたときに最初に呼び出される事前定義された RegisterOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnCreateSessionCompleteDelegates.AddUObject(this, &ThisClass::OnCreatePartyComplete);
    // ...
    }
    // ...
    }
  8. 最後に、オンラインセッションが非初期化されたときに、パーティ作成プロセス完了イベントのリスニングを停止するためにアンバインドする必要があります。これは、オンラインセッションが非初期化されたときに最初に呼び出される事前定義された ClearOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::ClearOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnCreateSessionCompleteDelegates.RemoveAll(this);
    // ...
    }
    // ...
    }

パーティ離脱の実装

このセクションでは、パーティセッションから離脱する機能を実装します。

  1. PartyOnlineSession_Starter クラスの Header ファイルを開き、パーティから離脱する関数を宣言します。

    public:
    // ...
    virtual void LeaveParty(const int32 LocalUserNum) override;
  2. 同じファイルで、パーティ離脱プロセスが完了したときに呼び出されるコールバック関数を宣言します。

    protected:
    // ...
    virtual void OnLeavePartyComplete(FName SessionName, bool bSucceeded) override;
  3. 次に、パーティ離脱プロセスが完了したときに呼び出されるデリゲートを宣言します。このデリゲートのラッパーを使用して、パーティ離脱プロセスが完了したときにイベントをトリガーできます。

    public:
    // ...
    virtual FOnDestroySessionComplete* GetOnLeavePartyCompleteDelegates()
    {
    return &OnLeavePartyCompleteDelegates;
    }
    private:
    // ...
    FOnDestroySessionComplete OnLeavePartyCompleteDelegates;
  4. 次に、LeaveParty() から始めてこれらの関数を定義します。PartyOnlineSession_Starter クラスの CPP ファイルを開き、以下のコードを追加します。この関数により、プレイヤーはパーティセッションから離脱できます。

    void UPartyOnlineSession_Starter::LeaveParty(const int32 LocalUserNum)
    {
    const FName SessionName = GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession);

    if (!GetABSessionInt())
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot leave a party. The session interface is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnLeavePartyComplete(SessionName, false);
    }));
    return;
    }

    if (!GetABSessionInt()->IsInPartySession())
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot leave a party. Not in any party."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnLeavePartyComplete(SessionName, false);
    }));
    return;
    }

    // Leave party.
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Leave party."));
    LeaveSession(SessionName);
    }
  5. 次に、以下のコードを追加して OnLeavePartyComplete() 関数を定義します。この関数は、Header ファイルで定義した完了デリゲートをブロードキャストします。

    void UPartyOnlineSession_Starter::OnLeavePartyComplete(FName SessionName, bool bSucceeded)
    {
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    if (bSucceeded)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to leave a party"));
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to leave a party"));
    }

    OnLeavePartyCompleteDelegates.Broadcast(SessionName, bSucceeded);
    }
  6. 次に、パーティ離脱プロセスが完了したときに呼び出されるように OnLeavePartyComplete() をバインドする必要があります。これは、オンラインセッションが初期化されたときに最初に呼び出される事前定義された RegisterOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnDestroySessionCompleteDelegates.AddUObject(this, &ThisClass::OnLeavePartyComplete);
    // ...
    }
    // ...
    }
  7. 最後に、オンラインセッションが非初期化されたときに、パーティ離脱プロセス完了イベントのリスニングを停止するためにアンバインドする必要があります。これは、オンラインセッションが非初期化されたときに最初に呼び出される事前定義された ClearOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::ClearOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnDestroySessionCompleteDelegates.RemoveAll(this);
    // ...
    }
    // ...
    }

パーティ招待送信の実装

このセクションでは、パーティ招待を送信する機能を実装します。

  1. PartyOnlineSession_Starter クラスの Header ファイルを開き、パーティ招待を送信する関数を宣言します。

    public:
    // ...
    virtual void SendPartyInvite(const int32 LocalUserNum, const FUniqueNetIdPtr& Invitee) override;
  2. 同じファイルで、パーティ招待送信プロセスが完了したときに呼び出されるコールバック関数を宣言します。

    protected:
    // ...
    virtual void OnSendPartyInviteComplete(const FUniqueNetId& Sender, FName SessionName, bool bWasSuccessful, const FUniqueNetId& Invitee) override;
  3. 次に、パーティ招待送信プロセスが完了したときに呼び出されるデリゲートを宣言します。このデリゲートのラッパーを使用して、パーティ招待送信が完了したときにイベントをトリガーできます。

    public:
    // ...
    virtual FOnSendSessionInviteComplete* GetOnSendPartyInviteCompleteDelegates() override
    {
    return &OnSendPartyInviteCompleteDelegates;;
    }
    private:
    // ...
    FOnSendSessionInviteComplete OnSendPartyInviteCompleteDelegates;
  4. 次に、SendPartyInvite() から始めてこれらの関数を定義します。PartyOnlineSession_Starter クラスの CPP ファイルを開き、以下のコードを追加します。この関数は、ターゲットの招待者にパーティ招待を送信します。招待者がパーティに参加していない場合、この関数はパーティ招待を送信する前に新しいパーティを作成します。

    void UPartyOnlineSession_Starter::SendPartyInvite(const int32 LocalUserNum, const FUniqueNetIdPtr& Invitee)
    {
    if (!GetABSessionInt())
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot send a party invitation. The session interface is not valid."));
    return;
    }

    const APlayerController* SenderPC = GetPlayerControllerByLocalUserNum(LocalUserNum);
    if (!SenderPC)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot send a party invitation. Sender's PlayerController is not valid."));
    return;
    }

    const FUniqueNetIdPtr SenderId = GetLocalPlayerUniqueNetId(SenderPC);
    if (!SenderId)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot send a party invitation. Sender's NetId is not valid."));
    return;
    }

    const FName SessionName = GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession);
    if (!Invitee)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot send a party invitation. Invitee's NetId is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SenderId, SessionName, Invitee]()
    {
    OnSendPartyInviteComplete(SenderId.ToSharedRef().Get(), SessionName, false, Invitee.ToSharedRef().Get());
    }));
    return;
    }

    // Create a new party first before inviting.
    if (!GetABSessionInt()->IsInPartySession())
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Not in a party session. Creating a new party before sending a party invitation."));

    if (OnCreatePartyToInviteMemberDelegateHandle.IsValid())
    {
    GetOnCreateSessionCompleteDelegates()->Remove(OnCreatePartyToInviteMemberDelegateHandle);
    OnCreatePartyToInviteMemberDelegateHandle.Reset();
    }

    OnCreatePartyToInviteMemberDelegateHandle = GetOnCreateSessionCompleteDelegates()->AddUObject(this, &ThisClass::OnCreatePartyToInviteMember, LocalUserNum, SenderId, Invitee);

    CreateParty(LocalUserNum);
    return;
    }

    // Send party invitation.
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Send party invitation."));
    GetABSessionInt()->SendSessionInviteToFriend(
    SenderId.ToSharedRef().Get(),
    SessionName,
    Invitee.ToSharedRef().Get());
    }
  5. 次に、以下のコードを追加して OnSendPartyInviteComplete() 関数を定義します。この関数は、Header ファイルで定義した完了デリゲートをブロードキャストし、プッシュ通知を表示します。

    void UPartyOnlineSession_Starter::OnSendPartyInviteComplete(const FUniqueNetId& Sender, FName SessionName, bool bWasSuccessful, const FUniqueNetId& Invitee)
    {
    // Abort if not a party session.
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    const FUniqueNetIdAccelByteUserRef InviteeABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(Invitee.AsShared());
    if (bWasSuccessful)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to send party invitation to %s"),
    InviteeABId->IsValid() ? *InviteeABId->GetAccelByteId() : TEXT("Unknown"));
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to send party invitation to %s"),
    InviteeABId->IsValid() ? *InviteeABId->GetAccelByteId() : TEXT("Unknown"));
    }

    // Display push notification.
    if (GetPromptSubystem())
    {
    GetPromptSubystem()->PushNotification(bWasSuccessful ? SUCCESS_SEND_PARTY_INVITE : FAILED_SEND_PARTY_INVITE);
    }

    UpdatePartyGeneratedWidgets();

    OnSendPartyInviteCompleteDelegates.Broadcast(Sender, SessionName, bWasSuccessful, Invitee);
    }
  6. 次に、PartyOnlineSession_Starter クラスの Header ファイルを開き、パーティ招待を送信する前にパーティが作成されていることを確認するために、以下の関数とデリゲートを宣言します。

    protected:
    void OnCreatePartyToInviteMember(FName SessionName, bool bWasSuccessful, const int32 LocalUserNum, const FUniqueNetIdPtr SenderId, const FUniqueNetIdPtr InviteeId);
    private:
    FDelegateHandle OnCreatePartyToInviteMemberDelegateHandle;
  7. 次に、パーティ招待を送信するときのパーティ作成を処理するために、上記の関数を定義します。

    void UPartyOnlineSession_Starter::OnCreatePartyToInviteMember(FName SessionName, bool bWasSuccessful, const int32 LocalUserNum, const FUniqueNetIdPtr SenderId, const FUniqueNetIdPtr InviteeId)
    {
    // Abort if not a party session.
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    GetOnCreateSessionCompleteDelegates()->Remove(OnCreatePartyToInviteMemberDelegateHandle);

    if (!bWasSuccessful)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot send a party invitation. Failed to create a new party."));
    OnSendPartyInviteComplete(SenderId.ToSharedRef().Get(), SessionName, false, InviteeId.ToSharedRef().Get());
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Party created. Try sending a party invitation."));
    SendPartyInvite(LocalUserNum, InviteeId);
    }
    }
  8. 次に、パーティ招待送信プロセスが完了したときに呼び出されるように OnSendPartyInviteComplete() をバインドする必要があります。これは、オンラインセッションが初期化されたときに最初に呼び出される事前定義された RegisterOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnSendSessionInviteCompleteDelegates.AddUObject(this, &ThisClass::OnSendPartyInviteComplete);
    // ...
    }
    // ...
    }
  9. 最後に、オンラインセッションが非初期化されたときに、パーティ招待送信プロセス完了イベントのリスニングを停止するためにアンバインドする必要があります。これは、オンラインセッションが非初期化されたときに最初に呼び出される事前定義された ClearOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::ClearOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnSendSessionInviteCompleteDelegates.RemoveAll(this);
    // ...
    }
    // ...
    }

招待承認とパーティ参加の実装

このセクションでは、パーティ招待を承認してパーティセッションに参加する機能を実装します。

  1. PartyOnlineSession_Starter クラスの Header ファイルを開き、パーティに参加する関数を宣言します。

    public:
    // ...
    virtual void JoinParty(const int32 LocalUserNum, const FOnlineSessionSearchResult& PartySessionResult) override;
  2. 同じファイルで、パーティ参加プロセスが完了したときに呼び出されるコールバック関数を宣言します。

    protected:
    // ...
    virtual void OnJoinPartyComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result) override;
  3. 次に、パーティ参加プロセスが完了したときに呼び出されるデリゲートを宣言します。このデリゲートのラッパーを使用して、パーティ参加プロセスが完了したときにイベントをトリガーできます。

    public:
    // ...
    virtual FOnJoinSessionComplete* GetOnJoinPartyCompleteDelegates()
    {
    return &OnJoinPartyCompleteDelegates;
    }
    private:
    // ...
    FOnJoinSessionComplete OnJoinPartyCompleteDelegates;
  4. 次に、JoinParty() から始めてこれらの関数を定義します。PartyOnlineSession_Starter クラスの CPP ファイルを開き、以下のコードを追加します。この関数は、新しいパーティセッションに参加する前に、まず既存のパーティセッションから離脱します。

    void UPartyOnlineSession_Starter::JoinParty(const int32 LocalUserNum, const FOnlineSessionSearchResult& PartySessionResult)
    {
    const FName SessionName = GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession);

    if (!GetABSessionInt())
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot join a party. The session interface is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnJoinPartyComplete(SessionName, EOnJoinSessionCompleteResult::Type::UnknownError);
    }));
    return;
    }

    // Always leave any party before joining a new party.
    if (GetABSessionInt()->IsInPartySession())
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Party found. Leave old party before joining a new one."));

    if (OnLeaveSessionForTriggerDelegateHandle.IsValid())
    {
    GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForTriggerDelegateHandle);
    OnLeaveSessionForTriggerDelegateHandle.Reset();
    }

    OnLeaveSessionForTriggerDelegateHandle = GetOnLeaveSessionCompleteDelegates()->AddUObject(
    this,
    &ThisClass::OnLeavePartyToTriggerEvent,
    TDelegate<void(bool)>::CreateWeakLambda(this, [this, LocalUserNum, PartySessionResult, SessionName](bool bWasSuccessful)
    {
    GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForTriggerDelegateHandle);

    if (bWasSuccessful)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to leave old party. Try to joining a new party."));

    JoinParty(LocalUserNum, PartySessionResult);
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot joining a new party. Failed to leave old party."));

    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnJoinPartyComplete(SessionName, EOnJoinSessionCompleteResult::Type::UnknownError);
    }));
    }
    }
    ));

    LeaveSession(SessionName);
    return;
    }

    // Join a new party.
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Join a new party."));
    JoinSession(LocalUserNum, SessionName, PartySessionResult);
    }
  5. 次に、以下のコードを追加して OnJoinPartyComplete() 関数を定義します。この関数は、Header ファイルで定義した完了デリゲートをブロードキャストします。

    void UPartyOnlineSession_Starter::OnJoinPartyComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
    {
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    if (Result == EOnJoinSessionCompleteResult::Type::Success)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to join a party"));
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to join a party"));
    }

    // Cache the party leader.
    LastPartyLeader = GetPartyLeader();

    // Reset the party member status cache.
    PartyMemberStatus.Empty();

    OnJoinPartyCompleteDelegates.Broadcast(SessionName, Result);
    }
  6. 次に、パーティ参加プロセスが完了したときに呼び出されるように OnJoinPartyComplete() をバインドする必要があります。これは、オンラインセッションが初期化されたときに最初に呼び出される事前定義された RegisterOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnJoinSessionCompleteDelegates.AddUObject(this, &ThisClass::OnJoinPartyComplete);
    // ...
    }
    // ...
    }
  7. 最後に、オンラインセッションが非初期化されたときに、パーティ参加プロセス完了イベントのリスニングを停止するためにアンバインドする必要があります。これは、オンラインセッションが非初期化されたときに最初に呼び出される事前定義された ClearOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::ClearOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnJoinSessionCompleteDelegates.RemoveAll(this);
    // ...
    }
    // ...
    }

パーティ招待拒否の実装

このセクションでは、パーティ招待を拒否する機能を実装します。

  1. PartyOnlineSession_Starter クラスの Header ファイルを開き、以下の関数を宣言します。

    public:
    // ...
    virtual void RejectPartyInvite(const int32 LocalUserNum, const FOnlineSessionInviteAccelByte& PartyInvite) override;
  2. 同じファイルで、パーティ招待拒否プロセスが完了したときに呼び出されるコールバック関数を宣言します。

    protected:
    // ...
    virtual void OnRejectPartyInviteComplete(bool bWasSuccessful) override;
  3. 次に、パーティ招待拒否プロセスが完了したときに呼び出されるデリゲートを宣言します。このデリゲートのラッパーを使用して、パーティ招待拒否プロセスが完了したときにイベントをトリガーできます。

    public:
    // ...
    virtual FOnRejectSessionInviteComplete* GetOnRejectPartyInviteCompleteDelegate() override
    {
    return &OnRejectPartyInviteCompleteDelegate;
    }
    private:
    // ...
    FOnRejectSessionInviteComplete OnRejectPartyInviteCompleteDelegate;
  4. 次に、RejectPartyInvite() から始めてこれらの関数を定義します。PartyOnlineSession_Starter クラスの CPP ファイルを開き、以下のコードを追加します。この関数は、プレイヤーが受け取ったパーティ招待を拒否します。コールバックは OnRejectPartyInviteComplete() 関数によって処理されます。

    void UPartyOnlineSession_Starter::RejectPartyInvite(const int32 LocalUserNum, const FOnlineSessionInviteAccelByte& PartyInvite)
    {
    if (!GetABSessionInt())
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot reject a party invitation. The session interface is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this]()
    {
    OnRejectPartyInviteComplete(false);
    }));
    return;
    }

    const APlayerController* RejecterPC = GetPlayerControllerByLocalUserNum(LocalUserNum);
    if (!RejecterPC)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot reject a party invitation. Rejecter's PlayerController is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this]()
    {
    OnRejectPartyInviteComplete(false);
    }));
    return;
    }

    const FUniqueNetIdPtr RejecterId = GetLocalPlayerUniqueNetId(RejecterPC);
    if (!RejecterId)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot reject a party invitation. Rejecter's NetId is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this]()
    {
    OnRejectPartyInviteComplete(false);
    }));
    return;
    }

    UE_LOG_PARTYESSENTIALS(Log, TEXT("Reject party invitation."));
    GetABSessionInt()->RejectInvite(
    RejecterId.ToSharedRef().Get(),
    PartyInvite,
    FOnRejectSessionInviteComplete::CreateUObject(this, &ThisClass::OnRejectPartyInviteComplete));
    }
  5. 次に、以下のコードを追加して OnRejectPartyInviteComplete() 関数を定義します。この関数は、Header ファイルで定義した完了デリゲートを実行します。

    void UPartyOnlineSession_Starter::OnRejectPartyInviteComplete(bool bWasSuccessful)
    {
    if (bWasSuccessful)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to reject party invitation"));
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to reject party invitation"));
    }

    OnRejectPartyInviteCompleteDelegate.ExecuteIfBound(bWasSuccessful);
    OnRejectPartyInviteCompleteDelegate.Unbind();
    }
  6. 次に、プレイヤーのパーティ招待が拒否されたときにリスニングするための関数をいくつか作成して、招待送信者に通知できるようにします。PartyOnlineSession_Starter クラスの Header ファイルを開き、以下の関数を宣言します:

    protected:
    // ...
    virtual void OnPartyInviteRejected(FName SessionName, const FUniqueNetId& RejecterId) override;
  7. 次に、パーティ招待が拒否されたときに呼び出されるデリゲートを宣言します。このデリゲートのラッパーを使用して、パーティ招待が拒否されたときにイベントをトリガーできます。

    public:
    // ...
    virtual FOnSessionInviteRejected* GetOnPartyInviteRejectedDelegates() override
    {
    return &OnPartyInviteRejectedDelegates;
    }
    private:
    // ...
    FOnSessionInviteRejected OnPartyInviteRejectedDelegates;
  8. 次に、OnPartyInviteRejected() 関数を定義します。PartyOnlineSession_Starter クラスの CPP ファイルを開き、以下のコードを追加します。この関数は、Header ファイルで定義した完了デリゲートをブロードキャストし、誰がパーティ招待を拒否したかを表示する通知もプッシュします。

    void UPartyOnlineSession_Starter::OnPartyInviteRejected(FName SessionName, const FUniqueNetId& RejecterId)
    {
    // Abort if not a party session.
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    const FUniqueNetIdAccelByteUserRef RejecterABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(RejecterId.AsShared());
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Party invitation is rejected by %s"),
    RejecterABId->IsValid() ? *RejecterABId->GetAccelByteId() : TEXT("Unknown"));

    // Display push notification to show who rejected the invitation.
    if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
    {
    StartupSubsystem->QueryUserInfo(
    0,
    TPartyMemberArray{ RejecterABId },
    FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, RejecterABId](
    const FOnlineError& Error, const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
    {
    if (UsersInfo.IsEmpty() || !UsersInfo[0] || !GetPromptSubystem())
    {
    return;
    }

    const FUserOnlineAccountAccelByte& MemberInfo = *UsersInfo[0];

    const FText NotifMessage = FText::Format(PARTY_INVITE_REJECTED_MESSAGE, FText::FromString(
    MemberInfo.GetDisplayName().IsEmpty() ?
    UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(RejecterABId.Get()) :
    MemberInfo.GetDisplayName()
    ));

    FString AvatarURL;
    MemberInfo.GetUserAttribute(ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, AvatarURL);

    GetPromptSubystem()->PushNotification(NotifMessage, AvatarURL, true);
    }));
    }

    OnPartyInviteRejectedDelegates.Broadcast(SessionName, RejecterId);
    }
  9. 次に、パーティ招待が拒否されたときに呼び出されるように OnPartyInviteRejected() をバインドする必要があります。これは、オンラインセッションが初期化されたときに最初に呼び出される事前定義された RegisterOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnSessionInviteRejectedDelegates.AddUObject(this, &ThisClass::OnPartyInviteRejected);
    // ...
    }
    // ...
    }
  10. 最後に、オンラインセッションが非初期化されたときに、パーティ招待拒否イベントのリスニングを停止するためにアンバインドする必要があります。これは、オンラインセッションが非初期化されたときに最初に呼び出される事前定義された ClearOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::ClearOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnSessionInviteRejectedDelegates.RemoveAll(this);
    // ...
    }
    // ...
    }

受信したパーティ招待の処理

前のセクションでは、パーティ招待の承認と拒否を実装しました。このセクションでは、受信したパーティ招待を処理する機能を実装します。この実装により、プレイヤーがパーティ招待を承認または拒否するかを選択できるプロンプトが表示されます。

  1. PartyOnlineSession_Starter クラスの Header ファイルを開き、以下の関数を宣言します:

    protected:
    // ...
    virtual void OnPartyInviteReceived(const FUniqueNetId& UserId, const FUniqueNetId& FromId, const FOnlineSessionInviteAccelByte& PartyInvite) override;
  2. 次に、パーティ招待が受信されたときに呼び出されるデリゲートを宣言します。このデリゲートのラッパーを使用して、パーティ招待が受信されたときにイベントをトリガーできます。

    public:
    // ...
    virtual FOnV2SessionInviteReceivedDelegate* GetOnPartyInviteReceivedDelegate() override
    {
    return &OnPartyInviteReceivedDelegate;
    }
    private:
    // ...
    FOnV2SessionInviteReceivedDelegate OnPartyInviteReceivedDelegate;
  3. 次に、この関数を定義します。PartyOnlineSession_Starter クラスの CPP ファイルを開き、以下のコードを追加します。この関数は、プレイヤーにパーティ招待を承認または拒否するよう通知する通知をプッシュします。また、Header ファイルで定義した完了デリゲートもブロードキャストします。

    void UPartyOnlineSession_Starter::OnPartyInviteReceived(const FUniqueNetId& UserId, const FUniqueNetId& FromId, const FOnlineSessionInviteAccelByte& PartyInvite)
    {
    // Abort if not a party session.
    if (UserId == FromId || PartyInvite.SessionType != EAccelByteV2SessionType::PartySession)
    {
    return;
    }

    const APlayerController* PC = GetPlayerControllerByUniqueNetId(UserId.AsShared());
    if (!PC)
    {
    return;
    }

    const FUniqueNetIdAccelByteUserRef SenderABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(FromId.AsShared());
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Receives party invitation from %s"),
    SenderABId->IsValid() ? *SenderABId->GetAccelByteId() : TEXT("Unknown"));

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);

    // Display push notification to allow player to accept/reject the party invitation.
    if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
    {
    StartupSubsystem->QueryUserInfo(
    0,
    TPartyMemberArray{ SenderABId },
    FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, SenderABId, PartyInvite, LocalUserNum](
    const FOnlineError& Error, const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
    {
    if (UsersInfo.IsEmpty() || !UsersInfo[0] || !GetPromptSubystem())
    {
    return;
    }

    const FUserOnlineAccountAccelByte& MemberInfo = *UsersInfo[0];

    const FText NotifMessage = FText::Format(PARTY_INVITE_RECEIVED_MESSAGE, FText::FromString(
    MemberInfo.GetDisplayName().IsEmpty() ?
    UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(SenderABId.Get()) :
    MemberInfo.GetDisplayName()
    ));

    FString AvatarURL;
    MemberInfo.GetUserAttribute(ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, AvatarURL);

    GetPromptSubystem()->PushNotification(
    NotifMessage,
    AvatarURL,
    true,
    ACCEPT_PARTY_INVITE_MESSAGE,
    REJECT_PARTY_INVITE_MESSAGE,
    FText::GetEmpty(),
    FPushNotificationDelegate::CreateWeakLambda(this, [this, LocalUserNum, PartyInvite](EPushNotificationActionResult ActionButtonResult)
    {
    switch (ActionButtonResult)
    {
    // Show accept party invitation confirmation.
    case EPushNotificationActionResult::Button1:
    DisplayJoinPartyConfirmation(LocalUserNum, PartyInvite);
    break;
    // Reject party invitation.
    case EPushNotificationActionResult::Button2:
    RejectPartyInvite(LocalUserNum, PartyInvite);
    break;
    }
    }
    ));
    }));
    }

    OnPartyInviteReceivedDelegate.ExecuteIfBound(UserId, FromId, PartyInvite);
    OnPartyInviteReceivedDelegate.Unbind();
    }
  4. プレイヤーが招待を承認すると、上記の関数は DisplayJoinPartyConfirmation() という名前の新しい関数を呼び出します。まず、その宣言を作成します。PartyOnlineSession_Starter クラスの Header ファイルを開き、以下のコードを追加します。

    protected:
    // ...
    void DisplayJoinPartyConfirmation(const int32 LocalUserNum, const FOnlineSessionInviteAccelByte& PartyInvite);
  5. 次に、この関数を定義します。PartyOnlineSession_Starter クラスの CPP ファイルを開き、以下のコードを追加します。この関数は、すでにパーティに参加しているプレイヤーに、離脱して新しいパーティに参加するかどうかを尋ねるダイアログを表示します。

    void UPartyOnlineSession_Starter::DisplayJoinPartyConfirmation(const int32 LocalUserNum, const FOnlineSessionInviteAccelByte& PartyInvite)
    {
    // Join the party if not in any party yet.
    if (!GetABSessionInt()->IsInPartySession() || GetPartyMembers().Num() <= 1)
    {
    JoinParty(LocalUserNum, PartyInvite.Session);
    return;
    }

    // Show confirmation to leave current party and join the new party.
    GetPromptSubystem()->ShowDialoguePopUp(
    PARTY_POPUP_MESSAGE,
    JOIN_NEW_PARTY_CONFIRMATION_MESSAGE,
    EPopUpType::ConfirmationYesNo,
    FPopUpResultDelegate::CreateWeakLambda(this, [this, LocalUserNum, PartyInvite](EPopUpResult Result)
    {
    switch (Result)
    {
    case EPopUpResult::Confirmed:
    // If confirmed, join the new party.
    JoinParty(LocalUserNum, PartyInvite.Session);
    break;
    case EPopUpResult::Declined:
    // If declined, reject the party invitation.
    RejectPartyInvite(LocalUserNum, PartyInvite);
    break;
    }
    }
    ));
    }
  6. 次に、パーティ招待が受信されたときに呼び出されるように OnPartyInviteReceived() をバインドする必要があります。これは、オンラインセッションが初期化されたときに最初に呼び出される事前定義された RegisterOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnV2SessionInviteReceivedDelegates.AddUObject(this, &ThisClass::OnPartyInviteReceived);
    // ...
    }
    // ...
    }
  7. 最後に、オンラインセッションが非初期化されたときに、パーティ招待受信イベントのリスニングを停止するためにアンバインドする必要があります。これは、オンラインセッションが非初期化されたときに最初に呼び出される事前定義された ClearOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::ClearOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnV2SessionInviteReceivedDelegates.RemoveAll(this);
    // ...
    }
    // ...
    }

パーティメンバーの削除(キック)の実装

このセクションでは、プレイヤーをパーティからキックする機能を実装します。

  1. PartyOnlineSession_Starter クラスの Header ファイルを開き、以下の関数を宣言します:

    public:
    // ...
    virtual void KickPlayerFromParty(const int32 LocalUserNum, const FUniqueNetIdPtr& KickedPlayer) override;
  2. 同じファイルで、パーティメンバーキックプロセスが完了したときに呼び出されるコールバック関数を宣言します。

    protected:
    // ...
    virtual void OnKickPlayerFromPartyComplete(bool bWasSuccessful, const FUniqueNetId& KickedPlayer) override;
  3. 次に、パーティからプレイヤーをキックするプロセスが完了したときに呼び出されるデリゲートを宣言します。このデリゲートのラッパーを使用して、パーティからプレイヤーをキックするプロセスが完了したときにイベントをトリガーできます。

    public:
    // ...
    virtual FOnKickPlayerComplete* GetOnKickPlayerFromPartyDelegate() override
    {
    return &OnKickPlayerFromPartyCompleteDelegate;
    }
    private:
    // ...
    FOnKickPlayerComplete OnKickPlayerFromPartyCompleteDelegate;
  4. 次に、KickPlayerFromParty() から始めてこれらの関数を定義します。PartyOnlineSession_Starter クラスの CPP ファイルを開き、以下のコードを追加します。この関数は、ターゲットプレイヤーをパーティからキックします。コールバックは OnKickPlayerFromPartyComplete() 関数によって処理されます。

    void UPartyOnlineSession_Starter::KickPlayerFromParty(const int32 LocalUserNum, const FUniqueNetIdPtr& KickedPlayer)
    {
    if (!GetABSessionInt())
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot kick a player from the party. The session interface is not valid."));
    return;
    }

    if (!KickedPlayer)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot kick a player from the party. KickedPlayer's NetId is not valid."));
    return;
    }

    const APlayerController* PC = GetPlayerControllerByLocalUserNum(LocalUserNum);
    if (!PC)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot kick a player from the party. Kicker's PlayerController is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, KickedPlayer]()
    {
    OnKickPlayerFromPartyComplete(false, KickedPlayer.ToSharedRef().Get());
    }));
    return;
    }

    const FUniqueNetIdPtr PlayerNetId = GetLocalPlayerUniqueNetId(PC);
    if (!PlayerNetId)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot kick a player from the party. Kicker's NetId is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, KickedPlayer]()
    {
    OnKickPlayerFromPartyComplete(false, KickedPlayer.ToSharedRef().Get());
    }));
    return;
    }

    UE_LOG_PARTYESSENTIALS(Log, TEXT("Kick party member."));
    GetABSessionInt()->KickPlayer(
    PlayerNetId.ToSharedRef().Get(),
    GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession),
    KickedPlayer.ToSharedRef().Get(),
    FOnKickPlayerComplete::CreateUObject(this, &ThisClass::OnKickPlayerFromPartyComplete));
    }
  5. 次に、以下のコードを追加して OnKickPlayerFromPartyComplete() 関数を定義します。この関数は、Header ファイルで先ほど定義した完了デリゲートを実行します。

    void UPartyOnlineSession_Starter::OnKickPlayerFromPartyComplete(bool bWasSuccessful, const FUniqueNetId& KickedPlayer)
    {
    const FUniqueNetIdAccelByteUserRef KickedPlayerABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(KickedPlayer.AsShared());
    if (bWasSuccessful)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to kick %s from the party."),
    KickedPlayerABId->IsValid() ? *KickedPlayerABId->GetAccelByteId() : TEXT("Unknown"));
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to kick %s from the party."),
    KickedPlayerABId->IsValid() ? *KickedPlayerABId->GetAccelByteId() : TEXT("Unknown"));
    }

    OnKickPlayerFromPartyCompleteDelegate.ExecuteIfBound(bWasSuccessful, KickedPlayer);
    OnKickPlayerFromPartyCompleteDelegate.Unbind();
    }
  6. パーティからプレイヤーをキックする機能を実装しました。次に、プレイヤーがキックされたときにリスニングするための関数をいくつか作成して、キックされたプレイヤーに通知できるようにします。PartyOnlineSession_Starter クラスの Header ファイルを開き、以下の関数を宣言します:

    protected:
    // ...
    virtual void OnKickedFromParty(FName SessionName) override;
  7. 次に、プレイヤーがパーティからキックされたときに呼び出されるデリゲートを宣言します。このデリゲートのラッパーを使用して、プレイヤーがパーティからキックされたときにイベントをトリガーできます。

    public:
    // ...
    virtual FOnKickedFromSession* GetOnKickedFromPartyDelegates() override
    {
    return &OnKickedFromPartyDelegates;
    }
    private:
    // ...
    FOnKickedFromSession OnKickedFromPartyDelegates;
  8. 次に、OnKickedFromParty() 関数を定義します。PartyOnlineSession_Starter クラスの CPP ファイルを開き、以下のコードを追加します。この関数は、Header ファイルで定義した完了デリゲートをブロードキャストし、パーティからキックされたプレイヤーに通知をプッシュします。

    void UPartyOnlineSession_Starter::OnKickedFromParty(FName SessionName)
    {
    // Abort if not a party session.
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    UE_LOG_PARTYESSENTIALS(Log, TEXT("Current logged player is kicked from the party"));

    // Display push notification.
    if (GetPromptSubystem())
    {
    GetPromptSubystem()->PushNotification(KICKED_FROM_PARTY_MESSAGE);
    }

    OnKickedFromPartyDelegates.Broadcast(SessionName);
    }
  9. 次に、プレイヤーがパーティからキックされたときに呼び出されるように OnKickedFromParty() 関数をバインドする必要があります。これは、オンラインセッションが初期化されたときに最初に呼び出される事前定義された RegisterOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnKickedFromSessionDelegates.AddUObject(this, &ThisClass::OnKickedFromParty);
    // ...
    }
    // ...
    }
  10. 最後に、オンラインセッションが非初期化されたときに、プレイヤーがパーティセッションからキックされたイベントのリスニングを停止するためにアンバインドする必要があります。これは、オンラインセッションが非初期化されたときに最初に呼び出される事前定義された ClearOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::ClearOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnKickedFromSessionDelegates.RemoveAll(this);
    // ...
    }
    // ...
    }

パーティメンバーのリーダーへの昇格の実装

このセクションでは、新しいパーティリーダーを昇格させる機能を実装します。

  1. PartyOnlineSession_Starter クラスの Header ファイルを開き、以下の関数を宣言します:

    public:
    // ...
    virtual void PromotePartyLeader(const int32 LocalUserNum, const FUniqueNetIdPtr& NewLeader) override;
  2. 同じファイルで、パーティリーダー昇格プロセスが完了したときに呼び出されるコールバック関数を宣言します。

    protected:
    // ...
    virtual void OnPromotePartyLeaderComplete(const FUniqueNetId& NewLeader, const FOnlineError& Result) override;
  3. 次に、パーティリーダー昇格プロセスが完了したときに呼び出されるデリゲートを宣言します。このデリゲートのラッパーを使用して、パーティリーダー昇格プロセスが完了したときにイベントをトリガーできます。

    public:
    // ...
    virtual FOnPromotePartySessionLeaderComplete* GetOnPromotePartyLeaderCompleteDelegate() override
    {
    return &OnPromotePartyLeaderCompleteDelegate;
    }
    private:
    // ...
    FOnPromotePartySessionLeaderComplete OnPromotePartyLeaderCompleteDelegate;
  4. 次に、PromotePartyLeader() 関数から始めてこれらの関数を定義します。PartyOnlineSession_Starter クラスの CPP ファイルを開き、以下のコードを追加します。この関数は、新しいパーティリーダーを昇格させます。その後、コールバックは OnPromotePartyLeaderComplete() 関数によって処理されます。

    void UPartyOnlineSession_Starter::PromotePartyLeader(const int32 LocalUserNum, const FUniqueNetIdPtr& NewLeader)
    {
    if (!GetABSessionInt())
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot promote new party leader. The session interface is not valid."));
    return;
    }

    if (!NewLeader)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot promote new party leader. New Leader NetId is not valid."));
    return;
    }

    const APlayerController* PC = GetPlayerControllerByLocalUserNum(LocalUserNum);
    if (!PC)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot promote new party leader. Promoter's PlayerController is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, NewLeader]()
    {
    OnPromotePartyLeaderComplete(NewLeader.ToSharedRef().Get(), FOnlineError(false));
    }));
    return;
    }

    const FUniqueNetIdPtr PlayerNetId = GetLocalPlayerUniqueNetId(PC);
    if (!PlayerNetId)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot promote new party leader. Promoter's NetId is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, NewLeader]()
    {
    OnPromotePartyLeaderComplete(NewLeader.ToSharedRef().Get(), FOnlineError(false));
    }));
    return;
    }

    UE_LOG_PARTYESSENTIALS(Log, TEXT("Promote a new party leader."));
    GetABSessionInt()->PromotePartySessionLeader(
    PlayerNetId.ToSharedRef().Get(),
    GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession),
    NewLeader.ToSharedRef().Get(),
    FOnPromotePartySessionLeaderComplete::CreateUObject(this, &ThisClass::OnPromotePartyLeaderComplete));
    }
  5. 次に、以下のコードを追加して OnPromotePartyLeaderComplete() 関数を定義します。この関数は、Header ファイルで定義した完了デリゲートを実行します。

    void UPartyOnlineSession_Starter::OnPromotePartyLeaderComplete(const FUniqueNetId& NewLeader, const FOnlineError& Result)
    {
    const FUniqueNetIdAccelByteUserRef NewLeaderABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(NewLeader.AsShared());
    if (Result.bSucceeded)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to promote %s as the new party leader."),
    NewLeaderABId->IsValid() ? *NewLeaderABId->GetAccelByteId() : TEXT("Unknown"));
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to promote %s as the new party leader."),
    NewLeaderABId->IsValid() ? *NewLeaderABId->GetAccelByteId() : TEXT("Unknown"));
    }

    OnPromotePartyLeaderCompleteDelegate.ExecuteIfBound(NewLeader, Result);
    OnPromotePartyLeaderCompleteDelegate.Unbind();
    }
  6. パーティリーダーを昇格させる機能が完成しました。新しいパーティリーダーが設定されると、パーティセッション更新イベントが呼び出されます。このイベントを使用して、他のパーティメンバーに新しいリーダーが設定されたことを通知できます。パーティセッション更新イベントの処理方法については、次のチュートリアルで学習します。今のところ、新しいパーティリーダーが設定されたことを示す関数を作成します。これを行うには、最後のパーティメンバーをキャッシュして、新しいパーティリーダーと比較します。それらが同じでない場合、新しいパーティリーダーが設定されています。PartyOnlineSession_Starter クラスの Header ファイルを開き、以下の変数を宣言します。これを使用して、最後のパーティリーダーをキャッシュします。

    private:
    // ...
    FUniqueNetIdPtr LastPartyLeader;
  7. プレイヤーが新しいパーティを作成または参加したときに、現在のパーティリーダーをキャッシュします。これは、LastPartyLeader 変数の初期値として機能します。次に、PartyOnlineSession_Starter クラスの CPP ファイルを開き、以下の強調表示されたコードが含まれていることを確認します:

    void UPartyOnlineSession_Starter::OnCreatePartyComplete(FName SessionName, bool bSucceeded)
    {
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    if (bSucceeded)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to create a party"));
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to create a party"));
    }

    // Cache the party leader.
    LastPartyLeader = GetPartyLeader();

    // Reset the party member status cache.
    PartyMemberStatus.Empty();

    OnCreatePartyCompleteDelegates.Broadcast(SessionName, bSucceeded);
    }
    void UPartyOnlineSession_Starter::OnJoinPartyComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
    {
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    if (Result == EOnJoinSessionCompleteResult::Type::Success)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to join a party"));
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to join a party"));
    }

    // Cache the party leader.
    LastPartyLeader = GetPartyLeader();

    // Reset the party member status cache.
    PartyMemberStatus.Empty();

    OnJoinPartyCompleteDelegates.Broadcast(SessionName, Result);
    }
  8. 次に、新しいパーティリーダーが設定された場合に通知を表示する関数を作成します。PartyOnlineSession_Starter クラスの Header ファイルを開き、以下の関数を宣言します。後で、パーティセッション更新イベントが呼び出されたときに、この関数を呼び出します。

    protected:
    // ...
    void DisplayCurrentPartyLeader();
  9. 次に、PartyOnlineSession_Starter クラスの CPP ファイルを開き、上記の関数を定義します。この関数は、新しいパーティリーダー情報を表示する通知をプッシュします。

    void UPartyOnlineSession_Starter::DisplayCurrentPartyLeader()
    {
    // Abort if the party leader is the same.
    if (LastPartyLeader && IsPartyLeader(LastPartyLeader))
    {
    return;
    }

    LastPartyLeader = GetPartyLeader();
    const FUniqueNetIdAccelByteUserPtr LeaderABId = StaticCastSharedPtr<const FUniqueNetIdAccelByteUser>(LastPartyLeader);

    // Query party leader information and then display a notification.
    if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
    {
    StartupSubsystem->QueryUserInfo(
    0,
    TPartyMemberArray{ LeaderABId.ToSharedRef() },
    FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, LeaderABId](
    const FOnlineError& Error, const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
    {
    if (UsersInfo.IsEmpty() || !UsersInfo[0] || !GetPromptSubystem())
    {
    return;
    }

    const FUserOnlineAccountAccelByte& MemberInfo = *UsersInfo[0];

    const FText NotifMessage = FText::Format(PARTY_NEW_LEADER_MESSAGE, FText::FromString(
    MemberInfo.GetDisplayName().IsEmpty() ?
    UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(LeaderABId.ToSharedRef().Get()) :
    MemberInfo.GetDisplayName()
    ));

    FString AvatarURL;
    MemberInfo.GetUserAttribute(ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, AvatarURL);

    GetPromptSubystem()->PushNotification(NotifMessage, AvatarURL, true);
    }));
    }
    }

パーティセッション更新の処理

パーティ関連のイベントが発生すると、パーティセッションが更新されます。たとえば、新しいパーティメンバーが参加または離脱したとき、新しいパーティリーダーが設定されたときなどです。このセクションでは、これらのパーティセッション更新を処理するいくつかの機能を実装します。

  1. PartyOnlineSession_Starter クラスの Header ファイルを開き、以下の変数を宣言します:

    private:
    // ...
    TMap<FString, bool> PartyMemberStatus;
  2. 同じファイルで、以下の関数を宣言します:

    protected:
    // ...
    virtual void OnPartyMemberJoined(FName SessionName, const FUniqueNetId& Member) override;
    virtual void OnPartyMemberLeft(FName SessionName, const FUniqueNetId& Member, EOnSessionParticipantLeftReason Reason) override;
  3. 同じファイルで、上記で宣言した関数によってブロードキャストされるデリゲートとそのラッパーを宣言します。

    public:
    // ...
    virtual FOnSessionParticipantJoined* GetOnPartyMemberJoinedDelegates() override
    {
    return &OnPartyMemberJoinedDelegates;
    }
    virtual FOnSessionParticipantLeft* GetOnPartyMemberLeftDelegates() override
    {
    return &OnPartyMemberLeftDelegates;
    }
    private:
    // ...
    FOnSessionParticipantJoined OnPartyMemberJoinedDelegates;
    FOnSessionParticipantLeft OnPartyMemberLeftDelegates;
  4. OnPartyMemberJoined()OnPartyMemberLeft() から始めて、以下のコードを追加してこれらの関数を定義します。この関数は、パーティメンバーが変更されたとき(たとえば、パーティメンバーが参加または離脱したとき)に呼び出されます。この関数は、どのパーティメンバーがパーティに参加または離脱したかを示す通知をプッシュします。また、前のセクション (パーティメンバーのリーダーへの昇格の実装) で作成した関数を呼び出して、新しいパーティリーダーに関する通知を表示します。また、Header ファイルで定義した完了デリゲートもブロードキャストします。

    void UPartyOnlineSession_Starter::OnPartyMemberJoined(FName SessionName, const FUniqueNetId& Member)
    {
    // Abort if not a party session.
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    // Store status whether the member is the current logged-in player.
    FUniqueNetIdPtr UserId = nullptr;
    if (GetIdentityInt())
    {
    UserId = GetIdentityInt()->GetUniquePlayerId(0);
    }
    const bool bIsMemberTheLoggedInPlayer = UserId && UserId.ToSharedRef().Get() == Member;

    const FUniqueNetIdAccelByteUserRef MemberABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(Member.AsShared());
    const FString MemberABIdStr = MemberABId->GetAccelByteId();

    /* Since this event could be called multiple times, we cache the party member status.
    * This cache is used to execute the following functionalities only when the party member status is changed (not the same status).*/
    if (PartyMemberStatus.Contains(MemberABIdStr))
    {
    if (PartyMemberStatus[MemberABIdStr])
    {
    // Abort if the status is the same.
    return;
    }
    PartyMemberStatus[MemberABIdStr] = true;
    }
    if (!PartyMemberStatus.Contains(MemberABIdStr))
    {
    PartyMemberStatus.Add(MemberABIdStr, true);
    }

    UE_LOG_PARTYESSENTIALS(Log, TEXT("Party participant %s joined to the party "),
    MemberABId->IsValid() ? *MemberABId->GetAccelByteId() : TEXT("Unknown"));

    // Query member information then display a push notification to show who joined the party.
    if (!bIsMemberTheLoggedInPlayer)
    {
    if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
    {
    StartupSubsystem->QueryUserInfo(
    0,
    TPartyMemberArray{ MemberABId },
    FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, MemberABId](
    const FOnlineError& Error,
    const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
    {
    if (UsersInfo.IsEmpty() || !UsersInfo[0] || !GetPromptSubystem())
    {
    return;
    }

    const FUserOnlineAccountAccelByte& MemberInfo = *UsersInfo[0];

    const FString MemberDisplayName = MemberInfo.GetDisplayName().IsEmpty() ?
    UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(MemberABId.Get()) :
    MemberInfo.GetDisplayName();

    const FText NotifMessage = FText::Format(PARTY_MEMBER_JOINED_MESSAGE, FText::FromString(MemberDisplayName));

    FString AvatarURL;
    MemberInfo.GetUserAttribute(ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, AvatarURL);

    GetPromptSubystem()->PushNotification(NotifMessage, AvatarURL, true);
    }));
    }
    }

    // Show notification if a new party leader is set.
    DisplayCurrentPartyLeader();

    OnPartyMemberJoinedDelegates.Broadcast(SessionName, Member);
    }
    void UPartyOnlineSession_Starter::OnPartyMemberLeft(FName SessionName, const FUniqueNetId& Member, EOnSessionParticipantLeftReason Reason)
    {
    // Abort if not a party session.
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    // Store status whether the member is the current logged-in player.
    FUniqueNetIdPtr UserId = nullptr;
    if (GetIdentityInt())
    {
    UserId = GetIdentityInt()->GetUniquePlayerId(0);
    }
    const bool bIsMemberTheLoggedInPlayer = UserId && UserId.ToSharedRef().Get() == Member;

    const FUniqueNetIdAccelByteUserRef MemberABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(Member.AsShared());
    const FString MemberABIdStr = MemberABId->GetAccelByteId();

    /* Since this event could be called multiple times, we cache the party member status.
    * This cache is used to execute the following functionalities only when the party member status is changed (not the same status).*/
    if (PartyMemberStatus.Contains(MemberABIdStr))
    {
    if (!PartyMemberStatus[MemberABIdStr])
    {
    // Abort if the status is the same.
    return;
    }
    PartyMemberStatus[MemberABIdStr] = false;
    }
    if (!PartyMemberStatus.Contains(MemberABIdStr))
    {
    PartyMemberStatus.Add(MemberABIdStr, false);
    }

    UE_LOG_PARTYESSENTIALS(Log, TEXT("Party participant %s left from the party. Reason: %s"),
    MemberABId->IsValid() ? *MemberABId->GetAccelByteId() : TEXT("Unknown"),
    ToLogString(Reason));

    // Query member information then display a push notification to show who left the party.
    if (!bIsMemberTheLoggedInPlayer)
    {
    if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
    {
    StartupSubsystem->QueryUserInfo(
    0,
    TPartyMemberArray{ MemberABId },
    FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, MemberABId](
    const FOnlineError& Error,
    const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
    {
    if (UsersInfo.IsEmpty() || !UsersInfo[0] || !GetPromptSubystem())
    {
    return;
    }

    const FUserOnlineAccountAccelByte& MemberInfo = *UsersInfo[0];

    const FString MemberDisplayName = MemberInfo.GetDisplayName().IsEmpty() ?
    UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(MemberABId.Get()) :
    MemberInfo.GetDisplayName();

    const FText NotifMessage = FText::Format(PARTY_MEMBER_LEFT_MESSAGE, FText::FromString(MemberDisplayName));

    FString AvatarURL;
    MemberInfo.GetUserAttribute(ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, AvatarURL);

    GetPromptSubystem()->PushNotification(NotifMessage, AvatarURL, true);
    }));
    }
    }

    // Show notification if a new party leader is set.
    DisplayCurrentPartyLeader();

    OnPartyMemberLeftDelegates.Broadcast(SessionName, Member, Reason);
    }
  5. 上記の関数は、パーティメンバーのステータス(参加または離脱)を PartyMemberStatus 変数にキャッシュします。この変数は、パーティメンバーが実際に変更された場合にのみ OnPartyMembersChange() を処理することを確認するためのヘルパーです。これはキャッシュであるため、必要に応じてクリーンアップする必要があります。したがって、以下の強調表示されたコードが含まれていることを確認してください:

    void UPartyOnlineSession_Starter::OnCreatePartyComplete(FName SessionName, bool bSucceeded)
    {
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    if (bSucceeded)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to create a party"));
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to create a party"));
    }

    // Cache the party leader.
    LastPartyLeader = GetPartyLeader();

    // Reset the party member status cache.
    PartyMemberStatus.Empty();

    OnCreatePartyCompleteDelegates.Broadcast(SessionName, bSucceeded);
    }
    void UPartyOnlineSession_Starter::OnJoinPartyComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
    {
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    if (Result == EOnJoinSessionCompleteResult::Type::Success)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to join a party"));
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to join a party"));
    }

    // Cache the party leader.
    LastPartyLeader = GetPartyLeader();

    // Reset the party member status cache.
    PartyMemberStatus.Empty();

    OnJoinPartyCompleteDelegates.Broadcast(SessionName, Result);
    }
  6. 次に、OnPartySessionUpdateReceived() 関数を定義します。この関数は、新しいパーティリーダーが設定されたときや、バックエンドからセッション更新を受信したときなど、パーティセッションが更新されたときに呼び出されます。このチュートリアルでは、このイベントを使用して、新しいパーティリーダーが誰であるかを示す通知もプッシュします。その後、Header ファイルで定義した完了デリゲートをブロードキャストします。

    void UPartyOnlineSession_Starter::OnPartySessionUpdateReceived(FName SessionName)
    {
    // Abort if not a party session.
    if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
    {
    return;
    }

    UE_LOG_PARTYESSENTIALS(Log, TEXT("Party session is updated"));

    // Show notification if a new party leader is set.
    DisplayCurrentPartyLeader();

    OnPartySessionUpdateReceivedDelegates.Broadcast(SessionName);
    }
  7. 次に、定義した関数がそれぞれのパーティイベントによって呼び出されるようにバインドする必要があります。これは、オンラインセッションが初期化されたときに最初に呼び出される事前定義された RegisterOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnSessionParticipantJoinedDelegates.AddUObject(this, &ThisClass::OnPartyMemberJoined);
    GetABSessionInt()->OnSessionParticipantLeftDelegates.AddUObject(this, &ThisClass::OnPartyMemberLeft);
    // ...

    GetABSessionInt()->OnSessionUpdateReceivedDelegates.AddUObject(this, &ThisClass::OnPartySessionUpdateReceived);
    }
    // ...
    }
  8. 最後に、オンラインセッションが非初期化されたときに呼び出されないように、これらの関数をアンバインドする必要があります。これは、オンラインセッションが非初期化されたときに最初に呼び出される事前定義された ClearOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::ClearOnlineDelegates()
    {
    // ...
    if (GetABSessionInt())
    {
    // ...
    GetABSessionInt()->OnSessionParticipantJoinedDelegates.RemoveAll(this);
    GetABSessionInt()->OnSessionParticipantLeftDelegates.RemoveAll(this);
    // ...

    GetABSessionInt()->OnSessionUpdateReceivedDelegates.RemoveAll(this);
    }
    // ...
    }

復元されたパーティセッションの処理

プレイヤーがパーティに参加しているときにゲームアプリケーションを終了し、再度ゲームにログインすると、最後のプレイヤーのパーティセッションを復元できます。その後、ゲームが復元されたパーティに再参加するか、離脱するかを決定できます。Byte Wars は復元されたパーティセッションから離脱するため、プレイヤーは再度ゲームにログインしたときに新しいパーティを作成できます。このセクションでは、その方法を学習します。

  1. PartyOnlineSession_Starter クラスの Header ファイルを開き、以下の関数を宣言します:

    protected:
    // ...
    void OnConnectLobbyComplete(int32 LocalUserNum, bool bSucceeded, const FUniqueNetId& UserId, const FString& Error);
  2. 次に、PartyOnlineSession_Starter クラスの CPP ファイルを開き、OnConnectLobbyComplete() 関数を定義します。ゲームは、プレイヤーがロビーに接続したときにこの関数を呼び出します。これは、プレイヤーが正常にログインし、AccelByte Gaming Services (AGS) オンラインセッションを使用する準備ができたときのイベントです。この関数は、復元されたパーティセッションがある場合、それを復元して離脱します。

    void UPartyOnlineSession_Starter::OnConnectLobbyComplete(int32 LocalUserNum, bool bSucceeded, const FUniqueNetId& UserId, const FString& Error)
    {
    if (!bSucceeded)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot restore and leave restored party session. Failed to connect to lobby. Error: %s."), *Error);
    return;
    }

    // Restore and leave old party session.
    GetABSessionInt()->RestoreActiveSessions(
    UserId,
    FOnRestoreActiveSessionsComplete::CreateWeakLambda(this, [this](const FUniqueNetId& LocalUserId, const FOnlineError& Result)
    {
    // Abort if failed to restore party sessions.
    if (!Result.bSucceeded)
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to restore party session. Error: %s"), *Result.ErrorMessage.ToString());
    return;
    }

    // Abort if the session interface is invalid.
    if (!GetABSessionInt())
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to restore party session. The session interface is not valid."));
    return;
    }

    const TArray<FOnlineRestoredSessionAccelByte> RestoredParties = GetABSessionInt()->GetAllRestoredPartySessions();

    // If no restored party session, do nothing.
    if (RestoredParties.IsEmpty())
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("No restored party session found. Do nothing."));
    return;
    }

    // Leave the first restored active party session.
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Restored party session found. Leave the restored party session."));
    GetABSessionInt()->LeaveRestoredSession(
    LocalUserId,
    RestoredParties[0],
    FOnLeaveSessionComplete::CreateWeakLambda(this, [](bool bWasSuccessful, FString SessionId)
    {
    if (bWasSuccessful)
    {
    UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to leave restored party session."));
    }
    else
    {
    UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to leave restored party session."));
    }
    }
    ));
    })
    );
    }
    ヒント

    ゲームで復元されたパーティセッションに再参加したい場合は、前に作成した JoinParty() 関数を使用できます。

  3. 次に、イベントによって呼び出されるように、定義した関数をバインドする必要があります。これは、オンラインセッションが初期化されたときに最初に呼び出される事前定義された RegisterOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
    {
    // ...
    if (GetABIdentityInt())
    {
    GetABIdentityInt()->OnConnectLobbyCompleteDelegates->AddUObject(this, &ThisClass::OnConnectLobbyComplete);
    }
    }
  4. 最後に、オンラインセッションが非初期化されたときに呼び出されないように、その関数をアンバインドする必要があります。これは、オンラインセッションが非初期化されたときに最初に呼び出される事前定義された ClearOnlineDelegates() 関数に以下のコードを追加することで実行できます。

    void UPartyOnlineSession_Starter::ClearOnlineDelegates()
    {
    // ...
    if (GetABIdentityInt())
    {
    GetABIdentityInt()->OnConnectLobbyCompleteDelegates->RemoveAll(this);
    }
    }

リソース