주의 : 공부하면서 정리한 것이기 때문에 잘못된 내용이 있을 수 있습니다.


개요



언리얼 엔진에서 캐릭터를 다루기 위해서 알아야 할 가장 중요한 컴포넌트가 무엇이냐고 질문을 하신다면, 저는 "가장 중요한 것은 UCharacterMovementComponent 입니다" 라고 대답을 할 것입니다.


대부분의 사람들이 이 컴포넌트를 아무 생각없이 사용하고 있지만 이 컴포넌트의 동작을 제대로 이해하지 못한다면 많은 문제에 부딪히게 됩니다. 


튜토리얼같은 것들을 따라 할때는 제한된 조건에서 캐릭터를 움직이기 때문에 이것을 이해해야 할 필요성을 별로 느끼지 못합니다. 하지만 복합적인 상황을 고려하면 반드시 이해해야만 합니다.


몇 가지 간단한 예를 들어 보도록 하겠습니다.

    • RootMotion 을 실행하고 있을 경우에 ACharacter::LaunchCharacter() 류의 메서드 호출이 제대로 동작하지 않습니다.
    • AIController 를 연결했을 경우에 AController::SetControlRotation() 류의 메서드 호출이 제대로 동작하지 않습니다.


결국 조금만 복잡한 상황을 처리하거나 깊이 들어 가면 CharacterMovement 컴포넌트의 동작을 제대로 이해하지 않고서는 작업을 진행하기 어려운 상태가 된다는 것입니다. 그리고 이는 작업의 방향에도 큰 영향을 끼칩니다.


위에서 예로 들었던 RootMotion 을 생각해 봅시다. 만약 idle 이나 walk 를 RootMotion 으로 만들었다면, 플레이어 캐릭터가 몹을 때렸을 때 밀려나게 만들 수 없을 것입니다. 그러므로 그런 상황을 가정했다면 idel 이나 walk 를 RootMotion 이 아니도록 만들어야만 합니다. 아니면 자신만의 CharacterMovement 컴포넌트를 구현해야합니다.


이러한 문제점들을 해결하기 위한 목적 이외에도, CharacterMovement 컴포넌트는 RVO Avoidance System, Navigation System, AI system, Animation System, Physics System, Input System 등과 연결되어 있기 때문에 잘 이해해야 할 시스템입니다.


저도 이 컴포넌트에 대한 이해없이 그냥 예제 보면서 따라하다가 무의미하게 시간만 낭비했습니다. 그래서 이 컴포넌트에 대한 이해도를 높여 보려는 목적으로 이 문서를 작성하게 되었습니다. 부족함이 많은 글이겠지만, 모쪼록 처음 접하는 사람들에게 도움이 되었으면 합니다.


이 문서에서는 모든 요소들에 대해서 세부적으로 다루지는 않습니다. 코드를 분석하는데 도움이 될 수 있는 가이드를 제공할 뿐입니다. 그리고 여기에서는 standalone 으로 플레이한다는 가정을 깔고 있습니다. Client-Server 모델에서는 약간 동작이 다른데 그 부분은 알아서 분석하시기 바랍니다.


다음과 같은 참고 문서들을 확인하시기 바랍니다.


기본 : 속도와 가속도



플레이어가 1초 후에 X 축으로 1 미터 앞에 있기를 원한다고 합시다. 그러면 이 목표를 달성하기 위한 메서드를 만들어야 합니다. 간단하게 생각하면 UCharacterMovement::SetLocation( const FVector& NewLocation, const float Time ) 과 같은 메서드를 만들 수 있습니다. 이것을 기획자가 사용한다면 매우 멋진 인터페이스가 될 것입니다.


하지만 이것을 실제 구현하고 있다고 생각해 봅시다. 순간이동이 아니기 때문에 0 초와 1 초 사이의 무수히( ? ) 많은 프레임에서의 위치를 계산해야 합니다. 등속도 운동을 해야 할까요? 아니면 등가속도 운동을 해야 할까요?


혹은 중간에 뭔가 장애물이 있다고 합시다. 이것을 어떻게 처리해야 할까요?


결국 월드와의 상호작용을 고려한다면 일관된 법칙을 가질 필요가 있습니다. 이미 PhysicsX 라는 물리 엔진을 기반으로 깔고 가고 있기 때문에 이에 맞추는 것이 여러모로 편할 것입니다. 


CharaterMovement 컴포넌트의 기본은 velocity( 속도 ) 와 acceleration( 가속도 ) 을 제어하는데 있습니다. 최종적으로 이를 통해서 캐릭터의 Location, Rotation 이 결정됩니다. 그리고 수많은 시스템들이 속도와 가속도를 결정하는데 영향을 주게 됩니다.


기억이 잘 안 나는 분들이 있을 것이기 때문에 기본적인 뉴턴 운동법칙에 대해서 복습해 보도록 하겠습니다. 뉴턴 운동법칙은 크게 3 가지 법칙으로 나뉩니다.

  • 1법칙 : 관성의 법칙.
  • 2법칙 : 가속도의 법칙.
  • 3법칙 : 작용과 반작용의 법칙.


우리가 주목할 것은 가속도의 법칙입니다. 그 정의는 다음과 같습니다.


