개인프로젝트

[Unreal_C++_DarkSoul]#20_기능 구현, Level Sequence

Hardii2 2023. 9. 17. 15:36

 

[Unreal_C++_DarkSoul]#20_Level Sequence

 

Cinematic을 재생하는 Level Sequence를 제작하고, Game Play에 삽입합니다.

 


 

Overview

 

  1. 개요 및 설계
  2. 코드
  3. 영상

 

#1. 개요 및 설계

1. 개요

  • 일반 Stage(일반 근거리 공격 객체와 원거리 공격 객체만 나오는 단계)와 Boss Stage(보스 객체가 나오는 단계)의 구별이 필요하다고 판단했으며, 이를 위해 오직 Boss Stage에 발생하는 특별한 연출을 구현해보고자 했습니다.
  • 재생 가능한 Level Sequence를 제작하고, Game Play 중 Player 캐릭터 객체가 일정 위치에 도달하면 Cinematic을 재생합니다. 이때, Level Sequence의 재생은 Game Play와 자연스럽게 연결되어 Seamless Transition(Game Play와 Cinematic 재생의 서로 간 끊김 없는 전환)을 목표로합니다.

 

2. 설계

  1. Level Sequence : Unreal Engine의 Level Sequence는 Level의 시퀀스 애니메이션을 만들고 편집하는 데 사용되는 도구입니다. 더불어, Level Sequence는 레벨에 배치된 개체, 조명, 카메라 및 기타 요소를 사용하여 영화 같은 시퀀스를 만들 수 있습니다. 이 프로젝트에서 Level Sequence는 플레이어 캐릭터가 Boss Stage의 특정 위치에 도달하면 Cinematic 영상을 재생하여, 일반 Stage와 대비를 만들기 위한 장치로 활용합니다.
  2. Trigger_CinematicPlay 객체: 먼저, Level에 위치한 Level Sequence의 재생을 유도하기 위한 Trigger_CinematicPlay 객체를 정의합니다. Trigger_CinematicPlay 객체는 Level의 특정 장소에 위치하여 Player 캐릭터 객체와 상호작용합니다. 단순히, Player 캐릭터 객체가 Trigger_CinematicPlay의 Box Component가 정의하는 특정 바운더리 내 위치하면 "충돌 검사"를 통해 Player 캐릭터 객체를 인지합니다. 그리고, Trigger_CinematicPlay 객체는 Editor에서 미리 입력받은 Level Sequence를 재생시킵니다.
  3. Grid 객체: Grid 객체는 공간 분할 패턴과 객체 풀링을 활용해 월드에 배치된 적 객체들을 "위치 값"을 기준으로 효율적으로 나누어 저장하고 관리합니다. Grid객체는 Level Sequence가 Cinematic 영상을 재생하는 동안 자신이 관리하는 적 객체들의 활동을 비활성화시키고, Level Sequence의 재생이 완료되는 시점에 다시 활성화시키도록 수정합니다.

 

#2. 코드

1. Level Sequence

 

 

Details

 

  • 먼저, Level Sequence를 제작합니다.
  • 제작 방법은 보통의 편집 프로그램과 비슷하고, 추가적으로 카메라 샷의 "블렌드" 기능을 활성화시켜 영상에 적용하면 Game Play와 자연스럽게 연결되어 Seamless Transition을 구현할 수 있습니다! 자세한 내용은 아래 "영상" 항목에 게시한 구현 영상을 참고하세요.

 

2. Trigger_CinematicPlay::BeginPlay()

void ATrigger_CinematicPlay::BeginPlay()
{

	Super::BeginPlay();
	
	// [23-09-08] : Level Sequence And Cinematic
	CinematicTriggerBox->OnComponentBeginOverlap.AddDynamic(this, &ATrigger_CinematicPlay::OnOverlapBegin);

}

 

Details

 

  • Trigger_CinematicPlay 객체는 Player 캐릭터 객체와 "충돌"하면, Unreal 에디터에서 입력받은 Level Sequence를 재생합니다. 따라서, Box Component의 "OnComponentBeginOverlap" Delegate 함수에 Level Sequence의 재생 여부를 관리하는 함수 "OnOverlapBegin"를 동적 바인딩 합니다.

 

3. Trigger_CinematicPlay::OnOverlapBegin()

void ATrigger_CinematicPlay::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{

	FTimerHandle LevelSeqPlayHandler;
	GetWorld()->GetTimerManager().SetTimer(LevelSeqPlayHandler, FTimerDelegate::CreateLambda([&]() {
		if (PlayCount < Max_PlayCount && LevelSeqActor)
		{
			PlayLevelSeq();
			PlayCount++;
		}
		}), 2.f, false);

}

 

Details

 

  • Player Character 객체와 Box Component의 충돌 처리를 수행하는 함수 "OnBeginOverlap" 함수는 Level Sequence의 최대 재생 횟수를 확인하고 "PlayLevelSeq" 함수를 호출합니다.
  • 이때, Timer를 활용하여 Player Character와 Box Component의 충돌 시점과 Level Sequence 재생 시점 사이에 딜레이를 두기 위함입니다.

 

4. Grid::BeginPlay()

