Sparta Unreal 부트캠프 96일차

정찬호·2026년 4월 17일

팀프로젝트

오류 수정 - 미션 초기화 관련

제한 시간 내에 샌드백을 부수는 미션에서 샌드백 상태가 초기화되지 않는 문제가 존재했습니다.
코드를 보니 내부에 Multicast RPC가 존재하는데, 얘는 Ownership 등록이 되어 있지 않아서 이게 호출이 안 되었을 텐데.. 왜 호출이 되고 있었을까요?

OnDead에 처리 코드가 중복으로 존재했군요.
동작하지 않는 Multicast 함수를 제거하고, OnDead를 변경하기로 했습니다. Client에서 호출되는 OnRep_CurrentHPRatio에 HP 비율에 따라 다른 처리를 하게 추가했습니다.

그런데 이렇게 하고 다른 곳에 코드를 제거하니 서버에서 변경이 안 되어서 충돌이 발생하게 됩니다.
OnDead와 ResetSandbag에 코드를 추가해 둡니다.

void AVGMissionSandbag::ResetSandbag()
{
	StatComponent->ResetStats();
	MeshComponent->SetVisibility(true);
	MeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
}

void AVGMissionSandbag::BeginPlay()
{
	Super::BeginPlay();

	if (HasAuthority())
	{
		StatComponent->OnDead.AddDynamic(this, &AVGMissionSandbag::OnDead);
		StatComponent->OnHPChanged.AddDynamic(this, &AVGMissionSandbag::OnHPChanged);
	}
}

void AVGMissionSandbag::OnHPChanged(float NewHP, float MaxHP)
{
	// HP 비율을 Replicate해서 클라이언트 UI에 표시
	CurrentHPRatio = (MaxHP > 0.f) ? (NewHP / MaxHP) : 0.f;
}

void AVGMissionSandbag::OnRep_CurrentHPRatio()
{
	// TODO: 위젯 업데이트 로직 or 제거
	UE_LOG(LogTemp, Display, TEXT("CurrentHPRatio is %f"), CurrentHPRatio);
	
	if (FMath::IsNearlyEqual(CurrentHPRatio, 1.f))
	{
		MeshComponent->SetVisibility(true);
		MeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
		OnSandbagReseted.Broadcast();
	}
	else if (FMath::IsNearlyZero(CurrentHPRatio))
	{
		MeshComponent->SetVisibility(false);
		MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	}
	else
	{
		OnSandbagHitted.Broadcast();
	}
}

void AVGMissionSandbag::OnDead(AController* LastInstigator)
{
	MeshComponent->SetVisibility(false);
	MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	// 막타 플레이어와 함께 미션에 보고
	OnSandbagDefeated.Broadcast(LastAttacker);
	UE_LOG(LogTemp, Display, TEXT("[%s] is dead."), *GetName());
}

== 미션 초기화 gif 추가 - X

오전에 테스트를 했을 때는 정상적으로 동작했으나 지금 다시 확인해보니 정상 동작하지 않는 것을 확인했습니다. 주말동안 수정을 할 예정입니다.

오류 수정 - 아이템 버리고 다시 줍기 불가 오류

어제 열심히 수정했던 코드가 올바랐습니다만, OnDropped 함수에 UFUNCTION() 빠뜨렸었네요. 추가 후 정상적으로 동작합니다.

== 아이템 버리고 줍기 gif 추가

밟판 순서 랜덤화

기존 코드에서는 미션에 등록된 순서대로 Gimmick의 인덱스를 정하는 로직으로 되어 있었습니다. 이는 발판을 밟는 미션의 발판 순서가 고정시킵니다.
이전에 야구 게임에서 정답을 랜덤 생성하는 로직을 떠올리며 발판의 순서를 설정하는 로직을 추가했습니다.

수정 전 코드 - AVGMissionBase::BeginPlay()

// 에디터에서 등록된 기믹들에 바인딩
		for (int32 i = 0; i < MissionGimmicks.Num(); i++)
		{
			if (Gimmick)
			{
				Gimmick->SetGimmickIndex(i); // 자동 인덱스 부여

				Gimmick->OnGimmickStateChanged.AddDynamic(
					this, &AVGMissionBase::OnGimmickStateChanged);
				Gimmick->OnGimmickInteracted.AddDynamic(
					this, &AVGMissionBase::OnGimmickInteracted);
			}
		}

수정된 코드 - AVGMissionBase::BeginPlay()

		TArray<int32> Indexes;
		for (int32 i = 0; i < MissionGimmicks.Num(); i++)
		{
			Indexes.Add(i);
		}
		
		// 에디터에서 등록된 기믹들에 바인딩
		for (int32 i = 0; i < MissionGimmicks.Num(); i++)
		{
			int32 Index = FMath::RandRange(0,Indexes.Num()-1);
			AVGMissionGimmickBase* Gimmick = MissionGimmicks[i];
			if (Gimmick)
			{
				Gimmick->SetGimmickIndex(Indexes[Index]); // 자동 인덱스 부여

				Gimmick->OnGimmickStateChanged.AddDynamic(
					this, &AVGMissionBase::OnGimmickStateChanged);
				Gimmick->OnGimmickInteracted.AddDynamic(
					this, &AVGMissionBase::OnGimmickInteracted);
			}
			Indexes.RemoveAt(Index);
		}

=== 발판 밟기 1차

=== 발판 밟기 2차

