이번엔 살인마가 다운된 생존자를 케이지에 가두고, 다른 생존자가 다가가 구출하는 전 과정을 작성했다.
정리하면 이번 목표는 이렇다.
현재 케이지는 상호작용 대상으로 분류하지 않아서 따로 조준해도 하이라이트가 되지 않았다.
그래서 탈출구와 똑같은 패턴으로 인터페이스를 붙였다.
갇힌 상태(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); // 완전 회복이 아니라 부상으로
}
이렇게 구출 시연이 마무리 된 모습을 볼 수 있다.
