플러그인 UI - 2

개발 일지|2024. 12. 2. 22:50

Custom Delegate 제작

  • Subsystem 플러그인에서 커스텀 델리게이트를 만들어 플러그인을 사용하는 user widget에 콜백 함수를 바인딩한다.
  • 이 방법으로 MultiplayerSessionSubsystem에서 플러그인을 사용하는 클래스로 연결
  • UserWidget -> Plugin -> SessoinInterface 클래스로 이어지는 One-Way dependency 설계를 델리게이트-콜백함수로 연결하며 플러그인의 호환성 유지
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMultiplayerOnCreateSessionComplete, bool, bWasSuccessful);

// Own Custom delegates for the menu class to bind callbacks
FMultiplayerOnCreateSessionComplete MultiplayerOnCreateSessionComplete;
  • 이를위해 MultiplayerSessionSubsystem에 다이나믹 델리게이트 선언
  • UserWidget 클래스인 MenuUI의 MenuSetup 함수에 콜백함수 바인딩
if (MultiplayerSessionsSubsystem) {
	MultiplayerSessionsSubsystem->MultiplayerOnCreateSessionComplete.AddDynamic(this, &ThisClass::OnCreateSession);
}
  • UserWidget 클래스에 OnCreateSession 함수 구현
void UMenuUI::OnCreateSession(bool bWasSuccessful)
{
	if (bWasSuccessful) {
		if (GEngine) {
			GEngine->AddOnScreenDebugMessage(
				-1,
				15.f,
				FColor::Yellow,
				FString(TEXT("Session created successfully!"))
			);
		}
	}
}
  • MultiplayerSessionSubsystem에 콜백함수 구현. 여기서 델리게이트 리스트 제거 및 MenuUI의 델리게이트 broadcast
void UMultiplayerSessionsSubsystem::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
	if (SessionInterface) {
		SessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegateHandle);
	}
	MultiplayerOnCreateSessionComplete.Broadcast(bWasSuccessful);
}
  • 그리고 HostButtonClicked 함수에 있던 ServerTravel 로직을 OnCreateSession 콜백 함수로 이동
  • 버튼을 누르자마자 바로 레벨이동이 발생하지 않고 세션이 생성될때까지 기다릴 수 있도록 하기위함
void UMenuUI::OnCreateSession(bool bWasSuccessful)
{
	if (bWasSuccessful) {
		if (GEngine) {
			GEngine->AddOnScreenDebugMessage(
				-1,
				15.f,
				FColor::Yellow,
				FString(TEXT("Session created successfully!"))
			);
		}
		UWorld* World = GetWorld();
		if (World) {
			World->ServerTravel("/Game/FirstPerson/Maps/Lobby?listen");
		}
	}
}

 

 


Find & JoinSession 구현

  • Character 클래스에서 했던것과 크게 다르지는 않다. 델리게이트와 콜백함수를 사용해 의존성을 줄인 방식으로 구현된다.
  • 먼저 MultiplayerSessionsSubsystem에서 SessionSearch 관련 로직을 짠다.
  • 마지막에 MenuUI 클래스에서 델리게이트를 통해 찾은 함수와 성공 여부를 보낼 수 있도록 한다.
