관리 메뉴

기억을 위한 기록들

UE4 - Trace를 활용해 탐색하기 (BP & C++) 본문

UnrealEngine/Unreal Engine 관련

UE4 - Trace를 활용해 탐색하기 (BP & C++)

에드윈H 2020. 4. 14. 15:54

* 쓰기에 앞서 누군가에게 알려주는 글이라기보다는 혼자 공부하며 정리하는 글쪽에 가까우니 해당 글을 100% 믿고 참고하시면 안됩니다. 틀린 부분이 있고 부족한 부분이 있을 수 있습니다. 지적 해주셔도 감사하겠습니다. *

 

언리얼엔진에서 사용하는 Trace(뜻 : 추적 ) 시스템을 알아보겠습니다.

 

사실 기본적인건 공식홈페이지 문서에서 잘 정리되어 있습니다.

 

https://docs.unrealengine.com/ko/Engine/Physics/Tracing/Overview/index.html

 

트레이스 개요

언리얼 엔진 4 의 트레이스 시스템 개요입니다.

docs.unrealengine.com

 

BluePrint

 

우선 먼저 블루프린트 (이하 BP)에서 간단한 Trace  방법으로는 4가지가 있다.

(우선 어느 클래스 위치에서든 웬만하면 Collision 카테고리 내)

 

1. Box (사각형)

 

2. Capsule (캡슐형)

3. Line (선)