void AGrid::BeginPlay()
{

	Super::BeginPlay();

	//...
    
	// [23-09-08] : Level Sequence And Cinematic
	if (bDeactivateGridWhileLevelSeqPlay)
	{
		HandleLevelSeqStarted();

		TArray<AActor*> AllActors;
		UGameplayStatics::GetAllActorsOfClass(GetWorld(), ALevelSequenceActor::StaticClass(), AllActors);
		for (auto Actor : AllActors)
		{
			if (Actor)
			{
				ALevelSequenceActor* LevelSeq = Cast<ALevelSequenceActor>(Actor);
				if (LevelSeq && LevelSeq->GetSequencePlayer())
				{
					LevelSeq->GetSequencePlayer()->OnFinished.AddDynamic(this, &AGrid::HandleLevelSeqFinished);
				}
			}
		}
	}

}

 

Details

 

  • Grid 객체는 Level Sequence가 재생하는 동안 자신이 관리하는 적 객체 그룹의 활동을 비활성화시키며, Level Sequence가 끝나는 시점에 다시 활성화시킵니다.
  • Grid 객체는 Unreal 에디터에서 미리 입력받은 "bDeactivateGridWhilleLevelSeqPlay" 값이 참일 경우 Level Sequence가 재생하는 시점에 적 객체들의 활동성을 관리하는 "HandleLevelSeqStarted" 함수를 호출하고, Level Sequence의 "OnFinished" Delegate 함수에 역시 적 객체 그룹의 활동성을 관리하는 "HandleLevelSeqFinished" 함수를 동적 바인딩합니다.

 

5. Grid::HandleLevelSeqStarted()

// [23-08-31] Deactivate Grid While Level Seq Playing
void AGrid::HandleLevelSeqStarted()
{

	for (const auto Enemy : EnemyGroupOnGrid)
	{
		if (Enemy)
		{
			// #1. Actor
			Enemy->SetActorHiddenInGame(true);
			if (Enemy->GetWeaponComponent()->GetCurrentWeapon())
				Enemy->GetWeaponComponent()->GetCurrentWeapon()->OffSelected();

			// #3. AI
			AAIController* AI = Cast<AAIController>(Enemy->GetController());
			if (AI && AI->GetBrainComponent())
			{
				AI->GetBrainComponent()->PauseLogic("AI Pause Logic");
			}
			PrintLine();
		}
	}

	if (BossEnemy)
	{
		BossEnemy->SetActorHiddenInGame(true);
		if (BossEnemy->GetWeaponComponent()->GetCurrentWeapon())
			BossEnemy->GetWeaponComponent()->GetCurrentWeapon()->OffSelected();

		AAIController* AI = Cast<AAIController>(BossEnemy->GetController());
		if (AI && AI->GetBrainComponent())
		{
			AI->GetBrainComponent()->PauseLogic("AI Pause Logic");
		}
	}

	PrintLine();

}

 

Details

 

  • "HandleLevelSeqStarted" 함수는 Grid 객체가 관리하는 적 객체 그룹의 활동을 비활성화시킵니다.
  • 먼저, Grid 객체가 관리하는 모든 적 객체를 숨기고, 이들이 현재 착용하고 있는 무기 또한 해제해 주며 이들의 AI 객체의 Brain Component를 멈춥니다.

 

6. Grid::HandleLevelSeqFinished()

// [23-08-31] Deactivate Grid While Level Seq Playing
void AGrid::HandleLevelSeqFinished()
{

	for (const auto Enemy : EnemyGroupOnGrid)
	{
		if (Enemy)
		{
			Enemy->SetActorHiddenInGame(false);

			TArray<AWeapon*> Weapons = Enemy->GetWeaponComponent()->GetWeapons();
			if (Weapons.Num() > 0 && Weapons.IsValidIndex(0))
			{
				EWeaponType WeaponType = Weapons[0]->GetWeaponType();
				Enemy->GetWeaponComponent()->EquipWeapon(WeaponType);
			}

			AAIController* AI = Cast<AAIController>(Enemy->GetController());
			if (AI && AI->GetBrainComponent())
			{
				AI->GetBrainComponent()->ResumeLogic("AI Resume Logic");
			}
		}
	}

	if (BossEnemy)
	{
		BossEnemy->SetActorHiddenInGame(false);

		TArray<AWeapon*> Weapons = BossEnemy->GetWeaponComponent()->GetWeapons();
		if (Weapons.Num() > 0 && Weapons.IsValidIndex(0))
		{
			EWeaponType WeaponType = Weapons[0]->GetWeaponType();
			BossEnemy->GetWeaponComponent()->EquipWeapon(WeaponType);
		}

		AAIController* AI = Cast<AAIController>(BossEnemy->GetController());
		if (AI && AI->GetBrainComponent())
		{
			AI->GetBrainComponent()->ResumeLogic("AI Resume Logic");
		}
	}

	bDeactivateGridWhileLevelSeqPlay = false;

}

 

Details

 

  • 반대로, "HandleLevelSeqFinished" 함수는 Grid 객체가 관리하는 모든 적 객체의 가시성을 활성화고 무기를 착용하며, AI 객체의 Brain Component를 다시 동작하도록 합니다. 

 

#3. 영상

1. 구현 영상