물체의 운동량의 시간에 따른 변화율은 그 물체에 작용하는 알짜힘과 ( 크기와 방향에 있어서 ) 같다.


출처 : 뉴턴 운동 법칙, 위키피디아.


이를 수식으로 쓰면 다음과 같습니다. 여기에서 F 는 알짜힘, P( = mv ) 는 운동량, m 은 질량, v 는 속도, a 는 가속도입니다.




일반적으로 질량이라는 것이 시간에 따라 변하지 않으므로 다음과 같이 식을 작성할 수 있습니다.




이제 우리가 어떤 물체를 움직이는 함수를 작성한다고 해 봅시다. 그러면 하나의 값을 사용해서 물체를 움직일 수 있습니다. 그것은 "힘"입니다.



UCharacterMovementComponent::AddForce() 메서드의 7 라인에서 Force 를 Mass 로 나누는 것을 볼 수 있습니다. 결국 PendingForceToApply 라는 것은 가속도를 의미하게 됩니다.


이제 실제 이 가속도는 어디에서 사용되는 것일까요? 다음과 같은 호출순서에 의해 가속도가 처리됩니다.



그림 1


3 번 호출의 내용을 보면 다음과 같습니다.



12 라인의 PendingForceToApply * DeltaSeconds 를 통해 Velocity 를 얻어 내고 있습니다. 가속도라는 것은 초당 속도의 변화량이기 때문에 DeltaSeconds 를 곱하는 것이죠. 만약 가속도가 100 이라면 1 초 후에는 속도가 100 이 증가되기를 원한다는 의미가 됩니다. 그러므로 0.1 초 동안 증가한 속도는 10 입니다( 이 설명은 왜 하고 있는지 ㅡㅡ;; ).


어쨌든 이런 식으로 캐릭터의 이동을 처리하게 됩니다. 보통 입력시스템과 관련한 플루프린트에서는 AddForce() 를 호출하게 됩니다.


본 주제로 돌아 가서 1초 동안 1 미터를 이동하려면 어떻게 해야 할까요? 이 경우에는 복잡한 계산이 필요합니다. 보통 캐릭터 입력은 등가속도 운동과 등속도 운동이 모두 포함되기 때문에 단순하게 계산하기 어렵습니다( 미적분이 필요합니다 ). 그러므로 이는 기획자가 사용하기에는 적절하지 않습니다. 프로그래머나 물리를 좀 아는 기획자가 미리 테이블을 만들어서 전달해 줄 필요가 있습니다. 아니면 기획자가 감으로 해 보는 수밖에 없습니다.


여기에서는 설명을 단순화하기 위해서 마치 속도가 한 메서드 내부에서만 계산되는 것처럼 설명했습니다. 하지만 실제로는 매우 많은 메서드 내에서 이 속도를 제어하게 됩니다. 앞에서도 언급했듯이 많은 다양한 시스템들의 영향을 받게 됩니다.


UpdatedComponent



CharaterMovement 컴포넌트가 실제로 제어하는 것은 어떤 요소일까요?


Character 의 경우에는 자신의 RootComponent 를 CharacterMovement 컴포넌트가 제어해야 하는 요소로 지정하고 있습니다.



위의 생성자의 13 라인을 보면 UpdatedComponent 에 CapsuleComponent 를 할당하고 있는 것을 볼 수 있습니다. 즉 CharacterMovement 컴포넌트는 루트 컴포넌트를 제어하게 된다는 의미입니다.


이제 CharacterMovement 컴포넌트 내에서는 UpdatedComponent 에 접근해서 이런 저런 일들을 수행하게 됩니다. 예를 들어 다음과 같은 메서드가 있습니다( UCharacterMovementComponent 는 궁극적으로 UMovementComponent 를 상속하고 있습니다 ).



PerformMovement



캐릭터의 최종 위치를 결정하는 핵심 메서드는 UCharacterMovementComponent::PerformMovement() 입니다. 여기에서는 여러 가지 입력을 평가해서 UpdatedComponent 에 그 결과를 반영합니다.


그림 2


전체적으로 크게 이동 처리와 회전 처리로 구분됩니다. 그림 2 의 3, 4, 5, 6 은 상황에 맞게 여러 가지 입력으로부터 속도를 계산합니다. 그리고 7 은 현재 상황( walking, jumping )에 맞게 이동 계산을 수행합니다. 그리고 8, 9 에서는 회전 계산을 수행합니다. 그리고 나서 10, 11 에서 이벤트를 처리합니다.


StartNewPhysics() 에서는 MovementMode 에 따라 다음과 같이 호출을 분기시켜 줍니다.


 MovementMode

 관련 Method

 비고

 MOVE_None

 

 

 MOVE_Walking

 PhysWalking()

 

 MOVE_NavWaling

 PhysNavWalking()

 

 MOVE_Falling

 PhysFalling()

 Jump 및 Launch 에 의해 실행됨

 MOVE_Flying

 PhysFlying()

 

 MOVE_Swimming

 PhysSwimming()

 

 MOVE_Custom

 PhysCustom()

 


RootMotion



RootMotion 과 CharacterMovement 컴포넌트의 관계를 잘 이해할 필요가 있습니다.


