● 상호 작용 액터의 기능 확장
A. 엘리베이터 버튼에 좀비 웨이브 넣기

제일 처음에 팀원들 끼리 게임 내용을 구성할 때 특정 구간에 좀비떼가 몰려오는 상황을 구현하기로 했었다. 그리고 적 기능을 담당하는 팀원이 구현했다. 일단 블루프린트로 만들었고, 엘리베이터 앞에 콜리전을 만들어서 플레이어와 겹치면 좀비떼가 몰려오도록 만들었다. 이제 이것을 내가 만든 액터와 합쳐야 한다.
private:
//문열림 연출용 함수
UFUNCTION()
virtual void DoorOpenTimeLineFunc(float Output);
//문 액터
UPROPERTY(EditInstanceOnly, Category = "Door Action", meta = (AllowPrivateAccess = true))
AActor* DoorActor = nullptr;
//문열림 연출 실행
FOnTimelineFloat UpdateFunctionFloat;
//문열림 연출용 CurveFloat
UPROPERTY(EditDefaultsOnly, meta = (AllowPrivateAccess = true))
UCurveFloat* DoorTimelineCurveFloat;
//문열림 연출용 Timeline
UPROPERTY(EditDefaultsOnly, Category = "Door Action", meta = (AllowPrivateAccess = true))
UTimelineComponent* DoorTimeline;
그 전에, 엘리베이터 버튼에 대해서 문열림 연출을 구현하지 않았기 때문에 지금 구현햐야 한다. 일단 병원 문 액터에서 타임라인 관련 내용을 가져왔다.
protected:
//수많은 좀비가 몰려오다.
UFUNCTION(BlueprintImplementableEvent, Category = "Door Action")
void StartWave(AActor* PlayerActor);
나는 엘리베이터 버튼 액터를 C++코드로 만들었지만 팀원의 좀비웨이브는 블루프린트로 만들었다. 그래서 이 사이를 이어주기 위해 블루프린트로 내용을 구현하는 함수를 선언했다.
A-1. 특정 좀비가 생성되지 않을 조건


내가 만든 스테이지2의 구성중 하나는 터널 사이를 이어주는 작은 통로인데 양 끝에 문이 있다. 스테이지 시작 방향의 문은 닫힌 상태고 스테이지 끝 방향의 문은 열린 상태다. 그리고 두개의 문의 스위치는 각각의 방향에 있는 터널에 있다. 그래서 지나가려면 스테이지 시작 방향의 문은 반드시 열어야 하지만 스테이지 끝쪽 방향의 문은 닫을 수 있다. 그리고 이 문 닫음 여부는 마지막 좀비 웨이브의 좀비 수에 영향을 준다.
//GateSwitch.h
public:
//이 스위치가 다루는 문의 열림 여부
bool GetGateOpen() const;
문 스위치 액터의 코드에서 문열림 여부를 확인할 수 있도록 public 함수를 만든다.
//ElevatorButton.h
private:
//추가 좀비 생성 여부를 결정하는 문 스위치
UPROPERTY(EditInstanceOnly, Category = "Enemy Wave", meta = (AllowPrivateAccess = true))
TArray<class AGateSwitch*> GateSwitches;
엘리베이터 버튼 액터에서 여러 문 스위치 액터를 등록할 수 있도록 TArray를 만든다.
//ElevatorButton.h
//특정 상황에 따라 추가로 좀비를 생성할지 결정
UFUNCTION(BlueprintPure, Category = "Door Action")
bool CanMakeOtherEnemies();
//ElevatorButton.cpp
bool AElevatorButton::CanMakeOtherEnemies()
{
if (GateSwitches.IsEmpty())
return true;
//지정한 문 중 하나라도 닫힌 상태라면 추가 생성 불가
for (const AGateSwitch* GateSwitch : GateSwitches)
{
if (::IsValid(GateSwitch) && !GateSwitch->GetGateOpen())
return false;
}
return true;
}
등록한 문 스위치중 하나라도 닫힌 상태면 추가 생성을 못하게 한다. 이 함수는 블루프린트에서 호출 할 수 있도록 BlueprintPure속키워드를 적는다. (입력 없이 반환만 필요한 경우 BlueprintCallable이 아니라 BlueprintPure를 쓰는 쪽이 편하다.)
private:
//반드시 실행하는 좀비 웨이브
UPROPERTY(EditInstanceOnly, BlueprintReadOnly, Category = "Enemy Wave", meta = (AllowPrivateAccess = true))
TArray<AWaveManager*> DefaultWaveMangers;
//조건 만족시 추가로 실행하는 좀비 웨이브
UPROPERTY(EditInstanceOnly, BlueprintReadOnly, Category = "Enemy Wave", meta = (AllowPrivateAccess = true))
TArray<AWaveManager*> AdditionalWaveMangers;
팀원이 만든 좀비를 대량으로 만드는 클래스가 있는데 이것도 반드시 생성할 것과 조건하에 생성할 것을 나눈다.
A-2. 좀비 웨이브 기능을 엘리베이터 버튼에 붙여넣기


