2015년 9월 9일

언리얼 엔진 4 Visual Logger 사용하기

저자: Joe Graf

사용자 리포트로는 찾아내기 어려운 버그 클래스들이 있습니다. 이런 타입의 버그들은 특히 AI가 게임 상황에 따라 내린 판단에 관여된 복잡한 단계에 연관되어 있죠. 이런 경우와 유사한 사용자 리포트 이슈는 관찰 가능한 결과만 보고하게 되고, 이렇게 되면 게임플레이 상태에 연관된 이슈를 트래킹 하기는 참 어려워 집니다. 언리얼 엔진 4는 이런 문제 해결을 돕는 툴이 있습니다. 이 도구를 Visual Logger라고 부릅니다. 그 핵심은 액터의 상태를 캡쳐하여 로그로 남기는 기능과 비슷하고 이를 게임이나 에디터에서 문제 발생 이후에 시각적으로 보여줍니다. 이 툴은 게임플레이 상태를 유저가 리포트 할 때에 볼 수 있도록 하는 강력한 툴입니다. 이 데이터를 봄으로써 여러분은 현재 코드에서 어떤 일이 일어났어야 하는지와 비교할 수 있게 됩니다.

Visual Logger가 실제 동작하는 것을 보기 위해, 창 > 개발자 툴 > 비주얼 로거를 선택합니다. 4.7 이전 버전에서는 콘솔창을 켜고(~ 키) "VisLog" 라고 입력합니다. 여기에는 StragyGame의 세션이 있는데, 첫 번째 이미지는 Visual Logger를 보여주고 두 번째 이미지는 에디터의 뷰포트를 보여줍니다. 보라색으로 AI가 갈 길이 보이고 타임라인에 빨간 위치 표시 마커가 있습니다.


동작 중인 Visual Logger


Visual Logger가 정보를 표시하고 있는 에디터의 레벨 뷰포트 창

이미지에서 표시된 부분은 Visual Logger가 녹화중에 로그로 기록되는 액터의 리스트입니다. 여기에는 이름으로 액터를 빠르게 찾는 검색 창도 있습니다.


액터 리스트와 검색 옵션

다음 이미지에서 표시된 것은 타임라인 뷰입니다. 이미지 속의 타임라인 바는 23.53초에 머물러 있습니다. StrategyAIController_1가 선택되어 있으므로, 이 시간 이 액터의 각종 정보들이 아래 상자에 나타납니다. 비취색 막대기는 로그가 있는 부분을 말합니다. 타임라인 바는 앞, 뒤로 움직이면서 현재 위치 시간대의 로그를 보여줍니다.


타임라인 바 영역

아래 이미지의 왼쪽 아래 사분면에, Visual Logger가 이 액터에서 캡쳐한 로그 중 해당 시간에 있는 데이터를 보여줍니다. 데이터는 주어진 프레임마다 액터가 UE_VLOG() 매크로를 이용한 Visual Log 진입을 요청하면 한 번씩 캡쳐가 됩니다. 데이터는 여러분의 게임을 위해 카테고리화 되고 커스터마이징 될 수 있는 스냅샷의 일부로 캡쳐됩니다. (아래에 예제 코드가 있습니다.)


커스텀 카테고리가 확장된 액터 스냅샷 영역

아래 이미지의 표시된 영역은 Visual Logger의 로그 표시 영역을 둘러싸고 있습니다. 로그가 써지는 영역을 보여주고, 로그 메시지 자체도 보입니다. 프레임마다 여러개의 로그 메시지가 있는 경우, 이 영역에 리스트로 나타날 것입니다.


메시지가 표시되는 로그 영역

Visual Logger의 역할 중 주된 부분에 대해 이해하셨으므로, 여러분의 게임에 어떻게 추가하는지를 보겠습니다. 아래 이미지에 일인칭 템플릿을 이용해 GDC 발표에 사용된 GDC라는 프로젝트를 만들었습니다. 액터의 상태 정보를 캡쳐하기 위해 사용되는 함수는 한 가지이고 UE_VLOG() 매크로를 콜해서 Visual Logging을 합니다.


예제 데이터를 보여주는 Visual Logger

툴의 스냅샷 영역을 채우기 위해서 여러분은 가상함수인 GrabDebugSnapshot()을 오버라이딩 하여야 합니다. 이 함수는 액터의 일부로 임플멘트 되어 있기 때문에 커스텀 정보를 추가하고 싶지 않은 경우라면 이 단계는 건너뛰어도 됩니다. Visual Logger는 빌드 설정 밖에서 컴파일 될 수 있으므로, 여러분은 적절한 헤더를 달아줘야 합니다. 아래 코드는 GDCCharacter.h에 스냅샷을 지원하기 위해 추가된 코드입니다.

#if ENABLE_VISUAL_LOG

    /** 이 액터에 관한 정보를 visual logger에 덧붙임 */

    virtual void GrabDebugSnapshot(FVisualLogEntry* Snapshot) const override;

#endif

