언리얼엔진 학습 및 과제

팀 프로젝트 슈터 게임 (8)

park94128 2025. 11. 14. 22:59

● 효과음 재생

지금까지 내가 만든 모든 상호작용은 화면상으로는 연출이 잘 작동하지만 효과음이 없으니 허전하다. 이제부터 구현할 차례가 된 것 같다.

 

UCLASS(Abstract)
class TEAM14CH3PROJECT_API AInteractionWithPlayer : public AActor, public IItemInterface
{
	GENERATED_BODY()

public:
	//플레이어가 이 함수를 호출하여 구현된 상호작용을 실행한다.
	UFUNCTION(BlueprintCallable)
	virtual void Interaction(ABaseCharacter* Character) override PURE_VIRTUAL(AInteractionWithPlayer::Interaction, );

protected:
	//상효작용시 실행할 사운드
	UPROPERTY(EditDefaultsOnly, Category = "Sound", meta = (AllowPrivateAccess = true))
	USoundWave* InteractionSound;
}

상호 작용을 하는 클래스의 부모에 사운드인 USoundWave 타입의 변수를 protected로 선언한다.

 

void AHospitalDoor::Interaction(ABaseCharacter* Character)
{
	if (bDoorOpen)
		return;
	
	//문을 열어라
	DoorTimeline->Play();
	bDoorOpen = true;
    
    //사운드
	UGameplayStatics::PlaySound2D(this, InteractionSound);

	//문을 열면 더이상 막히지 않음
	DoorMesh->SetCollisionEnabled(ECollisionEnabled::Type::NoCollision);
}

사운드는 UGameplayStatics에 있는 PlaySound2D로 재생할 수 있다. 상호 작용 클래스를 상속받은 모든 클래스에 해당 사운드를 실행하는 기능을 추가한다.

부모가 Abstract 클래스고 그 안에 있는 함수도 순수 가상함수라 부모 단에서 사운드 실행을 구현하지 못하는 것은 아쉽지만 어쩔 수 없다.

 

부모 클래스에서 변수 선언을 했으니 당연히 자식 클래스도 가지고 있고 이를 기반으로 하는 모든 블루프린트에도 노출된다. 각 블루프린트마다 적절한 사운드 에셋을 할당해줬다.

 

//LargeGateSwitch.h
UCLASS()
class ALargeGateSwitch : public AGateSwitch
{
	GENERATED_BODY()

public:
	ALargeGateSwitch() : AGateSwitch() {}

protected:
	//이 스위치를 사용할 조건을 만족하는가
	virtual bool UsingSwitchCondition() const override;

private:
	//문을 열 수 없을때 대신 실행할 사운드
	UPROPERTY(EditDefaultsOnly, Category = "Sound", meta = (AllowPrivateAccess = true))
	USoundWave* CantOpenSound;
};



//LargeGateSwitch.cpp
bool ALargeGateSwitch::UsingSwitchCondition() const
{
	//플레이어의 카드 획득 여부로 결정
	UCH3GameInstance* GameInstance = Cast<UCH3GameInstance>(UGameplayStatics::GetGameInstance(GetWorld()));
	bool bReturnValue = GameInstance->Level2KeyCard;

	//문을 열 수 없으면 사운드 실행
	if (!bReturnValue)
	{
		UGameplayStatics::PlaySound2D(this, CantOpenSound);
	}
	
	return bReturnValue;
}

모든 상호작용 액터가 동일한 사운드 재생 매커니즘을 가지는 것은 아니다. 예를 들어 큰 문을 여는 스위치 액터는 카드 아이템이 없는 상태에서 상호작용을 하면 다른 사운드를 대신 재생하도록 만들었다.

 

● 마지막 싸움

프로젝트의 막바지에 다다랐다. 적을 담당하는 팀원이 보스를 완성했다고 한다. 마지막 스테이지의 철장 안에 들어가면 보스와 싸우도록 구현하면 된다.

 

UCLASS()
class ABossStartTrigger : public AActor
{
	GENERATED_BODY()
	
public:	
	ABossStartTrigger();

protected:
	virtual void PostInitializeComponents() override;

private:
	//캐릭터와 겹칠 때 실행
	UFUNCTION()
	void OnPlayerOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
		int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

	//플레이어와 겹칠 범위인 콜라이더
	UPROPERTY(EditAnywhere, meta = (AllowPrivateAccess = true))
	class UBoxComponent* BoxComp;

	//보스 액터
	UPROPERTY(EditAnywhere, Category = "Boss", meta = (AllowPrivateAccess = true))
	TSubclassOf<class ABoss> BossActor;

	//보스 소환 위치
	UPROPERTY(EditAnywhere, Category = "Boss", meta = (AllowPrivateAccess = true))
	FVector SpawnLocation;

	//트리거 작동 여부
	UPROPERTY(VisibleAnywhere, Category = "Boss", meta = (AllowPrivateAccess = true))
	bool bTriggered;
};

철장 안에 플레이어를 감지할 콜라이더를 가진 액터를 만든다. 보스 액터, 소환 위치를 변수를 통해 입력받는다. 1회만 작동하도록 bool 변수를 선언한다.

 

void ABossStartTrigger::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	BoxComp->OnComponentBeginOverlap.AddDynamic(this, &ABossStartTrigger::OnPlayerOverlap);
}

void ABossStartTrigger::OnPlayerOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
	UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	//플레이어 캐릭터만이 작동 가능, 중복 작동 불가
	if (bTriggered || !OtherActor || OtherActor->ActorHasTag("Player"))
	{
		return;
	}
	
	bTriggered = true;
	
	//보스 몬스터의 활동 시작
	GetWorld()->SpawnActor<ABoss>(BossActor, SpawnLocation, FRotator::ZeroRotator);

	//문을 닫아라
	ACageLocker::GetInstance()->CloseDoor();
}

플레이어와 콜라이더에 닿으면 보스 몬스터인 액터를 생성하고 문을 닫는다.

 

콜라이더는 직접 배치된 맵을 보면서 크기를 조절했는데 어떻게든 콜라이더를 피하는 것을 막기 위해 한쪽 끝에서 반대쪽 끝까지 매우 길게 만들었다.

 

어디서 본 것 같다면 기분탓이다.

직접 플레이 하여 캐릭터가 콜라이더에 닿게 했더니 보스 몬스터가 잘 생성됨을 볼 수 있다.

 

● 맵에 아이템 배치

맵 제작의 최종 단계다. 팀원이 만든 무기 및 아이템 (그리고 내가 직접 만든 아이템)을 맵 여기저기에 배치하면 완성이다.

스테이지 1은 방 안에 생성되는 경험치 아이템과 별개로 복도에 회복 아이템을 배치한다.

 

스테이지 2에서는 제일 처음에 보는 문 스위치 바로 아래에 무기를 배치했다. 생각보다 적들을 강하게 만들어놔서 무기를 쉽게 쥐어주지 않을 경우 게임이 매우 어려워 질 것이다.

 

입구가 가려진 방에는 여러 개의 아이템을 배치한다.

 

어딘가에는 많은 경험치를 얻을 수 있지만 강력한 적이 매복중이다.

 

카드 아이템 배치도 잊으면 안된다.

 

스테이지 3에는 퍼즐 힌트가 없는 방이 있는데 여기는 힌트 대신 아이템을 제공해준다. 보스와 싸우기 전 마지막 준비를 위한 방이라고 보면 된다.