[AbyssDiver] 튜토리얼_TutorialGameState

칼든개구리·2025년 7월 26일
0
post-thumbnail

안녕하세요! 지난 포스팅에서는 다음과 같은 내용을 다뤘습니다.

AADTutorialGameMode: 튜토리얼의 흐름과 규칙을 제어하는 '두뇌'

ATutorialManager: 튜토리얼 UI를 표시하는 '얼굴'

여기서 한 가지 의문이 생깁니다. '두뇌'인 GameMode와 '얼굴'인 TutorialManager는 어떻게 서로를 직접 알지 못하는 상태에서 소통할 수 있을까요?

이번 포스팅에서는 이 둘을 포함한 여러 시스템을 느슨하게 연결해 주는 '접착제'이자, 튜토리얼의 현재 상태를 모든 플레이어에게 공유하는 '중앙 게시판' 역할을 하는 AADTutorialGameState에 대해 알아보겠습니다.

핵심 설계: 상태 저장과 전파
AADTutorialGameState의 역할은 단순하지만 매우 중요합니다.

  1. 상태 저장(State Storage): 튜토리얼의 현재 단계(CurrentPhase)를 변수로 저장합니다. GameState는 GameMode와 달리 서버에서 생성되어 모든 클라이언트에 복제(Replication)되므로, 모든 플레이어가 동일한 게임 상태를 공유할 수 있습니다.

  2. 상태 전파(State Propagation): 저장된 상태가 변경되었을 때, 이 변경 사항을 자신에게 관심 있는 다른 모든 액터에게 "상태가 바뀌었어!" 라고 알려줍니다. 이 알림 기능은 RepNotify와 Delegate를 조합하여 매우 효율적으로 구현됩니다.


  1. 헤더 파일 (ADTutorialGameState.h)
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameState.h"
#include "Tutorial/TutorialEnums.h"
#include "ADTutorialGameState.generated.h"

// ETutorialPhase를 파라미터로 받는 멀티캐스트 델리게이트 선언
DECLARE_MULTICAST_DELEGATE_OneParam(FOnPhaseChangedDelegate, ETutorialPhase);

UCLASS()
class ABYSSDIVERUNDERWORLD_API AADTutorialGameState : public AGameState
{
	GENERATED_BODY()
	
public:
	AADTutorialGameState();

	// "상태가 바뀌었음" 이벤트를 방송할 델리게이트 인스턴스
	FOnPhaseChangedDelegate OnPhaseChanged;

	FORCEINLINE ETutorialPhase GetCurrentPhase() const { return CurrentPhase; }

	// 서버에서만 호출하여 상태를 변경하는 함수
	void SetCurrentPhase(ETutorialPhase NewPhase);

	// 복제될 튜토리얼 단계 변수. OnRep_PhaseChanged 함수와 연결됨
	UPROPERTY(ReplicatedUsing = OnRep_PhaseChanged)
	ETutorialPhase CurrentPhase;
protected:
	// CurrentPhase 변수가 클라이언트에 복제 완료될 때 호출될 함수
	UFUNCTION()
	void OnRep_PhaseChanged();

	// 어떤 변수를 복제할지 엔진에 알려주는 함수
	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};

헤더 파일의 핵심은 CurrentPhase 변수와 OnPhaseChanged 델리게이트입니다.

UPROPERTY(ReplicatedUsing = OnRep_PhaseChanged): CurrentPhase 변수가 네트워크를 통해 복제될 때, OnRep_PhaseChanged라는 함수를 자동으로 호출하도록 지정합니다. 이것이 RepNotify 기능입니다.

DECLARE_MULTICAST_DELEGATE_OneParam: OnPhaseChanged라는 이름의 커스텀 이벤트를 정의합니다. 여러 구독자가 이 이벤트를 함께 수신할 수 있습니다.

  1. 소스 파일 (ADTutorialGameState.cpp) - 복제와 전파 로직
// 복제할 변수 등록
void AADTutorialGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	// CurrentPhase 변수를 네트워크 복제 대상으로 지정
	DOREPLIFETIME(AADTutorialGameState, CurrentPhase);
}

// 서버에서 상태를 변경하는 함수
void AADTutorialGameState::SetCurrentPhase(ETutorialPhase NewPhase)
{
	// 이 코드는 서버(Authority)에서만 실행되어야 함
	if (HasAuthority())
	{
		if (CurrentPhase != NewPhase)
		{
			CurrentPhase = NewPhase;
			// 서버에서도 RepNotify 함수를 직접 호출하여 로컬 변경 사항을 전파
			OnRep_PhaseChanged();
		}
	}
}

// RepNotify 함수: 상태 변경을 최종적으로 전파
void AADTutorialGameState::OnRep_PhaseChanged()
{
	// OnPhaseChanged 델리게이트에 등록된 모든 함수들에게
	// 변경된 CurrentPhase 값을 담아 방송(Broadcast)
	OnPhaseChanged.Broadcast(CurrentPhase);
}

소스 코드의 동작 흐름은 다음과 같습니다.

  1. SetCurrentPhase (서버에서만 실행): GameMode가 튜토리얼 단계를 바꾸려고 이 함수를 호출합니다. 서버는 CurrentPhase 값을 변경하고, 서버 자신을 위해 OnRep_PhaseChanged 함수를 수동으로 호출합니다.

  2. 네트워크 복제: 언리얼 엔진은 서버의 CurrentPhase 값이 변경된 것을 감지하고, 이 값을 모든 클라이언트에게 전송합니다.

  3. OnRep_PhaseChanged (클라이언트에서 자동 실행): 변수를 수신한 클라이언트에서 ReplicatedUsing에 지정된 OnRep_PhaseChanged 함수가 자동으로 실행됩니다.

  4. Broadcast (서버와 모든 클라이언트에서 실행): OnRep_PhaseChanged 함수는 OnPhaseChanged 델리게이트를 방송합니다. 이 델리게이트를 구독하고 있던 TutorialManager의 OnTutorialPhaseChanged 함수가 마침내 실행되어 UI를 업데이트합니다.


최종 시스템 흐름 정리 ⚙️
이제 세 클래스가 어떻게 유기적으로 협력하는지 최종적으로 정리할 수 있습니다.

[GameMode - 서버]: 플레이어의 행동 등으로 다음 단계로 넘어갈 때가 되면 GameState->SetCurrentPhase(NewPhase)를 호출합니다.

[GameState - 서버]: CurrentPhase 변수 값을 바꾸고, 로컬 OnRep_PhaseChanged()를 호출해 OnPhaseChanged 델리게이트를 방송합니다.

[네트워크 엔진]: 서버의 CurrentPhase 변수 변경을 감지하고 모든 클라이언트에 새 값을 전송합니다.

[GameState - 클라이언트]: 새 값을 수신하고, 자동으로 OnRep_PhaseChanged()를 호출해 OnPhaseChanged 델리게이트를 방송합니다.

[TutorialManager - 서버 & 클라이언트]: 구독해 둔 OnPhaseChanged 델리게이트 방송을 수신합니다. 인자로 넘어온 NewPhase 값을 이용해 DataTable을 조회하고 UI를 갱신합니다.

profile
메타쏭이

0개의 댓글