팀원이 만든 기능을 내가 만든 코드와 결합한다. 일단 좀비 생성 기능을 붙여놓기는 하나 내가 선언한 변수를 사용할 것이므로 적절히 변형시킨다.
- 블루프린트 이벤트 시작은 내가 선언한 함수 호출이다.
- 기존 트리거에서 사용한 매개변수 중 사용하는 것을 내가 선언한 함수의 매개변수로 만들고 그것을 사용하게 했다.
- 팀원은 좀비를 대량으로 만드는 클래스를 그냥 맵에 배치된 모든 것을 가져오게 만들었다. 나는 반드시 실행할 것과 조건 하에 실행할 것으로 나누었고 그게 액터에 등록된 상태니 그 변수를 사용하도록 만든다.
- 기존에는 TArray 순회를 1번만 하지만 나는 조건에 따라 2번 할 수도 있다.

좀비가 몰려오는 기능이 완성됐다.
A-3. 잠시후 열리는 문
엘리베이터 버튼을 누르면 바로 열리는 것이 아니라 잠시 후에 열린다. 그 사이에 몰려오는 좀비 무리와 싸우는 것이 의도한 설계다.

엘리베이터 버튼이 사용하는 타임라인 그래프는 다른 것과 이질적인 모습이다. 시작시 -1이고 30초에 걸쳐서 서서히 0이 되다가 30초 부터 값이 급등한다. 이 30초부터 문이 열린다.
void AElevatorButton::DoorOpenTimeLineFunc(float Output)
{
//음의 값은 무시한다.
if (Output < 0)
{
return;
}
//세로 방향으로 이동
const FVector Value = FVector(0, 0, Output);
DoorActor->SetActorLocation(FirstDoorLocation + Value);
}
30초 이전에는 음의 값이니 무시하고 30초부터 들어오는 값을 사용하여 문을 이동시키도록 했다.
B. 카드 아이템을 획득해야 작동하는 문 스위치