사운드 추가 작업

FreeSound에서 CC0 사운드를 찾아 Audacity를 사용해 가공해서 각 기믹에 효과음을 추가하고 있습니다.

=== 샌드백 사운드 재생 BP

Gimmick 상태 변화에 연결되어 작동시키기 위해 BP 전용 함수의 선언과 BP에서의 로직 구현을 진행했습니다.

protected:
	UFUNCTION()
	virtual void OnRep_GimmickStateTag();
	
	UFUNCTION(BlueprintImplementableEvent)
	void BP_OnGimmickStateTagChanged(FGameplayTag NewTag);
void AVGMissionGimmickBase::OnRep_GimmickStateTag()
{
	...
    
	// BP 상에서의 로직 실행
	BP_OnGimmickStateTagChanged(GetStateTag());
}

=== Pressure 사운드 재생 BP


TA 분반

새로운 개념을 배웠습니다. 이런게 있구나 라는 생각으로 노션과 강의들으며 내용을 정리해 봅니다.

MRQ

언리얼 엔진의 고품질 렌더링 솔루션이라고 합니다. 영상 내보내기, 캡쳐로 내보기 등의 영상 관련 작업하기에 적합한 시스템으로 이해했습니다.

기존에 Render Movie 라는 것이 있었지만 MRQ가 대체로 나왔으며 성능도 훨씬 좋아보입니다.

기존 Render Movie vs MRQ의 차이

항목기존 Render MovieMovie Render Queue
품질낮음 (실시간 캡처 수준)높음 (다중 샘플링 지원)
배치 렌더링불가여러 Job을 한 번에 처리
출력 포맷AVI, PNG 등 제한적EXR, ProRes 등 다양
모션 블러제한적Temporal Subsampling으로 정밀 표현
16-bit HDR불가EXR 출력으로 가능
설정 재사용불가Preset 에셋으로 저장/공유

게임 트레일러, 버추얼 프로덕션, 건축 시각화 등의 분야에서 특히 유용하다고 합니다.

Movie Render Graph (MRG)

UE 5.4부터 실험적 기능으로 추가되었스빈다.
MRQ를 노드 그래프 방식으로 확장한 것으로, 렌더 레이어와 패스를 시각적으로 구성할 수 있다고 합니다.
현재는 MRQ와 병행해서 사용 가능하고, 향후 MRQ를 대체할 예정이라고 합니다.

플러그인 활성화 및 창 열기

플러그인 활성화

MRQ는 기본 내장 플러그인이지만 직접 활성화해주어야 합니다.

=== 플러그인 창에서 검색한 모습

창 열기

MRQ 창은 다음의 2가지 방법으로 열 수 있습니다.

방법 1. 메인 메뉴 → WindowCinematicsMovie Render Queue

방법 2. 시퀀서 툴바 → Render Movie 버튼 옆 클릭 → Movie Render Queue 선택 후 버튼 클릭

랜더링 대기열 목록이 존재하고, 각 항목이 하나의 Job이라고 합니다. Job은 어떤 시퀀스를 어떻게 출력할 지를 결정하는 것인 것 같습니다.

AA(Anti - Aliasing)

MRQ의 핵심 품질 설정이라 합니다.
최종 프레임은 Spatial(공간) 샘플과 Temporal(시간) 샘플의 조합으로 생성된다고 합니다.

Temporal Sample Count(시간 샘플)

카메라 셔터가 열려 있는 시간을 여러 슬라이스로 나눕니다.
엔진의 모터 브러를 이용해 각 슬라이스 사이를 보간하므로 움직이는 오브젝트의 모션 블러 품질이 향상됩니다.

Spartial Sample Count(공간 샘플)

각 Temporal 샘플마다 N번 랜더하여 누적 평균합니다.
정적 오브젝트의 AA와 레이 트레이싱 노이즈 감소에 효과적입니다.

Temporal vs Spatial 선택 가이드

상황권장
움직이는 오브젝트가 많음Temporal 높임
정적 장면, 레이 트레이싱 노이즈Spatial 높임
전반적 AA 개선효율적으로 Temporal 우선

기타 옵션

항목설명
Override Anti-AliasingAA 방식 재정의 (None, MSAA, FXAA, TAA)
Render Warmup Count렌더 전 예열 프레임 수 (TAA 히스토리 안정화용) /추천 : 32
Engine Warmup Count렌더 전 엔진 틱 예열 수 / 추천 : 200

⚠️ 주의: Override Anti-AliasingNone으로 설정하면 TAA가 비활성화됩니다.

이 경우 AA 품질을 유지하려면 Spatial Sample Count를 8 이상으로 올려야 합니다.

⚠️ 주의: BP나 CPP에서 델타타임 변화량에 따른 코드의 경우 TAA 렌더를 위한 서브프레임에서도 델타타임이 동작됩니. 주의해야합니다.

렌더 패스 (Render Passes)

기본적으로 Deferred Rendering 패스 하나로 최종 프레임이 출력됩니다.

추가 패스를 설정하면 포스트 프로덕션용 데이터를 함께 출력할 수 있습니다.

패스 이름설명
Deferred Rendering기본 최종 이미지 (뷰포트와 동일)
Detail Lighting디테일 라이팅만
Lighting Only라이팅만 (텍스처 제외)
Reflections반사 패스
Unlit라이팅 제거
profile
게임 개발 지망생입니다.

0개의 댓글