RootMotion 이라는 것은 애니메이션의 Root 본을 기준으로 캐릭터를 제어하는 모드를 의미합니다. 자세한 내용은 언리얼의 [ Root Motion ] 을 참고하시면 됩니다( 원래는 한국어 번역도 있었던 것 같은데, 지금은 한국어만 안 되네요 ).


RootMotion 모드일 때 캐릭터의 위치을 애니메이션 기준으로 제어해야 하기 때문에, 그림 2 의 5, 6 을 통해서 속도를 계산합니다. 


SkeletalMesh 의 pose 는 USkeletalMeshComponent::TickPose() 에 의해 결정됩니다. 그런데 일반적으로는 이것이 USkinnedMeshComponent::TickComponent() 에 의해 호출되지만, RootMotion 일 때는 UCharacterMovementComponent::TickComponent() 에 의해 호출된다는 차이가 있습니다. 이는 원하는 시점에 정확하게 애니메이션을 시뮬레이션하기 위함이 아닌가 싶습니다.


RootMotion 의 경우에 5, 6 을 처리한다는 것은, 캐릭터에 대한 입력이 애니메이션인 경우라고 생각하시면 될 것 같습니다.


AI



아래 블루프린트는 캐릭터를 초당 한 번씩 회전시키려는 의도로 만들어졌습니다.



만약 AIController 를 사용하는 것이 아니라 일반 PlayerController 를 사용하고 있다면 위의 코드는 정상적으로 동작합니다. 그런데 AIController 만 붙이면 해당 호출이 무시됩니다.


그 이유를 구현 측면에서 좀더 파 보자면, AAIController::UpdateControlRotation() 에서 AAIController::SetControlRotation() 를 호출하고 있기 때문입니다. 그래서 블루프린트 내의 호출이 무시되는 것입니다.



AIController 는 FocalPoint 나 FocusActor 로부터 회전을 산출하도록 설계되어 있기 때문에, 이런 문제가 발생하는 것입니다. 결국 사용자가 각 컨트롤러의 구현 방식을 이해하지 못했다면 CharacterMovement 컴포넌트의 제어에 어려움이 발생합니다.


결론



CharacterMovementController 는 기본적으로 속도를 계산해 UpdateComponent 에 반영해 주는 일을 수행합니다. 그 과정에서 여러 요소들이 간섭을 하게 됩니다.


이 문서는 그러한 모든 요소들에 대해 설명하고 있지는 않습니다. 하지만 캐릭터 이동에 대해 제대로 이해하기 위해서는 이 컴포넌트와 다른 요소들이 어떻게 상호작용하는지 이해할 필요가 있습니다.


이 문서를 통해 캐릭터 이동이나 회전이 우리의 의도와는 다르게 동작할 수 있다는 점을 이해하고 그러한 경우에 어떻게 대처해야 하는지를 이해할 수 있다면, 이 문서를 작성한 의도의 대부분은 달성한 것이 아닐까 싶습니다.


시간이 되면( ? ), 다음에는 CharacterMovement 컴포넌트가 다른 요소들과 어떤 식으로 상호작용하는지에 대한 내용을 다뤄 볼 계획입니다.

개요



언리얼 엔진을 커스터마이징하거나 플러그인같은 것들을 제작하기 위해, 가장 먼저 이해해야 할 것은 언리얼의 빌드 시스템입니다.


물론 언리얼 엔진의 공식 문서에는 [ 언리얼 빌드 시스템 ] 이라는 항목이 존재하지만, 각 주제들이 개별적으로 설명되고 있기 때문에, 처음 접하는 입장에서는 종합적으로 이해하기 어렵습니다.


저도 이런 부분때문에 고생을 많이 했고, 이번 기회에 제대로 정리를 해 볼 생각입니다. 모쪼록 이 문서가 많은 사람들에게 도움이 되었으면 합니다.


이 문서에서는 빌드시스템의 각 항목에 대한 세부적인 내용을 다루기 보다는, 전체적인 윤곽을 보여 주고 문제를 해결하기 위해서 어떤 문서를 찾아야 하는지에 대한 가이드를 제공하려고 합니다.


여러분이 이 문서를 읽고 나면, 다음과 같은 것들을 할 수 있어야 합니다. 만약 그렇지 못하다면 제가 글을 잘 못쓰거나 누락한 부분이 있는 거겠죠.


  • 커스텀 빌드를 배포하기 위해서 소스 제어에 어떠한 것들을 등록해야 하는지 알 수 있습니다.
  • GenerateProjectFiles.bat 를 어떠한 상황에서 사용하는지 알 수 있습니다.
  • 언리얼 엔진의 프로젝트 구성에 대해 이해할 수 있습니다.
  • 모듈의 의미를 이해하고 사용할 수 있습니다.
  • 언리얼 엔진을 수정하고 배포할 수 있습니다.


이 문서는 "UnrealEngine 4.10.0-release" 와 윈도우즈 시스템을 기반으로 작성되었습니다.


참고로 이 문서에서는 자동화와 배포( automation & depolyment )에 대해서는 다루지 않습니다. 이건 빌드시스템과는 좀 다른 영역이라 나중에 기회가 되면 다루도록 하겠습니다.