4. Sphere (원형

 

 

4개의 대표적인 모형이 있습니다. 박스,캡슐,선,원형이라는 차이점들과

1. Multi라는 수식어가 붙어 있다.

2. Trace다음 ByChannel,ByProfile,ByObjects으로 구분이 되고 있다.

 

우선 Multi의 차이를 알아보면 

1. Multi여부 

 

"싱글 트레이스는 트레이스에서 단일 히트 결과만을, 멀티 트레이스는 다중 히트 결과를 반환합니다. 트레이스를 통해 사용되는 광선 유형을 직선, 박스, 캡슐, 구체 중에서 지정할 수도 있습니다."
 - 공식 문서 내용 중 일부 - 

 

공식 문서의 설명과 같이 앞에 "Multi~"가 붙은 트레이스는 다 중 히트 결과를 반환해줍니다.

Line Trace와 Multi Line Trace의 반환값 Multi는 배열로 반환되고,none-multi는 단일변수로 반환되고 있다. Branch를 붙여준건 그냥... 찾아낸 결과 있을때만 변수에 담기 위해랄까...

Multi가 있다면 추적하고자 하는 대상을 원하는 범위 이내에 다중으로 찾아낼수 있다.

(ex: 찾아낸 다음 가장 가까운 적 찾기, 찾은 적이 몇마리인지 확인하기 등등...) 

 

2. ByChannel / ByProfile / ByObjects 구분

 

위에서 단일탐색할지 다수탐색할지를 구문했다면 해당 3개는 탐색 된 '대상'에 관련한 정보이다.

에디터에서 "프로젝트 세팅 - 콜리전" 에서 확인 가능하다.

 

위 부터 Object Channels / Channels / Preset

 

 

위 부터 Object Channels / Channels / Preset 3가지가 있다. 

 

 설정  -> 구분되는 이름 

 Object Channels->ByObjects (용도 : 콜리전 영역에 지정하는 콜리전 채널) 
 Channels->ByChannel  (용도 : 어떤 행동에 설정하는 콜리전 채널) - 이득우의 언리얼 C++ 게임개발의 정석 중 일부-
 Preset ->  ByProfile 과 동일하다 (이름을 어느정도 맞춰주긴 했는데 이럴거면 똑같이 해주지...아무튼)

 

보이는 3개를 각각 임의로 추가하여 확인해보겠다.

 

새 Object 추가  기본 반응 각자 설정에 맞도록 한다
새 Channel 추가 기본 반응도 마찬가지

 

새 Profile 추가. 위에서 추가한 Monster 오브젝트와 Attack 겹침 설정으로 바꿔 주었다.

 

이런식으로 추가를 할수 있다.

대신 Object 와 Channel의 추가는 두개 포함하여 총 18개까지만 추가 할 수 있다. 그러니

게임 기획을 할때 18개 이내로 해주는것이 좋다.(어쩌면 그렇게 많은게 좋지 않을수도 있다는점)

 

이제 추가해준 것들을 확인해보면

 

우선 ForObjectsObject Channels 설정에 추가한 Monster가 추가되었다.

Monster Object가 배열인덱스 끝에 추가되었다.

 

ByChannel에도 Trace Channels에 추가 한 Attack이 생겼다.

 

 

ByProfile 에는 Preset 설정에서 추가한 BossMonster를 이름 형식으로 추가해야된다 그래서 오타를 발생시키면 안되니 유의해야한다.

 

 

 

 

이런식으로 프로젝트 설정- 콜리전과 직접적인 연관이 되어 있는것을 알수 있었다.

 

트레이스를 정리하자면

1.무슨 모양으로 할지

2. 단일로 할지 다중으로 할지 등이 있다.

 

BluePrint - > CPP로 알아보자 

 

우선 LineTraceForObjects를 들어가보면

BP에서의 모습 
CPP로 구현되어 있는 모습

BP 파라미터 -  Start/End/Object Types/Trace Complex/ Actors to Ignore / Draw Debug Type/ Ignore Self / Trace Color / Trace Hit Color / Draw Time 총 10개의 입력값이 있고,

 

CPP 파라미터 - 맨앞에 UObject* WorldContextObject 말고 뒤에 10개는 BP와 동일하다.

 

 

UObject* WorldContextObject 존재 이유 ?

 

CPP 함수 훑어보기

1. 코드

static const FName LineTraceSingleName(TEXT("LineTraceSingleForObjects"));

FCollisionQueryParams Params = ConfigureCollisionParams(LineTraceSingleName, bTraceComplex, ActorsToIgnore, bIgnoreSelf, WorldContextObject);

 

1. 코드분석 해보기 (ConfigureCollisionParams(...)함수)

 

//FCollisionQueryParams Params  -> 탐색방법에 대한 설정 값을 모은 구조체 

  ConfigureCollisionParams(...) 함수를 이용하여 설정한다

  파라미터 살펴보기 하나씩 보면

1.FName TraceTag    : 태그 네이밍부터

2.bool bTraceComplex : 추적 복잡성 여부 (디테일한 판정이 아니라면 false 추천)

3.const TArray<AActor*>& ActorsToIgnore : 갖고 있는 무시할 액터 배열에 추가해준다.

4.bool bIgnoreSelf가 True라면 

ConfigureCollisionParams(...)함수 내 부분

을 실행한다....

5.현재 글쓴이는 WorldContextObject를 이해 못하고 있다...일단 제쳐두고 다시 작성해야겠다.

이제 FCollisionQueryParams Params 설정은 되었다.

다음으로 넘어가기전에

아까 프로젝트 설정 - 콜리전 부분에서 세팅한 ( Monster,Attack,BossMonster )등을 어디서 확인할 수 있는지 알아보자

프로젝트 폴더에 보면  Config - DefaultEngine.ini 파일에 

[/Script/Engine.CollisionProfile] 라는 부분이 있다.

 

 

더보기
사이에 아까 추가한 Monster와 Attack이 있는 걸 알 수 있다.

새로 추가한게 1번 Monster 그다음 2번 Attack...순으로 Monster는 Object였고, Attack은 Channel로 했지만 둘이 통합하여 1번 2번 순으로 진행하게 된다 이러하여 총 18개만 가능하다고 한것같다.(Channel18번 까지)

프로필도 존재한다.

추가된 Attack과 Monster에 대한 설정이 나온다. 확인해본 결과 기본적으로 설정되어 있는 'WorldStatic','WorldDynamic'등의 기본값이 변경되어야만 해당 파일에도 적용이 되는걸 알 수 있었다.

 

 

프로젝트 설정에 입력했던 채널들은

 

EngineTypes.h

ECC_GameTraceChannel1 부터 ECC_GameTraceChannel2 .. 이렇게 순차적으로 Montser, Attack에게 할당되었다.

 

 

 

 

FCollisionObjectQueryParams ObjectParams = ConfigureCollisionObjectParams(ObjectTypes); 이다

ConfigureCollisionObjectParams(...)함수에서는

간단하게 타겟이 된 파라미터에서 넣어준 ObjectTypes를

UEngineTypes::ConvertToCollisionChannel 함수

EObjectTypeQuery이나 ETraceTypeQuery를 실제 사용되고 있는 ECollisionChannel 값으로 가져온다. 그리고 가져온 값을 FCollisionObjectQueryParams 타입으로 반환해주게 된다.

 

 

만약에 가져온 타입이 IsVaild 하지 않는다면 "올바르지 않은 오브젝트 타입"이라는 말을 뱉어내고 false를

return 하게 된다.

2.

if (ObjectParams.IsValid() == false){
               UE_LOG(LogBlueprintUserMessages, Warning, TEXT("Invalid object types"));
               return false;
}

 

 

여기까지 성공적으로 왔다면

입력 파라미터에서 받은 OutHit,Start,End 와 (Hit 결과 구조체, 시작과 끝 위치 값)

위에서 설정해준 Params , ObjectParams 구조체를 엔진에 구현되어 있는 함수 LineTraceSingleByObjectType(...)에 넣어준다.

3.

UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
bool const bHit = World ? World->LineTraceSingleByObjectType(OutHit, Start, End, ObjectParams, Params) : false;

 

 

WorldCollision.cpp

더보기

bool UWorld::LineTraceSingleByObjectType(struct FHitResult& OutHit,const FVector& Start,const FVector& End,const FCollisionObjectQueryParams& ObjectQueryParams, const FCollisionQueryParams& Params /* = FCollisionQueryParams::DefaultQueryParam */) const
{
            return RaycastSingle(this, OutHit, Start, End, DefaultCollisionChannel, Params,                                                                         FCollisionResponseParams::DefaultResponseParam, ObjectQueryParams);
}

 

엄청나게 깊구나... RaycastSingle(...)함수로 한번더 넣어준뒤 Return 값을 받아낸다.

 

 

더보기

 

PhysXCollision.CPP

 

어마어마하다...!

 

 

 

 

 

마지막으로 해당 Trace의 디버깅용으로 화면에 그려줄지를 확인해준다.

4.

#if ENABLE_DRAW_DEBUG
           DrawDebugLineTraceSingle(World, Start, End, DrawDebugType, bHit, OutHit, TraceColor, TraceHitColor,                                                       DrawTime);
#endif

 

그리고 결과값을 return한다.
return bHit;

 

 

 

정리

 

BP - LineTraceForObjects 실행시

 

 

블루프린트의 Trace를 사용하면 간단하고 빠르다.

하지만, 역시나 거쳐야하는 함수들이 반강제적이므로, 제한된다.

 

Only C++

 

Trace를 c++에서 사용해보자 !

 

 

예시 Attack() 함수

위와같이 예를 들어 어택이라는 함수가 작동시, 기존 BP와 같이 여러 파라미터값을 넘겨주며 일일이 확인하기에는 불필요한 요소와 과정들이 있다. 좀 더 깨끗하게 만들어보자.

 

 

 

 

 

 

훨씬 짧아졌다.

위와 같이 FCollisionQueryParams Params은 구조체 생성자들 통해 간단하고 필요한 정보만 설정해준다.

 

CollisionQueryParams.h 중 162줄

트레이스태그는 NAME_NONE으로, TraceComplex 은 false(위에서 추천한 바와같이), IgnoreActor = this (자기자신은 무시)로 해주면 훨씬 짧고 명료해진다. 

 

그리고

 

FCollisionObjectQueryParams ObjectParams = ConfigureCollisionObjectParams(ObjectTypes);

입력한 채널이 있는지 해당 프로젝트 설정에 있는지 확인하는 과정은 다이렉트로 넣어줌으로써

(ECollisionChannel::ECC_EngineTraceChannel1 와 같이) 간단해진다. (혹시나 불안하다면 확인하는 과정을 넣어주는것도 좋을듯)

 

 

@추가 디버그라인 그리기(DebugDraw) 관련

 

우선 헤더파일을 추가해준다.