아래에 보이는 이 방법의 임플멘테이션은 카테고리를 하나 추가하고 항목을 하나 추가합니다. 다시 말씀드리자면, 이 부분은 컴파일에서 Visual Logger를 포함시킨 경우를 위해 #ifdef로 감쌌습니다. 이 코드는 GDCCharacter.cpp에 사용되었습니다.

#if ENABLE_VISUAL_LOG

void AGDCCharacter::GrabDebugSnapshot(FVisualLogEntry* Snapshot) const

{

    Super::GrabDebugSnapshot(Snapshot);

    const int32 CatIndex = Snapshot->Status.AddZeroed();

    FVisualLogStatusCategory& PlaceableCategory = Snapshot->Status[CatIndex];

    PlaceableCategory.Category = TEXT("GDC Sample");

    PlaceableCategory.Add(TEXT("Projectile Class"), ProjectileClass != nullptr ? ProjectileClass->GetName() : TEXT("None"));

}

#endif

이 함수는 액터가 데이터를 Visual Logger에 전달할 때만 콜됩니다. 예로, 작동시키기와 코드 추가가 용이한 발사되는 투사체를 로깅하였습니다. 여기 아래 OnFire()함수에, UE_VLOG() 매크로가 사용된 것을 볼 수 있습니다. 이 방법으로 Visual Logger에게 액터의 어떤 데이터를 캡쳐할 지를 정해 줍니다. 위에서 말씀드렸듯, 매크로의 첫 번째 사용시, Visual Logger가 GrabDebugSnapshot()를 콜해서 스냅샷 영역에 표시할 때 필요한 데이터를 가져옵니다. 이 매크로는 UE_LOG() 매크로처럼 컴파일이 되므로 #ifdef로 묶을 필요가 없습니다.

void AGDCCharacter::OnFire()

{

    // 투사체 발사

    if (ProjectileClass != NULL)

    {

        const FRotator SpawnRotation = GetControlRotation();

        // MuzzleOffset 이 카메라 로컬 스페이스에 있기 때문에 캐릭터 위치로부터 최종 소염기 위치의 오프셋을 계산하기 전에 월드 위치로 변환합니다.

        const FVector SpawnLocation = GetActorLocation() + SpawnRotation.RotateVector(GunOffset);

        UWorld* const World = GetWorld();

        if (World != NULL)

        {

            // 소염기에 투사체를 소환합니다.

            World->SpawnActor<AGDCProjectile>(ProjectileClass, SpawnLocation, SpawnRotation);

            UE_VLOG(this, LogFPChar, Verbose, TEXT("Fired projectile (%s) from location (%s) with rotation (%s)"),

                *ProjectileClass->GetName(),

                *SpawnLocation.ToString(),

                *SpawnRotation.ToString());

        }

    }

    // 소리가 정해져 있으면 재생시켜 줍니다.

    if (FireSound != NULL)

    {

        UGameplayStatics::PlaySoundAtLocation(this, FireSound, GetActorLocation());

    }

    // 총 발사 애니메이션이 정해져 있으면 재생시켜 줍니다.

    if(FireAnimation != NULL)

    {

        // Get the animation object for the arms mesh

        UAnimInstance* AnimInstance = Mesh1P->GetAnimInstance();

        if(AnimInstance != NULL)

        {

            AnimInstance->Montage_Play(FireAnimation, 1.f);

        }

    }

}

텍스트 정보만 로그로 기록하는 것이 아니라 여러분은 위에서 StrategyGame의 레벨 뷰포트에서 보았듯, 시각적인 모양 정보를 로그로 남길 수 있습니다. 이 기능은 게임에서 어떤 일이 벌어지고 있는지를 보여주기 때문에 아주 강력합니다. 아래 이미지에 시각적으로 기록될 수 있는 서로 다른 시각적 로그 모양이 있습니다.


예제 시각적 로그 모양들: 경로 정보, 실린더, 콘, 캡슐, 박스

아래 매크로는 로그 모양을 사용하는 방법을 보여줍니다.

  • UE_VLOG_SEGMENT
  • UE_VLOG_LOCATION
  • UE_VLOG_BOX (축에 정렬된 박스)
  • UE_VLOG_OBOX (방향이 지정된 박스)
  • UE_VLOG_CONE
  • UE_VLOG_CYLINDER
  • UE_VLOG_CAPSULE

 

각 매크로는 로그 모양 정보 기록을 위한 서로 다른 파라미터를 필요로 합니다. 여러분은 VisualLogger.h에서 로그에 필요한 것이 각기 무엇인지 보실 수 있습니다.

마지막으로 말씀드리고자 하는 것은 Visual Logger는 이러한 파일들을 나중의 디버깅 세션을 위해서 저장할 수 있다는 것입니다. QA팀이 해당 세션을 저장하였다면, 해당 이슈를 해결하는 사내 시스템에 이를 첨부하고, 여러분은 이 파일과 해당 레벨을 열어서 무슨일이 일어났었는지 알 수 있게 됩니다. 이 툴은 여러분의 디버깅 프로세스를 엄청나게 가속시킬 것입니다.