공부하면서 정리한 거라서 잘못된 내용이 있을 수 있으므로, 이를 감안하고 보시기 바랍니다. 잘못된 내용들은 수정해 가도록 하겠습니다.


1. 소스 제어 - 기본 데이터 등록



언리얼 엔진의 소스를 받는 방법은 [ 언리얼 엔진 소스 코드 내려받기 ]에 나와 있습니다. 그런데 언리얼 엔진의 소스를 받고 나면 어떠한 것들을 소스 컨트롤에 등록해야 하는지 알기 어렵습니다. 어떤 파일들이 공유되어야 하고 어떤 파일들이 개인용 파일들인지 모르기 때문입니다. 뭘 올려야 하는지에 대한 설명도 제대로 없습니다.


언리얼 엔진의 소스를 받으면 다음과 같은 파일들이 생성됩니다.각각의 항목에 대한 자세한 내용은 [ 디렉토리 구조 ] 문서에 잘 나와 있습니다.



일단 소스를 받은 상황에서 아무 것도 하지 마시고, 1 번 항목을 제외하고는 통째로 소스 제어에 등록하시면 됩니다. 저는 git 을 안 쓰기 때문에 1번 항목을 지워버렸습니다.


2. Setup.bat



이제 Setup.bat 를 실행해서 의존성 파일들을 설치합니다. 1 번을 통해 소스 제어에 등록된 파일들을 다운로드받은 ( 프로그래머가 아닌 ) 작업자들도 반드시 이를 실행해야만 합니다.


나중에 자동화와 배포에 대해서 정리할 기회가 있으면 그 때 다시 언급하겠지만, 만약 작업자 머신에서 이 배치파일이 실행된 적이 없다면, 프로그래머가 열심히 바이너리를 배포해 놓아도 에디터에서 안드로이드같은 디바이스로 프로그램을 런칭시킬 수 없습니다( 물론 "Engine/Extras/AndroidWorks/Win64/AndroidWorks-1R1-windows.exe" 도 깔려 있어야겠죠. 여기에서는 자동화와 배포에 대한 주제를 다루지 않기 때문에 구체적인 언급은 피하겠습니다 ).


3. 소스 제어 - 의존성 파일 등록



이 과정을 통해서 생성된 파일들을 소스 제어에 등록하는 것은 취향( ? )의 문제로 보입니다. 만약 언리얼 빌드 시스템에 손을 댈 계획이라면 소스제어에 등록해야 하지만, 그렇지 않다면 등록할 필요가 없습니다.


저같은 경우에는, 언리얼 빌드 시스템에 손을 댈 계획이 없지만, 그래도 두 가지 이유 때문에 의존성 파일들을 소스 제어에 등록해 둡니다.

  • Setup.bat 는 의존성 파일들을 먼저 검색하고 없는 파일만 추가적으로 다운로드합니다. 일반적으로 외부에서 데이터를 받는 것보다는 내부에서 받는 것이 빠르기 때문에 미리 올려 놓는 것이 좋습니다. Setup.bat 를 실행했을 때 다운로드받는 양은 적어지고 필요한 작업만 수행합니다.
  • 나중에 엔진을 빌드하고 나면 의존성 파일들이 있는 디렉토리와 엔진 바이너리 파일들이 있는 디렉토리가 동일하기 때문에, 파일을 개별적으로 관리하기가 귀찮아집니다. 그냥 통째로 "Engine/Binaries" 폴더를 등록하는 것이 편합니다( 의존성 파일들이 해당 디렉토리에만 있는 의미는 아니니 헷갈리지 않길 바랍니다 ).


참고로 다른 자리에서 Setup.exe 를 실행하면 콘솔 프롬프트에 일부 파일을 덮어 쓸 것이냐는 질문을 하는데, 이 때 No 를 선택하시면 됩니다.


만약 의존성 파일을 등록해야겠다고 판단하셨다면, 새롭게 추가된 파일들을 모두 등록하시면 됩니다( 여기까지는 그냥 엔진 루트 디렉토리를 통째로 등록하면 됩니다 ).


4. GenerateProjectFiles.bat



이제 프로젝트 관련 파일들을 생성할 차례입니다. 이 파일은 프로그래머만 실행하시면 됩니다. 이 파일을 실행하게 되면 언리얼 엔진 소스 폴더를 열심히 돌면서 자동으로 프로젝트 파일을 생성합니다. 이와 관련한 도움말은 [ 자동 프로젝트 파일 생성 ] 이 있습니다.


요즘은 멀티 플랫폼에서 개발하는 시대이기 때문에, 특정 버전의 프로젝트 파일을 제공하는 것은 불합리합니다. 물론 make 파일을 사용하면 되지만, 이를 구성별로 사용자가 따로 관리하는 것은 어렵습니다. 그래서 언리얼은 이러한 과정을 모두 자동화했습니다. 안 그랬으면 UE42013.sln, UE42015.sln 같은 파일들을 만들어서 배포해야겠죠. 좀 더 나가면 UE4Editor2015.sln 같은 솔루션을 배포해야 할 수도 있었습니다.