void UMultiplayerSessionsSubsystem::FindSessions(int32 MaxSearchResults)
{
	if (!SessionInterface.IsValid()) {
		return;
	}
	FindSessionsCompleteDelegateHandle = SessionInterface->AddOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegate);

	LastSessionSearch = MakeShareable(new FOnlineSessionSearch());
	LastSessionSearch->MaxSearchResults = MaxSearchResults;
	LastSessionSearch->bIsLanQuery = IOnlineSubsystem::Get()->GetSubsystemName() == "NULL" ? true : false;

	LastSessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);

	const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
	if (!SessionInterface->FindSessions(*LocalPlayer->GetPreferredUniqueNetId(), LastSessionSearch.ToSharedRef())) {
		SessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegateHandle);

		MultiplayerOnFindSessionsComplete.Broadcast(TArray<FOnlineSessionSearchResult>(), false);
	}
}
void UMultiplayerSessionsSubsystem::OnFindSessionsComplete(bool bWasSuccessful)
{
	if (SessionInterface) {
		SessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegateHandle);
	}

	if (LastSessionSearch->SearchResults.Num() <= 0) {
		// Fail to find any Sessions
		MultiplayerOnFindSessionsComplete.Broadcast(TArray<FOnlineSessionSearchResult>(), false);
		return;
	}

	MultiplayerOnFindSessionsComplete.Broadcast(LastSessionSearch->SearchResults, bWasSuccessful);
}
  • 그러면 MenuUI에서 받은 결과를 바탕으로 반복문을 돌려 결과를 확인하고 우리가 세팅한 조건과 일치하다면 MultiplayerSessionsSubsystem의 JoinSession을 다시 호출한다.
void UMenuUI::OnFindSessions(const TArray<FOnlineSessionSearchResult>& SessionResults, bool bWasSuccessful)
{
	if (MultiplayerSessionsSubsystem == nullptr) {
		return;
	}
	for (auto Result : SessionResults) {
		FString SettingsValue;
		Result.Session.SessionSettings.Get(FName("MatchType"), SettingsValue);
		if (SettingsValue == MatchType) {
			MultiplayerSessionsSubsystem->JoinSession(Result);
			return;
		}
	}
}
  • 그러면 다시 MultiplayerSessionsSubsystem의 JoinSession에서 MenuUI의 JoinSession으로 넘어가도록 호출한다.
void UMultiplayerSessionsSubsystem::JoinSession(const FOnlineSessionSearchResult& SessionResult)
{
	if (!SessionInterface.IsValid()) {
		MultiplayerOnJoinSessionComplete.Broadcast(EOnJoinSessionCompleteResult::UnknownError);
		return;
	}

	JoinSessionCompleteDelegateHandle = SessionInterface->AddOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegate);
	
	const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
	if (!SessionInterface->JoinSession(*LocalPlayer->GetPreferredUniqueNetId(), NAME_GameSession, SessionResult)) {
		SessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegateHandle);
		MultiplayerOnJoinSessionComplete.Broadcast(EOnJoinSessionCompleteResult::UnknownError);
	}

}

void UMultiplayerSessionsSubsystem::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
	if (SessionInterface) {
		SessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegateHandle);
	}
	MultiplayerOnJoinSessionComplete.Broadcast(Result);
}
  • 최종적으로 MenuUI는 EOnJoinSessionCompleteResult 타입의 결과값을 받아 ClientTravel에 필요한 값들을 저장하고 호출하여 세션에 유저가 참가하도록 한다.
void UMenuUI::OnJoinSession(EOnJoinSessionCompleteResult::Type Result)
{
	IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get();
	if (Subsystem) {
		IOnlineSessionPtr SessionInterface = Subsystem->GetSessionInterface();
		if (SessionInterface.IsValid()) {
			FString Address;
			SessionInterface->GetResolvedConnectString(NAME_GameSession, Address);

			APlayerController* PlayerController = GetGameInstance()->GetFirstLocalPlayerController();
			if (PlayerController) {
				PlayerController->ClientTravel(Address, ETravelType::TRAVEL_Absolute);
			}
		}
	}
}

 

이렇게 보면 과정이 복잡한것 같지만, 플러그인을 통해 대부분의 로직이 돌아가면서 여러 프로젝트에서 돌려가며 쓸 수 있는 의존성이 최소화된 구조가 완성되었다.

'개발 일지' 카테고리의 다른 글

플러그인 다듬기  (0) 2024.12.03
Tracking Player  (0) 2024.12.03
플러그인 UI - 1  (0) 2024.12.01
플러그인 등록  (0) 2024.11.30
Join Session  (0) 2024.11.29

댓글()