Ch4 팀 프로젝트(8) - 바닥 흔적 구현

yys·2026년 7월 13일

TIL

목록 보기
70/86

흔적 남기기


데드 바이 데이라이트에는 생존자가 달리면 바닥에 붉은 긁힌 자국이 남고, 살인마만 그걸 보고 추적하는 시스템이 있다. 이번엔 이걸 그대로 간단한 형태로만 카피해보려고 한다.

정리하면 목표는 세 가지다.

  • 달릴 때만 흔적이 남는다
  • 살인마 화면에만 보인다
  • 시간이 지나면 사라진다

흔적 생성 위치

수집품 인벤토리처럼, 흔적도 결국 서버가 판정하고 표현은 각 클라가 하는 구조가 필요했다.
그래서 생존자에 USPScratchMarkComponent를 하나 붙이고, 스폰 결정은 서버(HasAuthority)에서만 하도록 했다.

void USPScratchMarkComponent::TickComponent(float DeltaTime, ...)
{
	const ASurvivorCharacter* Survivor = GetSurvivor();
	if (!Survivor || !Survivor->HasAuthority())   // 흔적 생성 판정은 서버만
	{
		return;
	}
	// ...
}

달릴 때만 일정 간격마다 스폰

매 프레임 흔적을 찍으면 바닥이 도배되기 때문에, 이동 거리를 누적하다가 일정 간격을 넘으면 하나 찍는 방식으로 했다.

bool USPScratchMarkComponent::IsLeavingMarks() const
{
	if (Survivor->bIsCrouched) return false;                          // 웅크림 X
	if (!CharacterMovement->IsMovingOnGround()) return false;         // 접지 상태만
	if (Survivor->GetVelocity().SizeSquared2D() < MinSpeedSq) return false;  // 실제로 이동 중
	return Movement->IsRunning();                                     // 달리기 상태일 때만
}
// 달리는 중 & 이동 거리가 간격을 넘으면 하나 스폰
DistanceSinceLastMark += FVector::Dist2D(CurrentLocation, LastSampleLocation);
if (DistanceSinceLastMark < NextInterval) return;
DistanceSinceLastMark = 0.f;
// 흔적을 처리하기 위한 멀티캐스트 함수
Multicast_SpawnScratchMark(MarkLocation, MarkNormal, BaseYaw);

데칼 노출 세팅


흔적은 모든 클라에 뿌려야 하지만, 실제로 그리는 건 살인마 화면에서만 이어야 한다.

그래서 서버가 Multicast로 브로드캐스트 하면, 각 클라는 자기가 살인마일 때만 데칼을 실제로 스폰하도록 했다.

void USPScratchMarkComponent::Multicast_SpawnScratchMark_Implementation(...)
{
	if (!ScratchDecalMaterial || !ShouldLocalViewerSeeMarks())   // 살인마 아니면 안 그림
	{
		return;
	}
	// 데칼 스폰
}

bool USPScratchMarkComponent::ShouldLocalViewerSeeMarks() const
{
	// 내 화면(로컬 플레이어)의 역할이 살인마일 때만 true
	return LocalPlayerState->GetPlayerRole() == ELobbyPlayerRole::Killer;
}

데칼 스폰 세팅


처음엔 캐릭터 위치에 그냥 데칼을 스폰했더니, 경사나 계단에선 공중에 뜨거나 파묻힌 증상이 나타났다. 데칼은 표면에 딱 붙어야 자연스럽다.

그래서 발밑에서 아래로 라인 트레이스를 쏴서 바닥 지점과 표면 노멀을 구하고, 그 지점에 노멀 방향으로 데칼을 정렬했다.

// 발밑 -> 아래로 트레이스해서 바닥 지점/노멀 구하기
if (World->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECC_Visibility, Params))
{
	MarkLocation = Hit.ImpactPoint;
	MarkNormal   = Hit.ImpactNormal;
}

데칼은 투영 축(X)이 표면을 향해야 해서, 노멀 반대 방향으로 정렬한 뒤 회전·크기·좌우 오프셋에 랜덤을 줘서 흩어지게 했다.

FRotator DecalRotation = FRotationMatrix::MakeFromX(-Normal).Rotator();    // 표면에 투영
DecalRotation.Roll += BaseYaw + FMath::FRandRange(-YawJitter, YawJitter);  // 회전 랜덤

UDecalComponent* Decal = UGameplayStatics::SpawnDecalAtLocation(
	World, ScratchDecalMaterial, DecalSize * SizeScale, FinalLocation, DecalRotation, DecalLifetime);
Decal->SetFadeOut(FadeStart, FadeDuration, false);   // 시간 지나면 페이드아웃

데칼 머티리얼


마지막은 데칼로 쓸 붉은 스크래치 머티리얼이다.
텍스처는 AI로 생성했고, 값이 있는 부분만 빨갛게 칠해주도록 세팅하였다.(검은 배경 + 흰 스크래치)

이렇게 해서 검은 배경은 투명, 흰 스크래치만 붉게 남는 데칼이 완성됐다.

다음과 같이 생존자가 달리면 바닥에 붉은 흔적이 깔리고, 시간이 지나면 사라지는 모습을 확인할 수 있다.
(사진에서는 생존자도 데칼이 보일 수 있도록 세팅했다.)

profile
게임 개발 지망생

0개의 댓글