제가 이전에 프로젝트를 진행하면서 그런 짓을 좀 해 봤는데, 정말 할 짓이 아닙니다. 파일 하나를 추가하면 솔루션이나 프로젝트를 열어서 일일이 추가해 줘야 하고, 삭제나 이동도 마찬가지입니다.


언리얼은 이 과정을 자동화시켜 사용하기 편하게 만들었습니다. 어떤 파일이 추가/삭제/이동되면 그냥 GenerateProjectFiles.bat 을 한 번 실행해 주기만 하면 됩니다. 프로젝트 관련 파일들은 Intermediate 디렉토리에 생성되며, 이러한 것들은 소스 제어에 등록될 필요가 없습니다. VS 에서 파일 구조를 filter 같은 것을 사용해서 관리하기 보다는, 실제 디렉토리를 중심으로 filter 가 생성되도록 하는 것이 더 합리적이고 파일을 찾기도 편합니다.


그런데 가끔 이 파일을 실행하면 아주 오랫 동안 아무 반응이 없을 때가 있습니다( 요즘 머신에서 15 초 넘어 가면 문제가 있습니다 ). 반응이 없어서 콘솔을 닫고 파일을 재실행하면 UnrealBuildTool.exe 가 사용중이라서 어쩌고 하면서 에러가 뜹니다. 이 경우에는 컴퓨터를 재시작하시고 Avast 같은 안티 바이러스 도구를 일시적으로 정지시키십시오. 엔진 폴더를 검사에서 배제하는 방법을 써 봤지만 소용이 없더군요. 그리고 나서 실행하시면 됩니다( 예전에는 디렉토리나 UnrealBuildTool.exe 파일을 배제하는 것만으로 됐던 것 같은데, 왜 그런지 모르겠네요. Windows 10 64bits 입니다 ). 추가 : Avast 에서 DeepScreen 기능을 아예 꺼버리니 제대로 동작합니다.


추가 : GenerateProjectFiles 를 실행하면 노란색으로 ucrt 경로 관련 경고가 출력될 경우가 있습니다. 이 경우에는 빌드도 제대로 안 되므로 반드시 해결해야 합니다. 그 방법은 [ UE4 빌드시에 ucrt 경로 충돌 문제 해결방법 ]에 정리해 뒀습니다.

 

이제 엔진 루트에 UE4.sln 이라는 솔루션 파일이 생성되어 있는 것을 확인하실 수 있습니다. 프로젝트 관련 ( 임시 ) 파일들은 "Engine/Intermediate" 디렉토리 안에 생성됩니다. 이 디렉토리는 소스제어에 등록할 필요가 없습니다. 참고로 솔루션 파일도 재생성할 수 있으므로 소스 제어에 등록할 필요가 없습니다.


5. 엔진 빌드하기



이제 엔진을 빌드해 줄 차례입니다. 언리얼 엔진을 빌드하려고 하면서 가장 어려웠던 점은 솔루션 구성을 이해하는 것입니다. 엄청나게 많은 구성들이 존재하기 때문에 머리가 엄청나게 복잡해집니다. 이와 관련한 문서는 [ 게임 프로젝트 컴파일하기 ] 와 [ 소스에서 언리얼 엔진 빌드하기 ] 등이 있습니다.



구성은 크게 5 종류의 카테고리로 나뉩니다. 각 항목은 다음과 같은 의미를 가집니다.


 항목

 설명

 Debug

 모든 프로젝트의 바이너리가 디버깅 심볼을 가지도록 합니다.

 DebugGame

 게임 프로젝트의 바이너리만 디버깅 심볼을 가지도록 합니다.

 Development

 일반적인 프로젝트에서의 Release 환경설정과 같습니다.

 Test

 Shipping 에서 콘솔, 통계, 프로우파일링을 추가한 것입니다.

 Shipping

 최상의 성능을 가진 설정입니다.


자 이제 우리는 어떠한 구성을 빌드해서 나오는 바이너리를 사용자에게 제공해야 하는걸까요? 도움말에서는 "DevelopmentEditor + Win64 + UE4" 를 빌드할 것을 권장하고 있습니다( 빌드하는데 상당히 오랜 시간이 걸리므로, 빌드를 걸어 놓고 그냥 딴 일을 하시기 바랍니다 ).


빌드할 때도 "Performing full C++ include scan (no include cache file)" 이라는 로그 이후에 오랫동안 아무런 반응이 없으면, Avast 같은 안티 바이러스 도구를 일시적으로 정지시켜야 합니다.


빌드가 끝나면 에디터를 실행하는 데 전혀 문제가 없습니다( 커스텀 혹은 인하우스 빌드는 런처를 통해서 실행할 수 없습니다, 엔진 루트의 "Engine/Binaries/Win64/UE4Editor.exe" 를 실행해야 합니다 ). 원래 에디터를 처음 실행할 때는 45% 정도에서 오래 멈춰 있으니 성급하게 끄지 말아 주세요.


그런데 에디터를 잘 가지고 놀다가 플레이( Play In Editor, PIE )를 하는 것은 상관없는데, 실행( Launch )를 하면 에러가 발생합니다.



이런 류의 에러는 앞으로 안드로이드용 개발을 하다가도 자주 만나게 되는 에러입니다. 


