콜리전은 단순히 눈에 보이는 모델링이 부딪히는 것이 아니라, 내부 질서와 규칙에 따라 작동하는 충돌 처리 전용 시스템.
- 충돌 반응 규칙: - Block + Block = Block (막힘)
- Overlap + Overlap (또는 Block) = Overlap (겹침/통과)
- Ignore + (무엇이든) = Ignore (무시)
- 오브젝트 채널과 프리셋: 오브젝트 채널은 '내가 어떤 부류(결계, 몬스터 등)인지'를 정의하고, 프리셋은 '내가 앞으로 다른 채널들을 어떻게 대할 것인지(막을지, 겹칠지)'를 미리 세팅해 두는 곳.
- 단순 vs 복합 콜리전: - 단순 콜리전: 폴리곤 수가 적은 박스, 캡슐 등의 형태. 캐릭터 이동이나 기본 충돌 시 성능을 위해 사용.
- 복합 콜리전: 스태틱 메시의 실제 형태를 그대로 따라간 정밀한 폴리곤. 정밀 타격이 필요할 때 제한적으로 사용. (Project Settings에서 섞어 쓰기 옵션 설정 가능)
- 충돌 우선순위: Ignore > Overlap > Block 순으로 강력함. (블록 상태에서는 다른 반응으로 바꿀 수 있지만, 이그노어 상태에서는 블록으로 강제로 바꿀 수 없음)
3. 트레이스 (Trace / Raycast)
보이지 않는 광선을 쏴서 충돌 여부를 검사하는 강력한 무기. 무거운 물리적 총알 객체를 생성하는 대신 트레이스를 쓰면 멀티플레이 서버 부하(최적화)를 크게 줄일 수 있음.
- Kismet vs UWorld 트레이스: - Kismet: UKismetSystemLibrary를 사용. 디버깅 선 긋기(DrawDebug)와 매개변수 정리가 잘 되어 있어 개발 시 매우 편리함.
- UWorld: GetWorld()를 직접 사용. 디버그는 불편하지만, 자유도가 높고 미세하게 속도가 더 빠름.
- Single vs Multi 트레이스:
- Single Trace: 광선을 쏘다가 처음으로 Block 당한 물체 딱 하나만 반환하고 멈춤.
- Multi Trace: 광선을 쏘며 관통하는 여러 물체들을 배열로 담아 반환함. (Overlap 판정까지 모두 인식 가능)
- 비동기 트레이스 (Async Trace):
- 한 번에 수많은 광선을 쏠 때(예: AI 정찰 시스템) 게임이 끊기는(동기 방식의 한계) 것을 막기 위해, 별도의 작업대(스레드)에서 뒷단 계산을 시키는 방식.
- 트레이스 작업이 끝나면 델리게이트(OnAsyncTraceCompleted)를 통해 순차적으로 결과물(OutHits)을 돌려받아 안전하게 처리함.
예시 코드:
① 동기 트레이스 (Single)
한 번에 하나의 물체만 블록(Block) 판정으로 찾아냄
void ATraceTest::StartSingleTrace()
{
FHitResult HitResult;
TArray<AActor*> ActorsToIgnore;
ActorsToIgnore.Add(this); // 나 자신(Player)이 맞는 것 방지
UKismetSystemLibrary::LineTraceSingle(
GetWorld(),
GetActorLocation(), // 시작점
GetActorForwardVector() * 1000.f + GetActorLocation(), // 끝점 (앞으로 1000)
UEngineTypes::ConvertToTraceType(ECC_Visibility), // 충돌 채널
false, ActorsToIgnore,
EDrawDebugTrace::ForOneFrame, // 디버그 선 표시
HitResult, true, FLinearColor::Red, FLinearColor::Green, 5.0f
);
}
② 비동기 트레이스 (Async Multi)
여러 물체를 관통(Overlap)하여 찾거나, 한 번에 수많은 트레이스를 쏠 때 메인 스레드 부하를 줄이기 위해 사용
void ATraceTest::StartAsyncTrace()
{
// 1. 작업이 끝나면 돌려받을 함수(델리게이트) 연결
FTraceDelegate TraceDelegate;
TraceDelegate.BindUObject(this, &ATraceTest::OnAsyncTraceCompleted);
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
// 2. 비동기 요청 (뒷단에서 연산 시작)
AsyncTraceHandle = GetWorld()->AsyncLineTraceByChannel(
EAsyncTraceType::Multi,
GetActorLocation(), GetActorForwardVector() * 1000.f + GetActorLocation(),
ECC_Visibility, QueryParams, FCollisionResponseParams::DefaultResponseParam, &TraceDelegate
);
}
// 3. 작업 완료 후 실행되는 콜백 함수
void ATraceTest::OnAsyncTraceCompleted(const FTraceHandle& Handle, FTraceDatum& Data)
{
for (const FHitResult& Hit : Data.OutHits)
{
AActor* HitActor = Hit.GetActor();
UE_LOG(LogTemp, Warning, TEXT("Hit: %s"), *HitActor->GetName());
}
}
4. 데미지 시스템 (Damage System)
언리얼 엔진에 내장된 유기적인 데미지 전달 아키텍처.
- 데미지 주고받기: - 송신 (가해자): ApplyDamage(일반), ApplyPointDamage(총기/방향성), ApplyRadialDamage(폭발) 함수 사용.
- 수신 (피해자): TakeDamage 가상 함수를 오버라이딩하여 체력 감소 로직 구현.
- UDamageType의 역할: 이 데미지가 물리 타격인지, 화상인지, 낙사 등 월드 데미지인지 '성격'을 규정해 주는 기준표.
- CDO(원본 객체) 활용의 정석: 데미지 타입을 확인할 때 매번 새 객체를 만들지 않고, CDO 원본 마스터 객체 하나를 슥 훑어봄. DamageEvent.DamageTypeClass->GetDefaultObject<UDamageType>() 형태로 가져와서 매우 가볍고 빠르게 특수 로직(예: 화상 지속 시간, 방어력 관통 등)을 적용할 수 있음.
① 가해자 측: 데미지 입히기 (ApplyDamage)
// OnAsyncTraceCompleted 같은 함수 내부에서 적중한 액터에게 데미지 전달
UGameplayStatics::ApplyPointDamage(
HitActor, // 피격 대상 (맞은 놈)
50.f, // 데미지 수치
GetActorForwardVector(), // 공격 방향 (밀려나는 방향 계산용)
Hit, // 히트 정보 (맞은 부위 등)
GetInstigatorController(), // 가해자 컨트롤러
this, // 유발 액터 (나)
UMyTestDamageType::StaticClass() // 데미지 성격 (불, 독 등)
);
② 피해자 측: 데미지 받기 (TakeDamage 오버라이드)
float AMyCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
float FinalDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
// 전달받은 데미지 타입의 CDO(원본 객체)를 가져옴!
const UFireDamageType* FireDamage = DamageEvent.DamageTypeClass->GetDefaultObject<UFireDamageType>();
if (FireDamage)
{
// 불 데미지 전용 특수 로직 처리
FinalDamage *= (1.0f + FireDamage->ArmorPenetration); // 방어력 관통 적용
this->ApplyBurnEffect(FireDamage->BurnDuration); // 화상 지속시간 적용
}
CurrentHP -= FinalDamage;
return FinalDamage;
}