● 효과음 재생
지금까지 내가 만든 모든 상호작용은 화면상으로는 연출이 잘 작동하지만 효과음이 없으니 허전하다. 이제부터 구현할 차례가 된 것 같다.
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에는 퍼즐 힌트가 없는 방이 있는데 여기는 힌트 대신 아이템을 제공해준다. 보스와 싸우기 전 마지막 준비를 위한 방이라고 보면 된다.
'언리얼엔진 학습 및 과제' 카테고리의 다른 글
| 온라인 채팅 구현하기 (2) (0) | 2025.11.20 |
|---|---|
| 온라인 채팅 구현하기 (1) (0) | 2025.11.19 |
| 팀 프로젝트 슈터 게임 (7) (0) | 2025.11.12 |
| 팀 프로젝트 슈터 게임 (6) (1) | 2025.11.11 |
| 팀 프로젝트 슈터 게임 (5) (0) | 2025.11.10 |