XXXEditor 구성을 사용하면, "UE4Editor-$(Platform)-$(Configuration).exe" 라는 바이너리를 생성합니다. 하지만 DevelopmentEditor 구성에서는 그냥 "UE4Editor.exe" 를 생성합니다.


그리고 Debug, DebugGame, Development, Test, Shipping 은 각각의 구성에 맞게 "UE4Game-$(Platform)-$(Configuration).exe" 라는 바이너리를 생성합니다. 이것이 하나의 에디터에서 다양한 플랫폼의 실행 파일을 런칭할 수 있는 비법( ? )입니다.


DevelopmentEditor 구성으로 UE4Editor.exe 를 빌드하면 기본적으로 UE4Game-Win64-Development.exe 를 런칭합니다. 다른 구성으로 빌드된 바이너리를 런칭하기 위해서는 "실행 > 프로젝트 런처" 를 클릭합니다.



그리고 나서 나오는 다이얼로그에서 오른쪽 상단의 "고급" 버튼을 클릭합니다. 그러면 각 구성과 플랫폼별로 런칭할 수 있는 구성이 나옵니다.



만약 해당 구성으로 빌드한 적이 없다면 또 "UE4Game 바이너리가 없습니다" 라는 에러 메시지를 만나게 되겠죠.


어쨌든 정리하자면 그냥 개발하는 동안에는 "DevelopmentEditor + Win64 + UE4" 와 "Development + Win64 + UE4" 구성만 빌드하면 됩니다. 만약 에디터에서 안드로이드 플랫폼에 프로젝트를 런칭하기를 원한다면 "Development + Andoroid + UE4" 를 빌드하면 됩니다.


프로젝트 런처를 통해서 실행한 프로그램을 프로세스에 연결해서 디버깅해 보고 싶다면 "Debug + Win64 + UE4" 같은 구성도 미리 빌드해 놓는 것도 좋겠죠. 하지만 이런 구성은 다른 개발자들에게 배포할 필요는 없으므로 소스 제어에는 등록할 필요가 없을 것 같습니다.


6. 소스 제어 - 바이너리 등록



"DevelopmentEditor + Win64 + UE4", "Development + Win64 + UE4", "Development + Android + UE4" 를 빌드했다면, "Engine/Binaries" 디렉토리를 통째로 소스 제어에 등록하시면 됩니다. 물론 3 번에서 의존성 파일을 등록하지 않았다면 그것을 배제하는 작업을 좀 해야겠죠. 추가 : 플러그인들도 빌드되기 때문에 Plugins 디렉토리도 같이 등록해야 합니다. 이 때 각 하위 디렉토리의 Intermediate 디렉토리에 있는 것들은 등록에서 배제해야 합니다.


추가적으로 다른 구성을 등록해야겠다고 생각하시면 그렇게 하셔도 상관은 없습니다. 아래와 같이 플랫폼별로 디렉토리가 세분화되어 있기 때문에 관리하기가 어렵지는 않을 겁니다.



7. 모듈 이해하기



언리얼에서 CPP 프로그래밍을 하다가 보면 모듈( Module )이라는 용어가 자주 등장합니다. 이와 관련해서는 [ 언리얼 아키텍처 ], [ 모듈 API 지정자 ], [ 게임플레이 모듈 ] 등의 도움말이 있지만, 그리 친절하지는 않습니다.

 

제가 대충 정리한 [ 언리얼 엔진 모듈 ] 항목을 참조하십시오. 해당 문서는 새로운 정보를 습득할 때마다 계속 업데이트 됩니다.

이러한 모듈 개념을 잘 이해하고 있어야, 나중에 다른 모듈을 사용하는데 어려움을 겪지 않게 됩니다.


8. CPP 프로젝트 만들기



이제 CPP 프로젝트를 하나 만들어 보겠습니다. 



자 이제 솔루션에는 BlankCpp 라는 프로젝트가 포함됩니다. 어떻게 포함되었을까요? 당연히 내부적으로 GenerateProjectFiles.bat 을 실행시켜서 포함시킨 것입니다


제가 언리얼을 처음 만질 때, 가장 두려웠던 것이 GenerateProjectFiles.bat 을 실행하는 것이었습니다. 혹시 뭔가 데이터가 날라가지 않을까라는 의심이 들었는데, 디렉토리에 파일이 남아 있으면 제대로 프로젝트가 생성되기 때문에 걱정하지 않으셔도 됩니다.


어쨌든 솔루션 익스플로러를 확인해 봅시다.



역시 여기에도 BlankCpp.Build.cs 라는 녀석이 생성되어 있는 것을 볼 수 있습니다. 즉 BlankCpp 모듈인 것입니다.


우리는 UE4 를 이미 빌드했기 때문에, BlankCpp 만 빌드하면 됩니다.



9. 소스제어 - CPP 프로젝트 데이터 등록



BlankCpp 프로젝트를 빌드했으면 이제 소스 제어에 올릴 차례입니다. 프로젝트 관련 파일들은 재생성하면 된다고 했기 때문에 올릴 필요가 없습니다. 그래서 아래 그림과 같이 선택된 디렉토리와 파일만 등록하면 됩니다.