이전 글에서 적지 않은 내용이 있는데, 문 스위치 액터는 하나의 코드지만 2개의 블루프린트에서 사용하고 있었다. 작은 통로를 막는 문을 담당하는 스위치 말고 출구로 향하는 길을 막는 큰 문을 담당하는 스위치가 있다.
두 액터는 할당된 메시, 타임라인, 그리고 배정된 문 액터는 다르지만 그 외에는 다를 것이 없어서 같은 코드를 쓰고 있었다. 그런데 카드 아이템의 존재 목적이 필요하기에 큰 문을 담당하는 액터를 바꿀 것이다. 스테이지 내에 1개만 존재하며 출구를 막고 있다는 의미가 있기 때문이다.
//GateSwitch.h
protected:
//이 스위치를 사용할 조건을 만족하는가
virtual bool UsingSwitchCondition() const;
//GateSwitch.cpp
bool AGateSwitch::UsingSwitchCondition() const
{
//원본 함수는 무조건 true
return true;
}
부모 클래스가 될 문 스위치 액터에 bool형 가상 함수를 만들었다. 원본 함수는 항상 true를 반환할 것이다.
void AGateSwitch::Interaction(ABaseCharacter* Character)
{
//스위치 사용이 가능한가
if (!UsingSwitchCondition())
return;
//문열림 여부 변경
bGateOpen = !bGateOpen;
if (bGateOpen)//열기
{
SwitchTimeline->Play();
GateTimeline->Play();
}
else//닫기
{
SwitchTimeline->Reverse();
GateTimeline->Reverse();
}
}
상호작용 함수내 최상단에 이 함수를 호출하여 false면 작동하지 않게 만든다. 부모 클래스에서는 true만 반환하는 함수라서 항상 작동할 것이지만 이 클래스를 상속받는 자식 클래스에서는 다른 결과가 나올 것이다.
//LargeGateSwitch.h
UCLASS()
class ALargeGateSwitch : public AGateSwitch
{
GENERATED_BODY()
public:
ALargeGateSwitch() : AGateSwitch() {}
protected:
//이 스위치를 사용할 조건을 만족하는가
virtual bool UsingSwitchCondition() const override;
};
//LargeGateSwitch.cpp
bool ALargeGateSwitch::UsingSwitchCondition() const
{
//플레이어의 카드 획득 여부로 결정
UCH3GameInstance* GameInstance = Cast<UCH3GameInstance>(UGameplayStatics::GetGameInstance(GetWorld()));
return GameInstance->Level2KeyCard;
}
그러면 이제 기존 문 스위치 액터 클래스를 상속받아 새로운 액터를 만들고, 선언했던 가상 함수에서는 카트 아이템 획득 여부를 반환하도록 한다.


큰 문을 담당하는 액터인 블루프린트에서 클래스 세팅에 들어가면 상속받을 클래스를 변경할 수 있는데 이것을 새로 만든 코드인 클래스를 지정하면 된다.
C. 철장 문 관리 액터와 UI 연동
팀원이 퍼즐 정답 입력 UI를 완성했다고 한다. 그래서 액터와 UI 서로 연동되도록 만들어야 한다.
void ACageLocker::Interaction(ABaseCharacter* Character)
{
if (bDoorOpen)
return;
//번호 입력 UI 열기
AChoiceHubPlayerController* PlayerController = Cast<AChoiceHubPlayerController>(Character->GetController());
if (!PlayerController)
{
UE_LOG(LogTemp, Warning, TEXT("UI 열기 불가"));
return;
}
PlayerController->ShowPasswordKeypad();
}
처음에는 상호작용 즉시 문이 열리도록 했지만 상호작용 함수에 그 기능이 빠졌다. 대신 UI 여는 기능을 실행한다.
private:
static ACageLocker* Instance;
public:
static ACageLocker* GetInstance();
//문열기
void OpenDoor();
//문닫기
void CloseDoor();
그리고 이 액터에 static instance를 만든다. 또한 문을 여는 기능을 public함수로 따로 만든다. 여기에 더하여 반대 기능인 문닫기 함수도 만들었는데 이는 내가 사용하려고 만들었다.
void ACageLocker::OpenDoor()
{
if (bDoorOpen)
return;
DoorTimeline->Play();
bDoorOpen = true;
}
void ACageLocker::CloseDoor()
{
if (!bDoorOpen)
return;
DoorTimeline->ReverseFromEnd();
bDoorOpen = false;
}
타임 라인 기능을 도입하여 문을 열고 닫는다. UI를 담당한 팀원은 올바른 정답 입력시 문을 여는 함수를 실행하기만 하면 될 것이다. 퍼즐을 담당하는 액터도 static instance라서 팀원이 UI 기능을 완성하는 데에는 짧은 시간이 소요됐다.
'언리얼엔진 학습 및 과제' 카테고리의 다른 글
| 온라인 채팅 구현하기 (1) (0) | 2025.11.19 |
|---|---|
| 팀 프로젝트 슈터 게임 (8) (0) | 2025.11.14 |
| 팀 프로젝트 슈터 게임 (6) (1) | 2025.11.11 |
| 팀 프로젝트 슈터 게임 (5) (0) | 2025.11.10 |
| 팀 프로젝트 슈터 게임 (4) (0) | 2025.10.31 |