Ch4 팀 프로젝트(11) - 구출 처리

yys·2026년 7월 16일

TIL

목록 보기
73/86

케이지 구출


이번엔 살인마가 다운된 생존자를 케이지에 가두고, 다른 생존자가 다가가 구출하는 전 과정을 작성했다.

정리하면 이번 목표는 이렇다.

  • 케이지를 상호작용 대상으로 만들어 조준·구출이 되게 한다
  • 구출은 완전 회복이 아니라 부상 상태로 풀어준다
  • 서버·클라 사이에 갇힌 모습이 똑같이 보이게 한다

케이지 상호작용화


현재 케이지는 상호작용 대상으로 분류하지 않아서 따로 조준해도 하이라이트가 되지 않았다.

그래서 탈출구와 똑같은 패턴으로 인터페이스를 붙였다.
갇힌 상태(Occupied)일 때만 상호작용 대상이 되게 함으로써, 빈 케이지는 하이라이트가 안되도록 세팅하였다.

bool ACage::IsInteractable_Implementation() const
{
	return CurrentStatus == ECageStatus::Occupied;   // 갇힌 케이지만 구출 대상
}

void ACage::Interact_Implementation(AActor* Interactor)
{
	if (ASurvivorCharacter* Survivor = Cast<ASurvivorCharacter>(Interactor))
		Survivor->BeginRescue(this);
}

FGameplayTag ACage::GetInteractableTag_Implementation() const
{
	return SPGameplayTags::Interactable::Cage;
}

구출 구현


여태 기존 상호작용(줍기·납품·탈출구)은 전부 "내가 나를 위해" 하는 행동이었지만,구출만 유일하게 구조자가 갇힌 자를 대상으로 한다.

그래서 역할을 이렇게 나눴다.

  • 채널링(진행도·몽타주·타이머)은 구조자의 상호작용 컴포넌트가 소유한다
  • 대상(갇힌 자)은 케이지의 TrappedSurvivor 에서 찾는다
  • 완료되면 구조자 컴포넌트가 Victim->RescueFromCage(구조자)를 호출한다
void USPInteractionComponent::BeginRescue(ACage* Cage)
{
	ASurvivorCharacter* Survivor = GetSurvivor();
	if (!Survivor || !Survivor->HasAuthority() || bIsInteract || !Survivor->CanInteract() || !Cage)
		return;

	ASurvivorCharacter* Victim = Cage->GetTrappedSurvivor();
	if (!Victim || Victim->GetSurvivorState() != ESurvivorState::Caged)
		return;

	CurrentCage = Cage;
	bIsInteract = true;
	PlayInteractMontage(RescueMontage.LoadSynchronous());
	GetWorld()->GetTimerManager().SetTimer(
		RescueTimer, this, &USPInteractionComponent::CompleteRescue, Cage->GetRescueDuration(), false);
}

진행도 바(InteractProgress)와 이동 취소는 이전에 만든 구조를 그대로 재활용했다. ComputeInteractProgress에 케이지 분기 하나만 추가하니 기존 진행바가 그대로 떴다.

void USPInteractionComponent::CompleteRescue()
{
	bIsInteract = false;
	StopInteractMontage();

	ACage* Cage = CurrentCage.Get();
	CurrentCage = nullptr;
	if (!Cage) return;

	if (ASurvivorCharacter* Victim = Cage->GetTrappedSurvivor())
		Victim->RescueFromCage(GetSurvivor());   // 구조자를 넘겨준다
	Cage->SetCageStatus(ECageStatus::Empty);
}

감금 로직 캡슐화


살인마가 생존자를 케이지에 넣는 코드는 원래 이렇게 상태 변경·부착 해제·위치 이동을 전부 직접 하고 있었다.

// Before
Survivor->SetSurvivorState(ESurvivorState::Caged);
Survivor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
Survivor->SetActorLocation(TargetCage->GetActorLocation());
TargetCage->SetCageStatus(ECageStatus::Occupied);

이걸 전부 EnterCaged 한 함수 안으로 밀어 넣고, 살인마 쪽은 한 줄만 부르게 했다.

// After
Survivor->EnterCaged(TargetCage);

캡슐화하면서 얻은 게 하나 더 있는데,SetSurvivorState내부에서 이동 잠금·진행 중 상호작용 취소를 자동으로 처리한다는 점이다.
CanMove() 목록에 Caged가 없으니, 상태만 바꿔주면 알아서 그 자리에 고정된다.

감금 위치는 케이지 원점 대신 전용 앵커(PrisonerAnchor) 를 두고 위치·회전을 맞췄다. 화살표 컴포넌트라 에디터에서 창살 안 중앙·정면으로 바로 조정할 수 있다.

void ASurvivorCharacter::EnterCaged(ACage* Cage)
{
	if (!HasAuthority() || !Cage) return;
	CurrentCage = Cage;
	++CagedCount;

	DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
	const FTransform Anchor = Cage->GetPrisonerAnchorTransform();
	Multicast_ApplyCagedPose(Anchor.GetLocation(), Anchor.Rotator());

	if (CagedCount >= 3)                                  // 3회차 = 즉사
	{
		SetSurvivorState(ESurvivorState::Dead);
		return;
	}

	Cage->SetOccupied(this);                             
	SetSurvivorState(ESurvivorState::Caged);

	const float Time = (CagedCount == 1) ? Cage->GetStageOneDuration() : Cage->GetStageTwoDuration();
	GetWorldTimerManager().SetTimer(CageTimerHandle, this, &ASurvivorCharacter::OnCageExpired, Time, false);
}

구출 결과 처리


구출되면 부상(Injured) 상태로만 풀려나고, 이후 별도 회복이 필요하다.

그리고 갇혔던 자를 케이지 밖으로 빼낼 때 구조자 옆으로 텔레포트시켰다.
케이지 탈출 몽타주가 없어서, 케이지 메시에 끼는 것도 방지할 겸 순간이동으로 대체했다.

CagedCount리셋하지 않고, 구출된 뒤 다시 잡히면 다음 단계(더 짧은 제한시간 → 즉사)로 진행되도록 맞춰주었다.

void ASurvivorCharacter::RescueFromCage(ASurvivorCharacter* Rescuer)
{
	if (!HasAuthority()) return;
	GetWorldTimerManager().ClearTimer(CageTimerHandle);
	Multicast_RestoreOrientRotation();

	if (Rescuer)   // 구조자 옆으로 빼냄
	{
		const FVector Drop = Rescuer->GetActorLocation() + Rescuer->GetActorRightVector() * RescueDropOffset;
		SetActorLocation(Drop);
	}

	SetSurvivorState(ESurvivorState::Injured);   // 완전 회복이 아니라 부상으로
}

이렇게 구출 시연이 마무리 된 모습을 볼 수 있다.

profile
게임 개발 지망생

0개의 댓글