그런데 다른 자리에서는 어떻게 프로젝트 파일을 생성하냐구요? "GenerateProjectFiles.bat BlankCpp.uproject -Game" 이라고 콘솔 창에서 입력하시면 됩니다. 아니면 uproject 를 열어서 에디터에서 다음과 같은 메뉴를 사용하셔도 됩니다.



또는 BlankCpp.uproject 파일의 컨텍스트 메뉴에서 생성할 수 있습니다.



어쨌든 전부다 본질은 GenerateProjectFiles.bat 에 있습니다.



10. 결론



언리얼 엔진의 빌드 시스템에 대해서 이해하면 개발이 편해지지만, 그렇지 못하면 지옥을 겪게 됩니다.


아직 제가 모르는 부분도 많고 자세히 다루지 않은 부분도 있습니다. 단지 이 문서에서는 가이드만 제시하는 것이기 때문에 다른 문서들을 참조하면서 해 보시기 바랍니다.


이 문서에서는 모듈에 대해서만 잠깐 다루고 타깃에 대해서는 다루지 않았는데, "*.Build.cs" 와 마찬가지로 "*.Target.cs" 라는 파일들이 존재합니다. 이건 dll 단위로 존재하게 됩니다. 자세한 내용은 [ 언리얼 빌드 시스템 타겟 파일 ] 도움말에서 찾아 보시기 바랍니다.


추가 : setup.bat 실행 후에 AndroidWorks 를 설치하셨다면, setup.bat 를 다시 실행해 주셔야 합니다.

 

추가 : 혹시 엔진을 커스터마이징해서 배포하는 팀( 혹은 사람 )과 엔진을 받아서 사용하는 팀이 구분되어 있고 서로의 영역을 건드리지 않는다면, [ 빌드 그래프 ] 와 [ Installed Build 참고서 ] 를 참고해서 배포하시기 바랍니다. 그냥 빌드해서 서밋하시면 코드가 변경되었기 때문에 엔진 코드를 수정하면 게임 칸텐츠를 작업하는 측에서 엔진까지 빌드해야 하는 경우가 생깁니다.

언리얼 엔진의 [ 데이터 주도형 게임플레이 요소 ] 항목을 따라 하다가 보면 문제가 발생합니다. 


UE4 에서는 UTF-8 포맷으로 CSV 를 읽고 있기 때문에, 일반적인 방식으로 XLSX 에서 CSV 를 뽑게 되면 한글같은 것은 다 깨져서 나옵니다.


이를 해결하기 위한 일반적인 방법은 다음과 같습니다.


  1. XLS 를 편집.
  2. CSV 로 저장( 이 과정에서 메시지 박스가 자꾸 떠서 불편함 ).
  3. CSV 를 메모장에서 열어 UTF-8 포맷으로 재저장.
  4. 다시 편집할 때는 1 ~ 3 을 반복.


이런 과정은 너무 불편한 데다가 실수로 CSV 만 손대면 XLS 와 내용이 틀려져서 관리가 어렵습니다.


그래서 이를 자동화하는 방법을 고민했고, 이런 저런 자료들을 참고하여 매크로를 만들었습니다: XLSM( 매크로 XLS )사용해서 CSV 를 익스포트할 수 있도록 했습니다.


앞으로 UE4 에서 사용할 엑셀 원본 파일은 아래의 파일을 기반으로 만들면 됩니다.


XlsToCsv.xlsm


여기에서 사용하는 VBAScript 는 다음과 같습니다.



그런데 "Export" 라는 sheet 에만 연결을 해 두었기 때문에, 다른 sheet 에서 작업을 하려면 따로 스크립트를 연결해야 합니다.




무기를 위해서 UStaticMeshComponent 를 상속하는 UStaticWeaponComponent 를 만들었다고 가정합시다.



다음으로 나서 내가 정의한 커스텀 캐릭터의 멤버로 넣습니다.



다음으로 생성자에서 Weapon 컴포넌트를 생성해 줍니다.



그리고 나서 ACustomCharacter 를 가지고 플루프린트 클래스를 만듭니다. 그 다음 컴포넌트 리스트에서 Weapon 컴포넌트를 선택하면 Details 패널에 다음과 같이 나옵니다.


그림1. EditAnywhere 를 지정했을 때 Details 패널.


뭔가 평소에 보던 것과는 다릅니다. 이는 EditAnywhere 라는 속성 지정자 때문입니다. 보통 VisibleAnywhere 와 EditAnywhere 를 사용하는데, 이는 서로 배타적입니다. 만약 이것을 VisibleAnywhere 로 변경한다면 다음과 같은 Details 패널을 볼 수 있습니다.


그림2. VisibleAnywhere 를 지정했을 때 Details 패널.


그렇다면 EditAnywhere 와 VisibleAnywhere 의 차이는 무엇일까요? 언리얼 엔진 공식 문서에서는 다음과 같이 설명합니다.


EditAnywhere


이 속성이 아키타입( archetype )이나 인스턴스의 속성창에서 편집될 수 있음을 의미합니다.


출처 : https://docs.unrealengine.com/latest/INT/Programming/UnrealArchitecture/Reference/Properties/Specifiers/EditAnywhere/index.html


VisibleAnywhere


이 속성이 속성창에서 보이지만, 편집할 수 없음을 의미합니다. 이 연산은 Edit* 지정자들과 호환되지 않습니다.


출처 : https://docs.unrealengine.com/latest/INT/Programming/UnrealArchitecture/Reference/Properties/Specifiers/VisibleAnywhere/index.html


일단 편집이 된다와 안 된다가 가장 큰 차이인 것 같지만, 실제로는 둘 다 편집이 가능합니다. 설명이 참 이상한 것 같습니다.


추가 : 언리얼 까페의 나가놀자 님에 따르면 두 옵션의 차이는 다음과 같다고 합니다.


Edit은 포인터 자체를 수정하겠다는 의미가 됩니다.

반면 Visible은 포인터의 멤버를 노출하겠다는 의미가 됩니다.


출처: http://cafe.naver.com/unrealenginekr/7259


어쨌든 UI 상에 있어 핵심적인 차이는 아키타입이나 인스턴스 속성창에서 편집될 수 있다는 것입니다. 언리얼에서 아키타입은 다른 엔진들의 프리팹 정도라고 생각하시면 될 것 같습니다. 어쨌든 중요한 것은 인스턴스별로 편집이 가능하다는 것입니다.


예를 들어 EditAnywhere 를 지정하고 만든 클래스의 인스턴스를 생성하면 그 인스턴스의 Details 패널에서 다음과 같은 항목을 볼 수 있습니다. VisibleAnywhere 를 지정하면 나오지 않습니다.



그림3. EditAnywhere 를 지정했을 때 인스턴스의 Details 패널.


정리하자면, EditAnywhere 는 속성을 인스턴스별 속성으로 만들 때 필요하며, VisibleAnywhere 는 속성을 블루프린트 자체의 속성으로만 사용할 때 필요합니다. EditAnywhere 를 지정했을 때 그림1 처럼 접혀 있는 이유는 인스턴스의 Details 패널이 너무 위아래로 길어지지 않도록 하기 위함으로 보입니다( 그래도 저같으면 블루프린트 안에서는 펼쳐져 있도록 구현했을 것 같은데요 ㅡㅡ;; ).


인스턴스별 속성이 뭐냐고 질문하는 분도 계실 겁니다. 그냥 인스턴스별로 속성을 재정의( overriding )할 수 있다는 의미입니다. 만약 EditAnywhere 를 지정한 경우라면 블루프린트 클래스를 여러 개 만들지 않고 하나만 만든 다음에 씬에 배치한 후에 속성을 편집할 수 있겠죠. 만약 VisibleAnywhere 를 지정한 경우라면 생성할 인스턴스 개수마다 블루프린트 클래스를 만들어야만 합니다.


물론 프로그래밍이나 스크립팅을 통해서 이를 제어한다면 상관없지만, 아티스트나 디자이너가 뭔가 하나씩 배치하고 속성을 바꾸기를 원한다면 EditAnywhere 를 사용하는 것을 고려해야 합니다.

언리얼 엔진을 커스터마이징하다가 보면 액터의 컴포넌트를 직접 정의해야 하는 경우가 있습니다. 예를 들어 무기 컴포넌트를 만든다고 합시다.


이 경우에 스태틱 메쉬 애셋을 사용할 것이므로 UStaticMeshComonent 를 상속하는 UStaticWeaponComponent 를 생성합니다. 혹시 채찍같은 것을 만들면 USkeletalMeshComponent 를 상속하는 USkeletalWeaponComonent 를 만들어야겠죠.



그런데 이러한 컴포넌트를 만들고 나서 액터에 추가하려고 하면 "Add Component" 드롭다운 메뉴에 내가 만든 컴포넌트가 리스팅되지 않습니다.




이는 UStaticWeaponComponent 을 생성할 수 있도록 지정하지 않았기 때문입니다. 아래와 같이 클래스 지정자를 설정하면 됩니다. 



이제 "Static Weapon" 항목이 추가되어 있는 것을 확인할 수 있습니다. 참고로 ClassGroup 이라는 것은 이 컴포넌트의 카테고리를 의미합니다.




언리얼 엔진을 빌드할 때 "Ctrl + Break" 를 누르면 그 다음부터 빌드가 진행되지 않는 경우가 있습니다. 


이 때 에디터나 VS 를 다시 닫았다가 열어도 빌드를 진행하지 못하고 아무리 기다려도 소용이 없습니다. 심지어 몇 시간을 기다려도 빌드는 끝나지 않습니다. 실제로 졸려서 자고 왔는데도 그 상태더군요.


이것의 원인은 UnrealBuildTool.exe 프로세스입니다. 이 상태가 되면 빌드를 누를 때마다 프로세스가 계속 누적이 됩니다.


그냥 UnrealBuildTool.exe 프로세스를 종료시켜 주시면 됩니다. 만약 종료가 안 된다면 VS 를 끄고 종료시키면 됩니다.


저같은 경우에는 작업관리자를 열었다가 닫는 것이 귀찮아서 바탕화면에 "KillUEBuildProc.bat" 파일을 만들어 놓고 종료시킵니다.



+ Recent posts