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

주의 : BSP 알고리즘에 대해 구체적으로 설명을 하지 않습니다. 어느 정도 개념을 알고 있다고 가정합니다.



UE4 Brush, BSP, PhysX Convex 에 대해


BSP( Binary Space Partitioning ) 는 둠라는 게임에서 사용된 이후로 매우 유명해졌습니다. 말 그대로 씬을 두 개의 노드로 분할한다는 의미로 이와 관련된 자료는 인터넷에 많이 있으니 검색해 보시기 바랍니다. 이 문서에서는 UE4 에서 어떤 방식으로 BSP 를 이용하는지에 대해서 중점적으로 다룹니다.


기존의 1인칭 슈팅 게임에서는 한정된 자원으로 게임을 만들어야 했기 때문에 메쉬의 사용을 최소화하고 빠르게 위치 및 충돌을 검출하기 위해서 BSP 를 많이 사용했습니다. 특히 얼리얼에서는 CSG( Constructive Solid Geometry ) 라는 개념을 통해 씬을 쉽게 구성할 수 있는 도구를 제공했습니다.


하지만 이제는 예전과는 다르게 1인칭 슈팅 게임들도 복잡한 지오메트리를 사용하게 되고 그에 걸맞게 하드웨어 성능과 공간분할 알고리즘들이 발전하게 되면서 BSP 를 사용하지 않는 추세입니다. 그러나 언리얼에서 여전히 BSP 를 중요하게 사용하는 부분이 있습니다. 바로 Geometry 라 표현되는 개체입니다.


게임을 만들어 보신 분이라면 한 번쯤은 "Trigger Volume" 이라는 액터를 배치해 보신적이 있을 것입니다. 이 트리거 볼륨 액터는 사용자가 어떤 영역에 들어 오고 나가는 것을 알려줍니다.


그림1


하지만 저 계단 위쪽에 들어 왔는지 나갔는지를 판단하려면 매우 복잡한 모양의 트리거 볼륨이 필요합니다. 왼쪽에 있는 "Modes" 툴바의 "Geometry Editing" 항목을 선택하면 이것을 복잡하게 편집할 수 있습니다. "Extrude" 를 기능을 사용해서 멋지게 편집해 봤습니다. 절대 여러 개의 볼륨을 사용한 것이 아닙니다. 하나만으로 만들었습니다.



그림2


이 경우에도 저 볼륨의 영역에 캐릭터가 들어갔다 나갔다 하면 앞에서와 같은 이벤트가 발생하게 됩니다. 


예전에는 BSP 노드에 대한 충돌검사를 수행했습니다. 하지만 UE4 에서는 PhysicsX 를 도입하면서 BSP 노드를 convex 집합으로 바꾸게 됩니다. 이것을 언리얼에서는 "Aggregate Geometry" 라 부르게 됩니다. 



언리얼에서 Volume 은 AVolume 이라는 개체로 표현되며, 이는 ABrush 를 상속합니다.



그림 3


    • ABrush 는 기존의 UE3 의 CSG 브러쉬를 대체합니다. "Placement Mode" 에서 "Geometry" 카테고리에 포함됩니다. 여러 개의 액터가 존재하는데 이것은 브러쉬 빌더의 타입을 보여 줍니다. FBspModeModules::StartupModule() 메서드에서는 "Geometry" 카테고리에 이 빌더들을 등록하고 있습니다. 관심이 있으신 분들은 찾아 보시기 바랍니다.
    • AVolume 은 ABrush 중에서 볼륨 개체를 추상화하기 위해서 사용됩니다. 기본적으로는 ABrush 의 기능과 동일합니다.
    • ABrushShape 은 geometry editing mode 에서 "Lathe" 를 위해 사용된다고 하는데 뭔지 잘 모르겠습니다.


그림 4


어쨌든 ABrush 가 여기에서 다루고자 하는 핵심입니다. ABrush 에서는 폴리곤 데이터를 UModel 개체에 물리 및 렌더링 데이터를 UBrushComponent 를 개체에 저장합니다.


이 문서에서 이야기하고자 하는 핵심 클래스들은 다음과 같습니다. 물론 훨씬 더 많은 자료구조를 포함하고 있지만 주제와 밀접한 클래스들만 정리했습니다.


그림 5


사설이 길었는데요 이제 조금 구체적으로 파 보도록 하겠습니다.


Brush : Polygons & BSP Nodes



언리얼에서는 브러쉬는 어떤 메쉬를 생성하기 위한 틀을 의미합니다. 액터로서는 ABrush 로 표현되지만, 실제 폴리곤( 혹은 지오메트리 ) 데이터는 UModel 개체에 포함되어 있습니다. UModel* ABrush::Brush 라고 정의되어 있습니다.


우리가 에디터에서 geometry editing mode 를 켜 놓고 편집을 하게 되면 실제로는 UModel 개체를 수정하게 되는 것입니다. 이것은 브러쉬의 지오메트리를 표현하기 위한 많은 정보들을 포함합니다. 그 중의 핵심이라고 할 수 있는 것은 폴리곤 데이터( FPoly )와 BSP 노드 데이터( FBspNode )입니다.


폴리곤 데이터는 하나의 면( FPlane )을 표현합니다. 물론 범위가 정해져 있으니 점 데이터도 포함하게 됩니다. 예를 들어 큐브는 6 개의 폴리곤과 24 개( 각 노드당 4 개 )의 점으로 구성됩니다. 점을 공유하고 있지는 않더군요. 그런데 점을 하나 수직으로 움직이게 되면 면 하나가 두 개의 삼각형으로 전환됩니다. 이 경우에는 7 개의 폴리곤과 26 개의 점으로 구성됩니다; 5 * 4 + 3 * 2 = 26.


그림 6


여기서 퀴즈입니다. 그림 2 의 폴리곤은 몇 개일까요? 볼륨을 둘러 싸는 가장 바깥쪽 면 개수만 세야 합니다. 답은 댓글에...


편집을 통해 폴리곤 정보가 변경되면 FBSPOps::bspBuild() 라는 메서드가 호출됩니다. 


UE4 에서 BSP 를 구성하는 데 있어 핵심이 되는 파라미터는 "Balance" 입니다. 이는 균형잡힌 이진트리를 만드느냐 아니면 가장 분할( 다른 폴리곤을 자르는 것 )이 적은 이진트리를 만드냐를 결정하게 됩니다. 그것의 pseudo code 는 다음과 같습니다.



여기에서 관계라는 것은 front, coplanar, back, split 입니다. 각각의 카운트를 세는거죠. 그리고 점수는 다음과 같이 계산됩니다.



이 Balance 값은 현재 15 로 하드코딩되어 있습니다( 수정 : 코드 검색하다가 보니 0, 15, 70 인 경우가 있었습니다 ). 예전에는 수치를 받았던거 같은데 UE4 로 오면서 경험적으로 좋은 수치를 설정한 것 같습니다. 


내용을 곱씹어 보면 다른 폴리곤을 많이 쪼갤수록 그리고 Front 와 Back 의 개수 차이가 클수록 점수가 높아집니다. 그런데 Lerp( linear interpolation ) 처럼 Balance 값을 통해 가중치를 조절합니다.


만약 Balance 가 크다면 어떻게 될까요? 100.0 - float(Balance) 의 값이 작아지므로 Front 와 Back 의 차이가 클수록 좋은 점수를 받겠죠. 반대로 Balance 가 작다면 어떻게 될까요? 100 - float(Balance) 의 값이 커지므로 쪼개는 개수가 많아질수록 좋은 점수를 받겠죠. 헷갈리니 예를 들어 보도록 하겠습니다.


그림 7 은 3 이 splitter 로 선택되었을 때의 상황을 가정하고 그린 것입니다. 2 를 2f 와 2b 로 나누지 않은 이유에 대해서는 나중에 알게 될 것입니다. 그림 7 에서 각 폴리곤들의 노멀은 바깥쪽이라고 가정하시고 보시면 됩니다.


그림 7


 Balance

 1 ( f : 0, b : 4, s : 0 )

 2 ( f : 0, b : 4, s : 0 )

 3 ( f : 1, b : 2, s : 1 )

 4 ( f : 1, b : 2, s : 1 )

 5 ( f : 0, b : 4, s : 0 )

 Best Splitter Order

 15

 60

 60

 90

 90

 60

 1, 2, 5, 3, 4

 75

 300

 300

 90

 90

 300

 3, 4, 1, 2, 5


Balance 가 75 인 경우에는 3 이 splitter 로 사용됩니다. 3 이 가장 먼저 splitter 로 사용되며 5f, 5b 가 생성됩니다. 여기에서 의문이 발생할 것입니다. 왜 2 는 쪼개지지 않을까요. 4 가 splitter 로 사용되는 시점에는 3 을 기준으로 front-end 로 분할된 상황이기 때문에 2 는 4 가 쪼갤 수 있는 대상이 아닙니다. 4 는 front 에 있고 2 는 back 에 있습니다. 그러므로 분할은 3 을 splitter 로 사용했을 때 단 한 번만 발생합니다. 최종적으로 리스트에는 1, 2, 3, 4, 5f, 5b 가 존재하게 됩니다.


결국 Balance 가 15 인 경우와 75 인 경우에는 다음과 같은 이진 트리가 나오게 됩니다.




그림 8


그림 8 에서 보면 leaf 노드에 색이 칠해져 있는 것을 볼 수 있습니다. leaf 노드에 도달했다는 것은 면으로 이루어진 도형이 닫힌거라 생각하시면 됩니다. 


예를 들어 오른쪽 그림의 경우를 봅시다. 평면 3, 1, 2, 5b 에 대해서 어떤 점과의 위치 관계를 구했을 때 모두 backside 라고 하면 녹색 도형 안에 점이 존재한다고 할 수 있습니다. 이것을 BSP Tree Traverse 의 관점에서 보자면 back-leaf 노드에 도달한 것입니다. 


왼쪽 그림의 경우를 봅시다. 만약 front node 가 존재한다면 그쪽으로 concave 가 형성되는 구멍이 뚫려 있다고 보시면 됩니다. 만약 front-leaf 노드에 도달했을 때 폴리곤의 normal 과 점의 관계가 backside 라고 한다면 닫히게 되는 것입니다. 만약 frontside 라고 하면 열린 폴리곤이라는 의미가 됩니다.


PhysX Convex



이제 이것을 실제로 사용하기 위해서 PhysX 에 넘겨 줄 필요가 있습니다. 그런데 많은 분들이 알고 계시다시피 물리 엔진들은 concave 를 사용하지 않습니다. 사용한다고 하더라도 내부적으로 이것을 convex 집합으로 바꾸게 됩니다. 이 convex 집합이 바로 Aggregate Geometry 입니다.


BSP 노드들을 Convex 로 바꾸는 작업은 ModelToHullsWorker() 에 의해서 수행됩니다. 여기에서 하는 작업은 별거 없습니다. 그림 8 처럼 concave 를 convex 로 쪼개는 작업을 합니다.


노드를 순회하면서 폴리곤을 수집하다가 leaf 를 만나면 그때까지 수집했던 폴리곤들을 사용해 convex 를 하나 생성합니다. 그런데 여기서 알아야 할 점은 front node 를 만나면 새로운 convex 를 만들기 위해 폴리곤들을 수집하기 시작한다는 것입니다. 아까도 언급했듯이 front node 가 있다는 것은 그쪽으로 뚫려 있다는 이야기이므로 leaf 를 만날 때까지 새로운 폴리곤을 수집해야 합니다.


닫힌 convex 가 완성되면 AddConvexPrim() 이라는 메서드를 호출하게 되는데요, 여기에 위에서 언급했던 폴리곤의 리스트( 즉 plane list )를 넘겨 줍니다. 그 메서드에서는 정확성을 높이기 위해서 vertex snapping 과 같은 작업을 수행하고 FKAggregateGeom 에다가 FKConvexElem 를 추가해 줍니다. 여기까지 하면 PhysX Convex 를 생성할 준비를 마친 것입니다.


실제 PhysX Convex 는 UBodySetup::CreatePhysicsMeshes() 호출을 통해 생성됩니다.


생성된 PhysX Convex 와 Brush Helper 의 모양을 비교해서 보여줬으면 좋겠지만 제가 PhysX Mesh 를 출력할 수 있는 방법을 찾지 못해서 안타깝네요. 


PhysX Capture



PhysicsX Visual Debugger ( PVD ) 를 사용해서 씬을 캡쳐할 수 있다는 제보를 받고 시도를 해 봤습니다. 이미 API integration 은 되어 있는 상황이더군요.


UE4 에서는 PvdConnect() 라는 메서드를 통해서 이를 지원합니다. 


에디터 콘솔 명령창에서 "pvd connect""pvd disconnect" 를 사용해서 활성화/비활성화할 수 있습니다. 실제 적용되는건 play 를 눌렀을 때입니다. 그러므로 play 버튼을 누른 후에 connect 했다가 disconnect 하면 됩니다. 계속 캡쳐되고 있으므로 프레임 디버깅할 것이 아니라면 빨리 disconnect 하세요.


그런데 저는 pvd 연결하면 프로그램이 멈춰버리더군요.... 나중에 방법을 찾으면 캡쳐해서 올리도록 하겠습니다.


추가 : 결국 해결( ? )했습니다. pvd3.exe 가 너무 많이 떠 있어서 그랬던 것 같습니다. 다 지우고 하나만 띄우니 되네요.


좌표계를 UE4 와 일치하는 화면을 보시려면 "Left-Handed", "Z+" 설정하시고 Bounding box 를 "All" 로 하시면 쉽게 확인할 수 있습니다. Convex 별로 physx collision 이 할당된 것을 확인하실 수 있습니다.



개요



[ 5.3. 플레이어 입력 ] 에서 어떤 플레이어 입력이 있는지 살펴 보았습니다. 이 섹션에서는 이동과 관련한 로직 및 애니메이션에 대해서 살펴 보도록 하겠습니다.


이동( locomotion, 보행, 운동 )과 관련된 로직에 대해서 다루도록 하겠습니다. 간단하게 요약하자면 UT 의 이동 로직은 UUTCharacterMovment 컴포넌트에 의존합니다. Character component 의 처리 결과에 따라서 어떠한 애니메이션들을 블렌딩할지를 결정하게 됩니다.


1인칭 캐릭터에서는 "Content/RestrictedAssets/Universal/1stPerson/UT4_Base_1stP_AnimBP.uasset" "Main_Motion" FSM( finite state machines ) 을 사용하며, 3인칭 캐릭터에서는 "Content/RestrictedAssets/Animations/Universal/UT4_Base_AnimBP.uasset""Locomotion" FSM 과 "Landing" FSM 을 사용합니다.


일단 Character Movement 컴포넌트의 주요 역할은 물리와 애니메이션에서 사용할 자료( 변수 )를 준비하는 것입니다. 만약 Character Movement 컴포넌트가 없고 Player Controller 만 존재한다면, Player Controller 나 Character 에서 물리 및 애니메이션을 처리해야만 할 것입니다.


Character Movement 컴포넌트



UT 에서 기본 Character Movement 컴포넌트의 클래스는 UUTCharacterMovement 입니다. "DefaultCharacter" 블루 프린트를 열어서 "Components" 뷰를 보면 아래쪽에 "UTCharacterMovment(Inherit)" 라는 항목을 볼 수 있습니다. 오른쪽의 "Details" 뷰를 보면 매우 많은 속성 카테고리들이 존재함을 알 수 있습니다.



보시면 알겠지만 게임에서 사용할 만한 상황들은 거의 다 들어 가 있습니다. 물론 상황에 맞춰 FSM 을 구성하는 수고는 해야 합니다.


이동 모드 



Character Movement 컴포넌트에서 이동 모드의 전환은 FSM 으로 표현될 수 있습니다만 여기에서 다루지는 않겠습니다. 애니메이션 블루프린트마다 애니메이션 FSM 을 따로 구성하기 때문에 코드 상에서의 상태 전환에 대해서 언급하는 것은 크게 의미가 없을 것 같습니다. 애니메이션 FSM 에 대해서는 다른 문서에서 다루도록 하겠습니다. 단지 다음과 같은 이동 모드들이 있다는 것만 알아 두시면 될 것 같습니다.


    • Walking : 땅바닥에 붙어서 걸어다니는 이동 모드.
    • NavWalking : AI 등이 네비게이션 메쉬와 길찾기를 통해서 걸어다니는 모드.
    • Swimming : 물에 들어 갔을 때의 이동 모드.
    • Falling : 점프를 하거나 바닥에 닿지 않아 떨어지고 있을 때의 이동 모드.
    • Custom : 사용자의 커스텀한 이동 모드.
    • Flying : 날아다니는 이동 모드.


이제 각 이동 모드와 관련한 중요한 속성들을 살펴 보도록 하겠습니다. 대부분 어렵지 않은 속성들을 가지고 있기 때문에 이해하는데 큰 어려움은 없을 것입니다. 일단 어떤 기능들을 가지고 있는지 이해하고 있어야지 이미 존재하는 기능을 또 구현하는 불상사를 막을 수 있기 때문에, 지루하더라도 한 번씩은 읽어 보시기 바랍니다. 


제가 밥상은 차려줄 수 있지만, 떠 먹는건 알아서 하시기 바랍니다.


Character Movement




각 이동 모드의 가속도 및 감속도를 설정하는 곳입니다.


 이름

 설명

 MaxFallingAcceleration

  • Falling 시의 최대 가속도( 이는 AirControl 속성에 의해 스케일링 됨 ).

 MaxSwimmingAcceleration

  • Swimming 시의 최대 상수 가속도.

 MaxRelativeSwimmingAccelNumerator

  • Swimming 시의 부가적 가속도인데, 속도 크기에 의해 나눠짐. Swimming 가속도는 MaxSwimmingAcceleration + MaxRelativeSwimmingAccelNumerator / ( Speed + MaxRelativeSwimmingAccelDenominator ) 임.

 MaxRelativeSwimmingAccelDenomitor

  • Swimming 가속도 공식의 일부.  Swimming 가속도는 MaxSwimmingAcceleration + MaxRelativeSwimmingAccelNumerator / ( Speed + MaxRelativeSwimmingAccelDenominator ) 임.

 BrakingDecelerationSliding

  • Sliding 시의 정지 감속도.

 DefaultBrakingDecelerationWalking

  • Walking 시의 정지 감속도 - BrakingDecelerationWalking 과 같은 값으로 설정하라.

 IgnoreClientMovementErrorChecksAndCorrection

  • true 이면, 이 이동 컴포넌트 상에서 클라이언트 에러를 위한 서버 위치 차이 검사를 무시한다.
  • 이는 캐릭터가 잠시 동안 극단적인 속력으로 움직이는데 클라이언트에서 부드럽게 보이도록 만들고 싶을 때 유용하다. 사용하고 나면 비활성했는지 확인해야 한다. 왜냐하면 이것은 캐릭터의 서버-클라이언트 이동 보정을 오동작하게 만들기 때문이다.


Character Movement(General Settings)




일반적인 설정을 하는 곳입니다.


 이름

 설명

 GravityScale

  • 커스텀 중력 스케일. 캐릭터를 위한 중력에 이 값이 곱해진다.

 MaxAccelleration

  • 최대 가속도( 속도가 변하는 비율 ).

 BrakingFrictionFactor

  • 정지할 때 사용되는 실제 마찰력 값에 곱해지기 위한 요소이다.
  • 이는 현재 사용되는 모든 마찰력 값에 적용된다. UseSeperateBrakingFriction 에 의존한다.
  • @note : 이는 경험적 이유로 2 값이 기본값이다. 1 값은 실제 drag equation 이다.

 BrakingFriction

  • ( Acceleration = 0 이거나 캐릭터가 최대 속력을 초과하고 있을 때마다 ) 정지시에 적용될 마찰력 ( drag ) 상수; 실제 사용되는 값은 BrakingFrictionFactor 와 곱해진다.
  • 정지중일 때, 이 속성은 바닥을 통해 움직이고 있을 때 얼마나 많은 마찰력이 적용될지를 제어할 수 있도록 해 준다. 이는 현재 속도를 스케일링하는 반대쪽 힘을 적용하게 된다.
  • 정지는 마찰력( 속도 의존적인 drag )와 상수 감속( deceleration )으로 구성된다.
  • 이는 모든 이동 모드에서 사용되는 현재값이다; 만약 이것을 원하지 않는다면, 이동 모드가 바뀔 때 이 값이나 bUseSeperatedBrakingFriction 을 재정의하라.
  • bUseSerperatedBrakingFriction 이 true 일 때만 사용된다. 그렇지 않으면 GroundFriction 같은 현재 마찰력이 사용된다.

 UseSeperateBrakingFriction

  • 만약 true 이면, ( 가속이 없을 때 ) 캐릭터를 천천히 멈추게 하기 위해서 BrakingFriction 이 사용될 것이다.
  • 만약 false 이면, CalcVelocity() 에 넘겨지는 것과 같은 마찰력을 사용할 것이다( 예를 들어 걸어다닐 때의 GroundFriction ). 이는 BrakingFrictionFactor 와 곱해진다.
  • 이 설정은 모든 이동 모드에 적용된다; 만약 특정 모드에서 이를 원하지 않는다면, 이동 모드가 변경될 때 토글링하는 것을 고려해 보라.

 CrouchedHalfHeight

  • Crouching( 앉기 )시에 사용할 충돌 절반 높이( 컴포넌트 스케일이 각각 적용됨 ).

 RotationRate

  • 초당 회전률. UseControllerDesiredRotation 이나 OrientationToMovement 가 true 일 때 사용된다. Infinite rotation rate 와 instant trun 을 위해서는 음수값을 설정한다.

 OrientRotationToMovement

  • true 이면, 가속 방향을 향해 캐릭터를 회전시킨다. 이 때 회전률은 RotationRate 를 통해 지정된다. UsecontrollerDesiredRotation 을 덮어 쓴다.
  • 보통 캐릭터의 UseControllerRotationYaw 같은 다른 설정들이 클리어되어 있기를 기대할 것이다.

 Mass

  • 폰( pawn )의 질량( 운동량( momentum )이 주어졌을 때를 위해 ).
 DefaultLandMovementMode
  • 물 속에 있지 않을 때의 기본 이동 모드. 플레이어 스타트업이나 텔레포트시에 사용됨.
  • Walking
  • NavWalking
  • Falling,
  • Swimming
  • Flying
  • Custom
 DefaultWaterMovementMode
  • 물 속에 있을 때의 기본 이동 모드. 플레이어 스타트업이나 텔레포트시에 사용됨.

 JustTeleported

  • 위치 변화가 일반 이동에 의한 것인지 텔레포트에 의한 것인지 결정하기 위해서 이동 코드에서 사용됨. 만약 텔레포트가 아니면, 위치 변화 기반해 속도가 재계산될 수 있음.

 WantstoCrouch

  • true 이면, 다음 업데이트시에 crouch( 혹은 crouch 유지 )를 시도함. false 이면 다음 업데이트시에 crouching 종료를 시도함.
 UseControllerDesiredRotation
  • true 이면, 컨트롤러의 회전을 향해서 캐릭터를 부드럽게 회전시킨다. 이 때 회전률은 RotationRate 를 통해 지정된다. OrientRotationToMovement 에 의해 덮어 써 진다.
 EnableScopedMovementUpdates
  • If true, high-level movement updates will be wrapped in a movement scope that accumulates updates and defers a bulk of the work until the end.
  • When enbled, touch and hit events will not be triggered until the end of multiple moves within n update, which can improve performance.
  • 역주 : 이동을 누적시키는 것과 관련한 업데이트들을 한 번에 묶어서 처리하고, 나머지 업데이트들은 지연시켜서 나중에 처리하는 기능을 의미하는 듯하다.

 RunPhysicsWithNoController

  • true 이면, 캐릭터 소유자를 위한 컨트롤러가 존재하지 않더라도 이동을 실행할 것이다.
  • 일반적으로 컨트롤러가 없으면, 이동은 무시되며, 캐릭터가 걷고 있다면 속도와 가속도가 0 이 될 것이다.
  • 컨트롤러 없이 생성된 캐릭터에서 이 플래그가 활성화되면, 이동 모드를 DefultLandMovementMode 나 DefultWaterMovementMode 로 적절히 초기화할 것이다.

 MaxSimulationTimeStep

  • 각각의 분절된( discrete ) 시뮬레이션 스텝의 최대 타임 델타이다.
  • 주로 큰 타임 스텝을 쪼개는 진보된 이동 모드에서 사용된다( usually those applying gravity such as falling and walking ).
  • 이 값을 작게 하면, 빠르게 움직이는 오브젝트 시나리오나 복잡한 충돌 시나리오를 해결할 수 있는데, 성능 비용을 지불해야 한다.
  • @경고 : 만약 ( MaxSimulationTimeStep * MaxSimulationIterations ) 가 최소 프레임율보다 너무 작다면, 마지막 시뮬레이션 스텝은 시뮬레이션을 완료하기 위해서 MaxSimulationTimeStep 을 초과할 수도 있다.

 MaxSimulationIterations

  • 각각의 분절된 시뮬레이션 스텝들을 위해 사용되는 반복( iteration ) 횟수.
  • 주로 큰 타임 스텝을 쪼개는 진보된 이동 모드에서 사용된다( usually those applying gravity such as falling and walking ).
  • 이 값을 증가시키면, 빠르게 움직이는 오브젝트 시나리오나 복잡한 충돌 시나리오를 해결할 수 있는데, 성능 비용을 지불해야 한다.
 CrouchMaintainsBaseLocation
  • true 이면, crouching 은 쪼그라든 캡슐의 중심을 낮춤으로써 캡슐의 바닥을 그대로 유지한다. false 이면, 캡슐의 바닥이 위로 올라가고 중심은 그대로 남는다.
  • 같은 행위가 uncrouch 시에도 적용된다: true 이면 바닥은 같은 위치를 유지하고, 중심이 올라 간다. false 이면, 캡슐이 커지고 바닥이 무엇인가와 충돌할 때만 올라간다. 
  • 기본적으로 이 변수는 이동 모드가 변경될 때만 설정된다: walking 일 때 true 로 설정하고 그렇지 않으면 false 로 설정한다. 이동 모드가 변경될 때 이 행위를 재정의하는데 부담가질 필요가 없다.
 RequestedMoveUseAcceleration
  • path following 을 위해 가속도를 사용할지 여부를 결정한다.
  • true 이면, path following 시에 목표 속도에 도달하기 위해서 가속도를 적용한다.
  • false 이면, path following 속도가 직접 설정되며, 가속도를 고려하지 않는다.


CharacterMovement : Walking




걷기와 관련한 설정을 하는 곳입니다. 


 이름

 설명

 MaxStepHeight

  • 캐릭터가 걸어 올라 갈 수 있는 ( 계단의 ) 최대 높이. 

 WalkableFloorAngle

  • 걸어다닐 수 있는 표면의 최대 각도. 이보다 큰 각도이면 걷기에는 너무 뾰족한 것임. 

 WalkableFloorZ

  • 바닥의 법선( normal )에 대한 최소 z 값. 이것보다 크면 걸을 수 없음. WalkableFloorAngle 로부터 계산됨.

 GroundFriction

  • 이동 제어에 영향을 주는 설정. 값이 높을수록 방향이 빠르게 변함.
  • 만약 bUseSeperateBrakingFriction 이 false 라면,  ( Acceleration 이 0 일 때마다 ) 멈출 때 더 빠르게 정지하는 능력에 영향을 주기도 함. 이는 BrakingFrictionFactor 와 곱해짐.
  • 이 속성은 땅바닥 위를 이동하다가 정지할 때 마찰력이 얼마나 적용되어야 하는지를 사용자가 제어할 수 있도록 하는 속성임. 이는 현재 속도를 스케일링하는 반대방향의 힘임.
  • 이 값을 변경함으로써 이는 눈이나 기름과 같은 미끄러운 표면을 시뮬레이션하기 위해서 사용될 수도 있음( 아마도 폰이 서 있는 재질에 기반해서 설정해야 할 것임 ).

 MaxWalkSpeed

  •  걸어다닐 때의 최대 속력. 떨어지고 있을 때의 최대 측면( lateral ) 속도를 결정하기도 함.

 MaxWalkSpeedCrouched

  •  앉아서 걸어다닐 때의 최대 속력.

 BrakingDecelerationWalking

  • 가속도 없이 걸어 다닐 대의 감속도. 이는 상수값에 의해서 속도를 직접적으로 감소시키는 지속적인 반대방향의 힘이다.

 CanWalkOffLedges

  • true 이면 캐릭터가 절벽 가장자리( ledge )에서 걸어서 떨어질 수 있음.

 CanWalkOffLedgesWhenCrouching

  • true 이면 캐릭터가 앉아서 걸어다닐 때 절벽에서 걸어서 떨어질 수 있음.

 CurrentFloor

  • 캐릭터가 서 있는 바닥에 대한 정보( 걷기 이동시에만 갱신됨 ).

 MaintainHorizontalGroundVelocity

  • true 이면, 걷기 이동시에 경사로를 올라 가더라도 항상 수평 속도를 유지하는데, 이는 경사로가 아닌 평면에서 이동했을 때보다 더 빠르게 이동하게 만든다. 
  • false 이면, 경사로가 아닌 평면에서 이동했을 때와 같은 속도로 이동하게 된다.

 IgnoreBaseRotation

  • 캐릭터가 그것이 서 있는 바닥의 회전을 무시할지 여부.
  • true 이면, 캐릭터가 현재 world 회전값을 유지한다.
  • false 이면, 캐릭터가 움직이는 바닥의 회전값만큼 회전한다.

 PerchRadiusThreshold

  • 캐릭터의 캡슐의 가장자리와 표면의 가장자리가 가까울 때 캐릭터가 표면의 가장자리에 걸쳐 있지 못하게 하는 문턱값.
  • 걸어다닐 수 있는 표면보다 낮은 값의 MaxStepHeight 를 가지는 캐릭터는 떨어지지 않는다는 것에 주의할 것.

 PerchAdditionalHeight

  • 절벽 가장자리에 걸쳐 있을 때, MaxStepHeight 에 이 값을 더해서 걸어다닐 수 있는 바닥에서 얼마나 떨어져 있는지를 검사한다.
  • 걸어 올라가도록 하기 위해서 MaxStepHeight 강제로 바꿈; 이는 캐릭터를 가장자리에서 떨어지게 하거나 바닥에서 약간 높게 떠서 올라가도록 함.

 ForceNextFloorCheck

  • 캐릭터가 걷기 이동중 상태일 때 그것이 실제로 이동하지 못했더라도 강제로 유효한 바닥을 검사하도록 함. 다음 바닥 검사 이후에 클리어됨.
  • 보통 AlwaysCheckFloor 가 false 일 때는 특정 상황이 발생하지 않는다면 바닥 검사를 피하려고 시도할 필요가 없다. 하지만 이는 다음 검사를 항상 실행하도록 강제하기 위해서 사용됨.

 LedgeCheckThreshold

  • 폰이 절벽 가장자리로 가서 떨어지는지 여부를 검사하기 위해서 사용됨. 만약 이 값보다 가장자리의 길이가 짧다면 폰은 절벽에서 떨어질 수 있다.

 AlwaysCheckFloor

  • 캐릭터가 걷고 있는 동안에 stationary character 를 위한 바닥 검사를 항상 강제할 것인지 여부.
  • 보통 움직이고 있지 않을 때는 바닥 검사를 피하는 것이 좋다. 하지만 ( 오브젝트들이 캐릭터 위로 타고 올라 가는 상황과 같이 ) 깡총깡총 뛰다가 잘못되는 상황이 존재한다면 바닥 검사를 강제하기 위해서 사용될 수 있다.

 UseFlatBaseforFloorChecks

  • 캐릭터가 평평한 바닥을 가진 도형을 사용하고 있는 것인양 바닥 검사를 수행함.
  • 이는 캐릭터가 절벽 가장자리에서 ( 캡슐이 가장자리에서 균형을 이루는 것처럼 ) 천천히 떨어지는 상황을 방지한다.


CharacterMovement : Jumping / Falling



캐릭터가 점프하거나 떨어지고 있는 상황을 제어하는 곳입니다.



 이름

 설명

 JumpZVelocity

  • 점프할 때 초기 속도( 즉각적인 수직 가속도 ).

 BrakingDecelerationFalling

  • 떨어질 때 측면 감속도. 가속도에 영향을 주지 않음. 

 AirControl

  • 떨어질 때, 캐릭터에 대해 가능한 측면 이동 제어의 양.
  • 0 = 제어 없음. 1 = MaxWalkSpeed 의 최대 값에서 완전한 제어.

 AirControlBoostMultiplier

  • 떨어질 때, AircontrolBootVelocityThreshold 보다 작은 측면 속도일 때 Aircontrol 에 곱해질 값. 
  • 이 값을 0 으로 설정하면 air control boosting 이 비활성화됨. 최종 결과는 1 로 잘림.

 AirControlBoostVelocityThreshold

  • 떨어질 때, 측면 속도의 크기가 이 값보다 작으면, AirControl 에 AirControlBoostMultiplier 가 곱해짐.
  • 이 값을 0 으로 설정하면 air control boosting 이 비활성화됨.

 FallingLateralFriction

  • 떨어질 때, 측면 공중 이동에 적용될 마찰력.
  • bUseSeperateBrakingFriction 이 false 이면, ( 가속도가 0 일 때마다 ) 멈출 때 더욱 빠르게 멈추는 능력에 영향을 줌.

 ImpartBaseVelocityX

  • true 이면, ( 점프를 포함해 ) 떨어지는 것이 끝났을 때 기저 액터의 X 축 속도에 전달됨. 

 ImpartBaseVelocityY

  • true 이면, ( 점프를 포함해 ) 떨어지는 것이 끝났을 때 기저 액터의 Y 축 속도에 전달됨.

 ImpartBaseVelocityZ

  • true 이면, ( 점프를 포함해 ) 떨어지는 것이 끝났을 때 기저 액터의 Z 축 속도에 전달됨.

 ImpartBaseAngularVelocity

  • true 이면, 점프나 떨어지는 것이 끝났을 때 기저 컴포넌트의 각속도의 접선( tangential ) 성분에 전달됨.

 NotifyApex

  • true 이면, 꼭대기( apex )에서 점프할 때 CharacterOwner 의 컨트롤러에 NotifyJumpApex() 이벤트를 전달함. 이는 이벤트가 트리거되면 클리어됨.

 JumpOffJumpZFactor

  • 캐릭터 밑에 있도록 허용되지 않은 액터로 점프해서 올라갔을 때 사용될 JumpZVelocity 의 분수값( 예를 들어, 여러분이 다른 플레이어의 위에 서 있도록 허용되지 않았다면 ).


Character Movement : Swimming



캐릭터가 물에 빠졌을 때의 이동을 제어하는 곳입니다.



 이름

 설명

 MaxSwimSpeed

  • 최대 수영 속력.

 BrakingDecelerationSwimming

  • 수영시의 감속도이며 가속도에는 영향을 주지 않음.

 Buoyancy

  • 물의 부력. 비율( 1.0 = 자연스런 부력, 0.0 = 부력 없음 ).

 MaxOutOfWaterStepHeight

  • 물에서 나가기 위한 최대 높이.

 OutofWaterZ

  • 폰이 물에서 나가려 할 때 적용되는 Z 축 속도.
 JumpOutofWaterPitch
  • 물 속에 있을 때, 이 값 이상의 pitch 각을 올리면 jump 함.


Character Movement : Flying



날아 다닐 때의 이동을 제어하는 곳입니다.



 이름

 설명

 MaxFlySpeed

  • 최대 비행 속력.

 BrakingDecelerationFlying

  • 비행시의 감속도이며 가속도에는 영향을 주지 않음.


Character Movement : Custom Movement



사용자가 커스텀하게 설정하는 이동을 제어하는 곳입니다.


 이름

 설명

 MaxCustomMovementSpeed

 최대 속력.


Character Movement : Physics Interaction


캐릭터 이동시 물리의 영향을 어떻게 받을 것인지 설정하는 곳입니다.



 이름

 설명

 EnablePhysicsInteraction

  • 만약 활성화되면, 걸어다닐 때 플레이어는 물리 개체들과 상호작용함.

 TouchForceScaledtoMass

  • 만약 활성화되면, TouchForceFactor 가 영향받는 오브젝트의 kg 질량마다 적용됨.

 PushForceScaledtoMass

  • 만약 활성화되면, PushForceFactor 가 영향받는 오브젝트의 kg 질량마다 적용됨.

 PushForceUsingZOffset

  • 만약 활성화되면, PushForce 위치가 PushForcePointZOffsetFactor 를 사용해 이동됨. 그렇지 않으면 단순하게 충돌 지점을 사용함.

 ScalePushForcetoVelocity

  • 만약 활성화되면, 적용된 push force 는 물리개체의 속도를 플레이어의 속도와 같게 만들려고 시도함. 이는 힘을 감소시킬 것이며, PushForceFactor 에 의해 정의된 것보다 더 많은 힘을 적용하지는 않을 것이다.

 StandingDownwardForceScale

  • 플레이어가 서 있는 개체에 적용되는 힘이 ( 질량과 중력 때문에 ) 이 만큼 스케일링 됨.

 InitialPushForceFactor

  • 플레이어가 blocking 하는 물리 개체로 튕겨졌을 때 적용하는 초기 충격력( impulse force ).

 PushForceFactor

  • 플레이어가 blocking 하는 물리 개체와 충돌했을 때 적용할 힘.

 PushForcePointZOffsetFactor

  • 힘이 적용되는 위치에 대한 Z축 오프셋. 0.0 은 물리 개체의 중심이며, 1.0 은 천장. -1.0 은 바닥.

 TouchForceFctor

  • 플레이어가 터치했을 때 물리 개체에 적용되는 힘.

 MinTouchForce

  • 플레이어가 터치한 물리 개체에 적용되는 최소 힘. 만약 0.0 보다 작으면, 최소값 없음.

 MaxTouchForce

  • 플레이어가 터치한 물리 개체에 적용되는 최대 힘. 만약 0.0 보다 작으면 최대값 없음.

 RepulsionForce

  • 모든 겹쳐있는 요소들에게 지속적으로 적용되는 kg 당 힘.


개요



UT 에서 캐릭터 관련 애셋들이 실제로 어떻게 사용되는지 구체적으로 알아 보기 전에 캐릭터의 로직에 대해서 살펴 볼 필요가 있습니다.


플레이어가 게임 도중에 취할 수 있는 행동은 Input 매핑과 관련이 있습니다. "Edit >> Project Settings >> Input" 항목에는 두 종류의 Input Mapping 이 존재합니다; Action Mapping 과 Axis Mapping.




이 문서에서는 어떠한 입력 매핑이 존재하고, 그런 것들이 어떻게 캐릭터와 연관되는지에 대해서 살펴 보도록 하겠습니다.


입력 매핑



다들 알고 계실거라 생각하지만 노파심에 설명하자면 Action Mapping 은 단일 이벤트이며, Axis Mapping 은 지속 이벤트입니다. 이러한 Input Mapping 과 관련한 자세한 내용이 궁금하시다면, 언리얼 공식 문서의 [ 입력 (Input) ] 항목을 참조하십시오. 참고로 매핑을 할 때는 특정 키가 연관되어 있다는 것만 지정하지, down, up, double click 등의 정보를 명시적으로 지정하지는 않습니다.


어쨌든 UT 의 Action Mapping 은 다음 표와 같습니다( 여기에서는 PC 관련 매핑만 다루도록 하겠습니다 ).


 매핑 키

  매핑 입력 키

 설명 

 AUTPlayerController 메서드

 Jump

 Space Bar

 점프.

 IE_Pressed : Jump()

 Crouch

 C

 Left Ctrl

 앉기.

 IE_Pressed : Crouch()
 IE_Released : UnCrouch()

 Slide

 Left Shift

 슬라이딩.

 IE_Pressed : Slide()
 IE_Released : StopSlide()

 PrevWeapon

 Mouse Wheel Up

 이전 무기를 선택.

 IE_Pressed : PrevWeapon()

 NextWeapon

 Mouse Wheel Down

 다음 무기를 선택.

 IE_Released : NextWeapon()

 ThrowWeapon

 M

 무기를 버림.

 IE_Released : ThrowWeapon()

 StartFire

 Left Mouse Button

 Right Ctrl

 총질을 시작.

 IE_Pressed : OnFire()

 StopFire

 Left Mouse Button

 Right Ctrl

 총질을 멈춤.

 IE_Released : StopFire()

 StartAltFire

 Right Mouse Button

 아마도 alternative fire( secondary fire ).

 Alternative fire 를 시작.

 IE_Pressed : OnAltFire()

 StopFire

 Right Mouse Button

 Alternative fire 를 멈춤.

 IE_Released : OnStopAltFire()

 StartActivatePowerup

 Q

 Powerup( U Damage, Invisibility, Berserk, Jump Boots ) 을 활성화함.

 IE_Pressed : OnActivatePowerupPress()

 SlowerEmote

 Mouse Wheel Down

 감정 표현 애니메이션을 느리게 만듦.

 IE_Pressed : FasterEmote()

 FasterEmote

 Mouse Whee Up

 감정 표현 애니메이션을 빠르게 만듦.

 IE_Pressed : SlowerEmote()

 Play Taunt

 J

 첫 번째 도발 애니메이션 재생.

 IE_Pressed : PlayTaunt()

 Play Taunt2

 K

 두 번째 도발 애니메이션 재생.

 IE_Pressed : PlayTaunt2()

 TapRight

 D

 오른쪽으로 회피.

 IE_Pressed : OnTabRight()

 IE_Released : OnTapRightRelease()

 TapLeft

 A

 왼쪽으로 회피.

 IE_Pressed : OnTabLeft()
 IE_Released : OnTapLeftRelease()

 TapForward

 W

 앞쪽으로 회피.

 IE_Pressed : OnTabForward()
 IE_Released : OnTabForwardRelease()

 TapBack

 S

 뒤쪽으로 회피.

 IE_Pressed : OnTabBack()
 IE_Released : OnTabBackRelease()

 SingleTapDodge

 V

 앞의 TabXXX 시리즈가 더블 클릭을 요구하는데 단일 클릭만으로 회피.

 IE_Pressed : OnSingleTabDodge()

 ShowScores

 Tab

 점수 현황판을 보여 줌.

 IE_Pressed : OnShowScores()
 IE_Released : OnHideScore()

 ShowMenu

 Escape

 메뉴를 보여 줌.

 ShowMenu()

 Talk

 T

 전체 채팅창을 보여 줌.

 IE_Pressed : Talk()

 TeamTalk

 Y

 팀 채팅창을 보여 줌.

 IE_Pressed : TeamTalk()


Axis Mapping 은 다음 표와 같습니다.


 매핑 키

 매핑 입력 키

 스케일

 설명 

 AUTPlayerController 메서드

 MoveForward

 W

 Up

 1.0

 1.0 

 앞쪽으로 이동. 

 MoveForward()

 MoveBackward

 S

 Down

 1.0

 1.0 

 뒤쪽으로 이동.

 MoveBackward()

 MoveLeft

 A

 1.0

 왼쪽으로 이동.

 MoveLeft()

 MoveRight

 D 

 1.0 

 뒤쪽으로 이동.

 MoveRight()

 TurnRate

 Left

 Right

 -1.0

 1.0

 비율로 Yaw 회전.

 TurnAtRate()

 Turn

 Mouse X

 1.0

 Yaw 회전.

 AddYawInput()

 LookUp

 Mouse Y

 -1.0

 Pitch 회전.

 AddPitchInput()

 MoveUp

 C

 Space Bar

 1.0

 -1.0

 수직으로 이동. 

 MoveUp()


이러한 입력 매핑들은 AUTPlayerController::SetupInputComponent() 에서 수행됩니다.



이동 입력



이동을 위한 이벤트 처리 로직은 아래의 sequence diagram 에 나와 있습니다. 간단한 실행 흐름만을 보여 주려고 했기 때문에, 내부적으로 설정되는 필드같은 것은 생략했습니다. 여기에서 전부 표현해 주기에는 너무 많습니다. 


반복되는 호출에 대해서는 하위 호출을 생략했으니 보시는데 주의하시기 바랍니다.




위의 다이어그램은 매우 복잡해 보이지만 사실 매우 단순합니다. 입력은 Player Controller( AUTPlayerController ) 에서 처리합니다. 그리고 Character( AUTCharacter ) 로 전달하죠. 그러면 Character 는 최종 TM 을 결정하기 위한 플래그나 정보를 설정합니다. 그런데 애니메이션 피드백이나 물리 피드백을 위해서 Movement Component( UUTCharacterMovement ) 에 상태 정보를 전달합니다. 


이벤트 처리 단계에서는 대부분 현재 상태나 플래그를 설정하는 것으로 끝납니다. 애니메이션 피드백이나 물리 피드백을 위한 실제 처리는 UUTCharacterMovement::PerformMovement() 에서 처리됩니다.


PerformMovement() 의 내부가 어떻게 구성되는지 좀 더 자세히 알고자 하신다면 [ UE4 캐릭터 이동 시스템 가이드 ]를 참조하시기 바랍니다.


전투 입력



FPS 게임이다보니 전투와 관련한 입력은 그리 많지 않습니다; fire, alt-fire, select weapon, drop weapon.


전투 입력이나 이동 입력이나 전반적으로 그 처리의 흐름이 크게 다르지 않습니다. 단지 Movement Component 와의 연관성이 더 적습니다. 특이한 것은 FDeferredFireInput 의 인스턴스는 UUTCharacterMovement::TickComponent() 호출 시점에서 소비된다는 것입니다.


나가며



여기에서는 플레이어 입력과 관련한 로직들을 간단하게 살펴 보았습니다.


그런데 근본적으로 우리가 관심을 가지고 있는 것은 이런 로직들이 최종적으로 다른 애셋들( 메쉬, 애니메이션 등 )과 어떤 식으로 연관되는지 알아 내는 것이므로 아직 성에 차지 않을 수 있습니다. 하지만 내용이 너무 복잡해져서 여기에서 모두 담기 어려운 면이 있어서 나눠서 분석할 계획입니다.

개요



1인칭 캐릭터는 말 그대로 1인칭 카메라 모드에서 동작합니다.


UT 에서는 하나의 캐릭터 내에 1인칭 캐릭터 메쉬와 3인칭 캐릭터 메쉬를 모두 포함하고 있습니다. 일단 플레이를 시작하면 플레이어를 위해서 DefaultCharacter 블루프린트의 인스턴스를 생성하게 됩니다.


그림1. DefaultCharacter 블루프린트.


노란 엣지를 가진 메쉬를 확인할 수 있습니다. 이 블루프린트의 상속구조는 다음과 같습니다.


그림2. DefaultCharacter 상속 구조.



[ 그림1 ]의 블루프린트에서 "(Inherited)" 라고 표시되어 있는 컴포넌트들은 모두 AUTCharacter 클래스에서 정의한 컴포넌트들입니다. 그래서 위치를 바꾸거나 이름을 변경하는 것이 불가능합니다. 만약 변경하고 싶다면 소스 코드를 수정해야 합니다.


어쨌든 이 문서에서는 1인칭 캐릭터와 관련한 컴포넌트에 집중하도록 하겠습니다. [ 그림1 ]의 "Components" 뷰에서 선택한 두 개의 컴포넌트( CharacterCameraComonent 와 FirstPersonMesh )들이 1인칭 캐릭터 전용 컴포넌트입니다. UTChracterMovement 는 1인칭 캐릭터와 3인칭 캐릭터에서 공용으로 사용합니다. 이 CharacterMovement 컴포넌트에 대한 간략한 설명은 [ UE4 캐릭터 이동 시스템 가이드 ] 에서 찾아볼 수 있습니다.


FirstPersonMesh



[ 그림1 ]의 FirstPersonMesh 컴포넌트는 USkeletalMeshComponent 의 인스턴스입니다. 이것은 UStaticMeshComponent 와 대응되는 것으로서 본애니메이션이 가능한 메쉬 컴포넌트를 의미합니다.


[ 그림3 ] 은 이 컴포넌트의 가장 중요한 속성들을 보여 줍니다.


그림3. FirstPersonMesh 컴포넌트의 주요 속성들.


속성들은 다음과 같은 의미를 가집니다. 여기에서는 간략하게 설명하도록 하겠습니다.

    • Animation 카테고리에서 "Content/RestrictedAssets/Animations/Universal/1stPerson/UT4_Base_1stP_AnimBP_C.uasset" 이라는 애니메이션 블루프린트를 사용할 것임을 지정합니다. 
    • Mesh 카테고리에서 "Content/RestrictedAssets/Character/Human/Male/malcolm_ut4_1stp_SKELMESH.uasset" 이라는 스켈레탈 메쉬를 사용할 것임을 지정합니다.
    • Materials 카테고리에서 "Content/RestrictedAssets/Character/Malcom_New/Materials/M_Malcom_Body_Panini.uasset" 이라는 머티리얼을 사용할 것임을 지정합니다.
    • Collision 카테고리에서 "NoCollision" 프리셋을 지정합니다. 즉, 부모 컴포넌트인 CapsuleComponent 의 콜리전 설정에 의존하겠다는 것입니다. 참고로 CapsuleComponent 의 콜리전 프리셋은 "Pawn" 으로 지정되어 있으며, [ 그림 4 ] 에서 확인할 수 있습니다.


그림4. CapsuleComponent 컴포넌트의 Collision Preset.


CharacterCameraComponent



CharacterCameraComponent 는 1인칭 카메라를 의미합니다. CharacterCameraComponent 가 FirstPersonMesh 를 자식으로 가지고 있기 때문에 카메라를 회전시키거나 이동시키면 자동으로 FirstPersonMesh 도 카메라의 영향을 받게 됩니다.


반대로 캐릭터 컨트롤러를 회전시키게 되면 자동으로 카메라도 회전합니다. 이는 CameraSettings 카테고리의 "UsePawnControlRotation" 속성을 true 로 만듦으로써 가능합니다.




"UsePawnControlRotation" 는 카메라가 플레이어 컨트롤러의 forward 방향을 바라보도록 강제하겠다는 것입니다. 다시 말하면 카메라가 항상 캐릭터의 등짝을 바라보게 된다는 의미입니다. 1인칭에 딱 맞는 카메라라 할 수 있겠죠. 세부적인 구현은 다음과 같습니다. 



정리



여러 가지 컴포넌트들이 존재하지만, 1인칭 캐릭터와 관련된 핵심 컴포넌트는 FirstPersonMesh 스켈레탈 메쉬 컴포넌트와 CharacterCameraComonent 카메라 컴포넌트입니다.


중요한 것은 FirstPersonMesh 가 CharacerCameraComponent 의 자식이어서 캐릭터의 TM 이 카메라에 종속되며, 카메라가 항상 등쪽을 바라보도록 하기 위해서 "UsePawnControlRotation" 을 사용한다는 것입니다.


이 문서에서는 간략하게 구조에 대해서만 언급하고 다른 문서들에서 1인칭 캐릭터의 세부 사항에 대해서 다루도록 하겠습니다.

개요



언리얼 엔진에서 모듈이라는 개념을 이해하는 것은 매우 중요합니다. 그래서 이 문서에서는 다음과 같은 주제에 대해서 다루고자 합니다.


  • 모듈의 개념.
  • 모듈의 빌드 방식.
  • 모듈 룰 파일.


모듈의 정의


 

언리얼에서의 "모듈"이라는 것은 특정 함수성( functionality )를 제공하는 기능 집합을 의미합니다. 예를 들어 UI 모듈이면 UI 관련 기능 집합을 담고 있는 dll 이라 할 수 있습니다. 아래 그림은 언리얼 엔진이 제공하는 모듈의 예를 보여 줍니다.

 

 

이런 모듈을 만들기 위해서 기존에는 프로젝트를 따로 생성했었습니다. 그런데 언리얼의 경우에는 하나의 프로젝트 내에서 여러 개의 모듈을 생성할 수 있도록 하고 있습니다. 아마도 dll 을 만들기 위해서 프로젝트를 여러 개 생성해야 하고 설정도 해야 하는 불편함을 없애려는 목적으로 그렇게 한 것 같습니다.

 

언리얼에서 한 프로젝트에서 모듈을 여러 개 생성할 수 있도록 해 주는 메커니즘은 NMAKE 입니다. UnrealBuildTool.exe 을 이용해서 모듈 단위로 묶어서 컴파일하고 링크하는 것이 가능하기 때문에, 한 프로젝트에서 여러 개의 모듈 파일( dll )이 나올 수 있는 것입니다.


그런데 도대체 모듈은 왜 구분하는 것일까요? 대부분의 모듈들은 정적으로 임포트되지 않습니다. 왜냐하면 특정 기능집합을 특정 응용프로그램에서는 사용하지 않을 수도 있기 때문입니다. 예를 들어 네트워킹과 관련한 기능이 전혀 없는 응용프로그램에서 그것과 관련한 모듈을 반드시 임포트해야 할 필요는 없을 것입니다.


이러한 모듈 개념은 동적인 로드/언로드/리로드를 가능하게 해 주며, 이런 특성은 플러그인 시스템을 구축하기 위해서 사용되기도 합니다.

 

모듈의 빌드 방식


 

언리얼 엔진에서 모듈은 두 가지 방식으로 빌드됩니다. Modular( 조립식 ) 모드와 Monolithic( 통합식 ) 모드가 있습니다.

 

Modular 모드는 모든 모듈이 개별 dll 로 쪼개진다는 것을 의미하고, Monolithic 모드는 모든 모듈이 정적 라이브러리처럼 하나의 실행파일에 임포트되는 것을 의미합니다. 어떤 모드로 빌드되느냐는 UnrealBuildTool 세팅, 플랫폼, 빌드 환경 설정에 따라 다릅니다.

 

언리얼에서는 빌드를 하기 위해서 타겟 룰이라는 것을 사용합니다. 그러한 타겟 룰의 유형에는 Game, Editor, Client, Server, Program 이 있는데, Game, Client, Server 의 경우에는 Monolithic 을 권장하고, Editor 에는 Modular 를 권장합니다. 그리고 Program 의 경우에는 프로그램의 종류에 따라 다릅니다.

 

그렇다고 Game 타겟 룰은 무조건 Monolithic 이어야 한다는 것은 아닙니다. 이것은 Target.cs 파일을 작성하는 사람의 마음대로 정할 수 있는 것입니다. 예를 들어 TargetRules 클래스는 다음과 같은 가상 메서드를 포함합니다. 이를 재정의하게 되면 Monolithic 모드 실행 여부를 결정할 수 있습니다.

 


기본적인 구현은 이렇습니다. Editor 가 아닌 경우에는 "-modular" 라는 인자를 강제로 지정하지 않으면 Monolithic 모드로 빌드하게 됩니다. Editor 인 경우에는 "-monolithic" 을 지정해야만 Monolithic 모드로 빌드하게 됩니다. 앞의 조건에 해당하지 않으면 무조건 Modular 모드로 빌드합니다.


그런데 어떤 경우에 Modular 모드를 사용하고 어떤 경우에 Monolithic 모드를 사용할까요? Modular 모드를 사용하면 빌드( 컴파일 + 링크 ) 단위를 구분할 수 있다는 장점이 있습니다. 이는 빌드 시간에 영향을 줍니다. 일부 모듈만 수정된 상황이라면 Modular 모드는 그 모듈만을 빌드합니다.

 

모듈 룰 파일


 

Modular 모드로 모듈을 빌드하면 dll 이 생성됩니다. 이 모듈을 컴파일하기 위해 dllexport 지정자가 사용되면, 이 모듈에 접근하기 위해서는 dllimport 지정자가 필요합니다. 만약 한 번이라도 dll 을 만들어 보신 분이라면, 쉽게 이해하실 수 있을 것입니다. 이러한 지정자를 모듈 API 지정자라 부릅니다.


언리얼 엔진 솔루션을 열어 솔루션 익스플로러를 확인하면, 여러 개의 폴더가 있는데, 그 중에 아무 거나 펼쳐 봅시다. 여기에서는 EditorStyle 폴더를 열어 보았습니다.


 

그러면 그 폴더에 EditorStyle.Build.cs 라는 파일이 있는 것을 확인할 수 있습니다. 이것이 모듈 룰 파일입니다. 이 모듈 룰 파일이 있는 디렉토리는 하나의 모듈이라 할 수 있습니다. 모듈 룰 파일에서는 모듈 이름을 지정하고, import 할 다른 모듈을 지정하고, 자신의 프로젝트 내에서 include 할 header 의 경로를 지정합니다. EditorStyle.Build.cs 파일을 살펴 봅시다.



5 라인에서 모듈의 이름을 "EditorStyle" 로 지정해 줍니다. 그리고 PublicDependencyModuleNames, PrivateDependencyModuleNames, PrivateIncludePathModuleNames 등을 지정합니다. 위에서 언급했듯이 외부 모듈 등을 설정해 줍니다. 여러 종류의 다른 리스트들이 존재하지만, 아직까지 모든 리스트의 의미에 대해서 파악하지는 못했습니다. 이게 제대로 정리되어 있는 문서가 없네요. 단지 경험적으로 역할에 대해 대충 추정할 뿐입니다. 이 부분에 대해서는 기회가 되면 따로 정리하거나 문서를 갱신하도록 하겠습니다.


어쨌든 이렇게 모듈 이름이 지정되면 UnrealHeaderTool 이라는 녀석이 자동으로 EDITORSTYLE_API 라는 정의를 생성해 줍니다.


그래서 "#define EDITORSTYLE_API" 라는 것은 아무리 검색해도 찾을 수 없지만, 빌드할 때 자동으로 생성되는 *.generated.h 파일에는 포함됩니다.






개요



UT 를 설치하고 나면 게임을 어떻게 실행해야 하는지 몰라서 답답함을 느끼게 될 것입니다. 이 때 도움을 줄 수 있는 것이 "ServerLaunch.exe" 라는 프로그램입니다.


이 파일의 경로는 "$(SolutionDir)/UnrealTournament/Binaries/ServerLaunch.exe" 이며, 이것을 디버깅하거나 분석할 수 있는 소스 파일 패키지( ServerLaunchSource.zip )도 같은 디렉토리에 들어 있습니다.




이 파일을 실행하게 되면 다음과 같은 윈도우가 뜹니다.



로고 버튼 혹은 LAUNCHER 버튼 : Settings



가장 위쪽에 있는 로고나 LAUNCHER 라는 글자를 클릭하면 Settings 다이얼로그가 나옵니다.



  • "Location of the UE4Editor.exe" 항목은 "UE4Editor.exe" 의 경로입니다. 즉 게임을 실행할 때 "UE4Editor.exe" 를 통해서 실행한다는 의미입니다. 물론 "UE4Editor.exe" 도 "UnrealTournament" 모듈을 포함하기 때문에 게임을 실행하는 데는 문제가 없습니다.
  • "Client IP" 는 클라이언트 프로그램의 IP 인데요, 로컬에서 띄울 것이기 때문에 "127.0.0.1" 로 설정되어 있습니다.
  • "Add Custom Server Command Line" 항목은 서버 실행시에 넘길 매개변수입니다. 어떠한 매개변수가 추가될 수 있는지는 아직까지는 잘 모르겠습니다. 거기까지는 분석을 안 했거든요...
  • "Game Types" 의 의미는 다음과 같습니다.
    • DM : Death Match.
    • TDM : Team Death Match.
    • CTF : Capture The Flags.
  • "Client Resolutions" 는 클라이언트 해상도입니다. 원하는 해상도를 추가하시면 됩니다. 게임 내에서 해상도를 바꿔도 다시 실행했을 때 런처의 설정으로 덮어 써 집니다. 해상도 말고 다른 설정들은 유지됩니다.


참고로 왜인지 모르겠지만 다이얼로그의 크기가 맞지 않아서 "Save" 버튼이 안 나오고 있는데, 현재 그 버튼에 포커스가 가 있으므로 엔터를 치면 "Save" 가 눌립니다. 맘에 들지 않으면 소스를 수정하시면 됩니다. 소스의 윈폼 디자이너에서 다이얼로그 크기를 늘렸다가 다시 줄이면 제대로 나옵니다.


"Single Player" 버튼



"Single Player" 버튼을 누르면 하나의 싱글 모드 클라이언트가 실행됩니다. 다음과 같은 식으로 인자가 넘어 갑니다.


UE4Editor.exe UnrealTournament ?Game=DM?GoalScore=5?TimeLimit=6 -game -log -winx=20 -winy=40 -resx=1600 -resy=900 -consolex=20 -consoley=960


의미를 이해하는 데는 큰 어려움이 없을 거라 생각합니다.


이 모드에서는 "Map To Play" 드롭다운박스에서 선택한 맵을 플레이를 시작하게 됩니다.


클라이언트를 띄우면서 콘솔이 같이 뜨는데, 그건 서버가 아닙니다. 서버는 따로 "Launch Server" 버튼을 통해 띄우게 되어 있습니다.



이 모드에서는 튜토리얼같은 싱글플레이만 즐길 수 있습니다. 멀티 플레이에 입장이 안 되는 건 아니지만, 입장해 봐야 다른 클라이언트가 들어 올 수 없으므로 의미가 없습니다.


현재 초기 맵이 "ShockAttaches" 라는 DM 맵이므로, 초기 화면으로 가려면 ESC 버튼을 누르고 Back 버튼을 누르면 됩니다.



클라이언트가 하나 뜨면 "DESKTOP-XXX" 와 같은 플레이어 아이디가 생성됩니다. 확인해 본 결과 싱글 플레이를 띄우면 항상 같은 아이디를 할당하는 것을 알 수 있었습니다.


이제 "BASIC TRAINING" 과 같은 싱글 모드 contents 를 즐길 수 있습니다. 아까도 말했듯이 멀티 플레이는 들어 가 봐야 의미가 없습니다. 트레이닝 맵에 처음 입장할 때는 텍스쳐 압축같은 것을 하느라 시간이 걸릴 수 있으므로 로그 창을 잘 보고 인내심을 기르시기 바랍니다.


"Launch Server" 버튼



"Launch Server" 버튼을 누르면 서버를 띄우게 됩니다. "Listen Server" 가 선택되지 않았을 때 다음과 같은 인자가 넘어 갑니다.


UE4Editor.exe UnrealTournament ?Game=DM?GoalScore=5?TimeLimit=6 -SERVER -log


그리고 나서 다음과 같이 서버 콘솔이 하나 뜹니다.



하지만 "Listen Server" 에 체크를 하게 되면, 게임을 띄웁니다. 제가 listen server 의 의미는 잘 몰라서 더 이상은 설명드리기 어려울 것 같습니다.


UE4Editor.exe UnrealTournament ?Game=DM?GoalScore=5?TimeLimit=6?Listen=1 -GAME -winx=20 -winy=40 -resx=1280 -resy=720 -consolex=20 -consoley=780 -log


"Connect Local Client" 버튼



"Connect Local Client" 는 "127.0.0.1" 에 접근하는 클라이언트를 "Number of Clients" 에 설정된 개수만큼 띄워 줍니다. 그 서버는 "Launch Server" 버튼을 통해 미리 띄워 둔 서버입니다. 물론 "Listen Server" 에 체크를 해서는 안 됩니다.



Single Player 의 인자와 다른 점이 있다면, "127.0.0.1" 이라는 주소가 들어 가 있다는 것과 클라이언트마다 창 위치를 다르게 한다는 것입니다. 만약 본인이 서버를 따로 구축했다면, 소스 코드에서 이 주소 부분을 수정하면 됩니다. 그렇게 되면 더 이상 "Local" 은 아니겠지만 말이죠.


이 클라이언트들도 싱글모드에서와 마찬가지로 각자 ID 를 할당받게 됩니다.


개요


 

UE4 는 모듈이라는 개념을 사용해서 프로젝트를 관리합니다. 각 프로젝트에는 "*.Target.cs" 라는 파일이 존재합니다. 이는 빌드타겟을 지정하고 있으며, 내부의 SetupBinaries() 메서드에서는 사용하고자 하는 모듈을 지정하게 됩니다.

 

UT 의 UnrealTournament 는 세 개의 빌드 타겟을 가집니다.

 

    • UnrealTournament
    • UnrealTournamentEditor
    • UnrealTournamentServer

 

그 중에서 UnrealTournamentEditor 빌드 타겟은 다음과 같은 모듈들을 포함합니다.

 

 

아래의 섹션들중에서 클라이언트 및 에디터와 관련한 두 모듈에 대해서 설명하도록 하겠습니다.

 

UnrelTournament 모듈


 

이 모듈의 정의파일은 UnrealTournament.Build.cs 입니다.

 

다음과 같은 기능들을 포함합니다. 모든 기능에 대해서 언급하기는 어렵고 관심이 있으신 분들은 관련 모듈 파일들을 살펴 보시기 바랍니다.

 

 

대부분 게임을 구동하는 데 사용하는 모듈들입니다. NetworkReplayStreaming, Json, JsonUtilities 등은 json replay data streaming 을 위해 사용됩니다.

 

 

UnrealTournamentEditor 모듈


 

이 모듈의 정의파일은 UnreaTournamentEditor.Build.cs 입니다.

 

당연한 이야기지만 UnrealTornament 의 기능을 포함하며, 편집을 위한 기능들을 추가적으로 가집니다.

 


이 문서는 https://github.com/EpicGames/UnrealTournament 의 How to get started (Windows) 항목에 대한 번역입니다. 해당 github 에 접근하는 것에 실패했다면, [ GITHUB 계정 연동 ] 문서를 참조하시기 바랍니다.

 


 

현재 언리얼 토너먼트 프로젝트는 언리얼 엔진 4.12 프리뷰 버전에 기반합니다( 역주 : 나중에는 버전이 더 올라갈 수 있습니다 ). 편의를 위해서 이 엔진 소스는 이제 우리 저장소에 포함됩니다. 윈도우즈에서의 사용을 위해서는 비주얼 스튜디오 2015 가 필요합니다.

 

언리얼 토너먼트 프로젝트 다운로드하기:

    • 이 페이지에 있는 Download ZIP 파일을 클릭해서 프로젝트를 다운로드하십시오.
    • 여러분의 컴퓨터에 있는 폴더에서 파일의 압축을 해제하십시오( 역주 : zip 파일의 크기는 작지만, 설치과정을 마치고 에디터를 실행하게 되면 최종적으로는 60 GB 이상의 용량이 필요합니다 ).
    • 여러분이 생성했던 디렉토리 내의 Setup.bat 파일을 실행하십시오. 이는 GitHub 에 저장되어 있지 않은 필요한 바이너리들을 다운로드하게 됩니다.
    • Engine\Extras\Redist\en-us 폴더에 있는 UE4PrereqSetup_x64 를 실행하십시오. 이는 누락되어 있을 수도 있는 다른 종속성 파일들을 설치할 것입니다( 역주 : Setup.bat 파일을 실행하는 과정에서 이것이 같이 실행되었다면, 굳이 설치할 필요가 없습니다 ).

 

언리얼 토너먼트 프로젝트 컴파일하기:

    • Engine\Source\Programs\UnrealSwarm 에 있는 UnrealSwarm.sln 을 열기 위해서 비주얼 스튜디오를 사용하십시오. 솔루션 구성( Solution Configuration ) 드롭다운 메뉴에서 Development 모드를 선택하십시오.
    • Agent 프로젝트에서 마우스 오른쪽을 클릭해서 속성( Properties )를 선택하십시오. 서명( Singing ) 탭으로 이동하여, "Sign the ClickOnce manifests" 에 대한 선택을 해제하십시오( 역주 : 소스를 받으면 기본값으로 해제되어 있습니다 ).
    • 마우스 오른쪽을 클릭해서 Agent 프로젝트를 빌드하십시오. 빌드가 끝나면 UnrealSwarm 솔루션을 닫아도 됩니다.
    • 프로젝트 압축을 해제한 디렉토리에서 GenerateProjectFiles.bat 를 실행하십시오. 이는 UE4.sln 을 생성할 것입니다.
    • UE4.sln 을 비주얼 스튜디오로 열어서, 솔루션 구성을 Development Editor 로 변경하십시오.
    • Programs 폴더에 있는 ShaderCompileWroker 와 UnrealLightMmass 를 빌드하십시오.
    • UnrealTournament 프로젝트를 빌드하십시오. UE4Editor.exe 가 Engine\Bindaries\Win64 에 생성될 것입니다.
    • 명령줄에서 "UE4Editor.exe UnrealTournament" 를 실행하거나, 비주얼 스튜디오에서 UnrealTournament 프로젝트에서 마우스 오른쪽을 클릭해서 디버그로 실행하십시오.
가끔 GenerateProjectFiles.bat 을 호출하면 Social 모듈 파일이 없다고 나오는데, 그건 zip 파일이 잘못 올라 가 있는 것입니다. 다른 버전의 파일을 받으시든지 [ 이 페이지 ] 의 Common issues 항목을 참조하시든지 하십시오. 


Unreal Tournament( UT ) 란 Epic Games 와 Digital Extremes 에서 공동개발한 1인칭 슈팅( First Person Shooting ) 게임입니다. UE4 로 넘어 오면서는 그냥 Epic Games 에서만 개발하고 있는 것으로 보입니다. UT 사이트에 가 봤더니 다른 공동 개발사에 대한 언급은 없더군요.

 

어쨌든 UT 는 Counter Strike 와 같은 전통적인 FPS 게임과는 다르게 총을 쏘는 즉시 반응하는 것이 아니라 나가는 궤적을 볼 수 있으며 곡사도 가능합니다. 그리고 매우 다양한 패턴의 무기가 존재합니다. 이런 부분들이 우리나라 게이머들에게는 별로 취향에 맞지 않는지 우리나라에서는 인기가 없습니다.

 

그럼에도 불구하고 제가 UT 를 분석하려고 하는 이유는 다음과 같기 때문입니다.

    • 최신버전에 맞게 갱신된 소스가 공개되어 있습니다.
    • 매우 오랜 기간동안 개발해 와서 안정성이 상당히 높습니다.
    • Epic Games 에서 개발하고 있기 때문에 UE4 사용법에 대한 훌륭한 레퍼런스가 될 것으로 보입니다.

 

UT 분석을 통해서 다음과 같은 부분에 대한 이해도를 높이려고 합니다.

    • Character.
    • Animation.
    • Cosmetic.
    • Physics.
    • Networking.
    • Gameplay.
    • Slate.
    • Optimization.

 

어느 정도 깊이로 분석할지는 모르겠지만, 최선을 다해 볼 생각입니다.

UE4 용 VS 2015 프로젝트를 생성하면 아래와 같은 에러가 발생한 후에 build 가 제대로 안 되는 경우가 있습니다.

 

 

위에서 10.0.10586.0 버전의 ucrt 를 사용하는 것을 알 수 있습니다. 하지만 실제 해당 디렉토리에는 ucrt 가 생성되어 있지 않습니다.

 

그래서 Windows Software Development Kit - Windows 10.0.10586.0 을 설치해 봤습니다. 하지만 여전히 ucrt 는 생성되지 않습니다. Visual Studio 설치 프로세스를 통해 설치하면 위의 에러는 나지 않는데, 이상하게도 VS 2015 가 솔루션을 로드한 다음에 조금 있다가 크래쉬가 납니다. 결국 VS 를 다시 설치했습니다. 이건 순서가 어땠는지 헷갈리네요.

 

그래서 실제 VS 의 lib-path 는 어떻게 되어 있는지 확인해 봤습니다

 

 

그것은 10.0.10240.0 버전이었습니다. 그래서 [ [ 번역 ] Universal CRT 소개 ] 에서 언급한 것처럼 VC++ props 에 $(UniversalCRT_IncludePath) 등을 추가해 봤으나 아무런 소용이 없었습니다.

 

결국에는 UnrealBuildTool 에서 잘못된 lib-path 를 가지고 프로젝트를 생성한다는 결론을 내릴 수밖에 없었습니다. 아마도 10586 과 관련한 path 가 최신의 path 이기 때문에 그것을 지정하는 것이 아닌가 생각합니다. 이것을 수정하고 싶지만 귀찮아서 그냥 꽁수로 해결하기로 했습니다.

 

그래서 10240 버전이 아닌 path 들이 지정되는 것을 막기 위해서 다음과 같은 프로그램들을 제거했습니다.

 

 

이제 제대로 빌드가 됩니다. 회사에서는 잘 되었었는데, 집에서는 안 된 것은 WDK 를 최신버전으로 설치했느냐 그렇지 않느냐의 차이인 것 같네요.

 

어쨌든 결론은 Windows Kits 디렉토리에 설치되는 Windows Software Development Kit 나 Windows Driver Kit 를 같은 버전으로 유지해야 한다는 것입니다. 아니면 UnrealBuildTool 이 제대로 설치된 ucrt 경로를 찾아 주든가요... 아마 후자를 기대하기는 힘들 것 같습니다.

 

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


이 글은 [ UE4 캐릭터 이동 시스템 가이드 ]의 보충 문서입니다. 


UCharacterMovementComponent 의 속성은 여러 개의 카테고리로 나뉘는데, 이 문서에서는 "MovementMode" 카테고리에 대해서 다룹니다. 말 그대로 걷거나 달리는 것과 관련한 행위를 제어합니다.


이것과 관련한 속성으로는 다음과 같은 것들이 있습니다( Desc 항목은 주석을 그대로 옮겨 놓은 것입니다 ).


 Property

 Specifier

 Desc

 MovementMode

 BlueprintReadOnly

 액터의 현재 이동 모드( walking, falling 등 ).

  - walking: 표면 위를 걷는데, 마찰력 효과를 가지며, 방해물을 걸어 올라갈 수 있음. 수직 속력은 0 임.

  - falling: 중력 효과를 받아 떨어지는데, 점프하거나 표면의 가장자리에서 발이 떨어지는 상황임.

 - flying: 중력 효과를 무시하고 날아감.

 - swimming: 유체 덩어리를 통해서 수영하고 있는데, 중력 효과와 부심( bouyancy ) 효과의 영향을 받음.

 - custom: 사용자 정의 커스텀 이동 모드. 매우 많은 서브 모드를 포함할 수 있음.

 이는 Character owner 를 통해 그리고 client-server 이동 함수들을 위해 자동으로 복제된다.

 CustomMovementMode

 BlueprintReadOnly

 MovementMode 가 Custom 일 때 현재의 커스텀 서브 모드를 지정함.

 이는 Character owner 를 통해 그리고 client-server 이동 함수들을 위해 자동으로 복제된다.


주석 자체가 매우 많은 설명을 담고 있기 때문에 뭐라 더 설명할 것이 없을 것 같습니다.


요는 정해진 4 개의 이동 모드가 있고, 사용자가 원한다면 자신만의 커스텀 모드를 설정할 수 있다는 것입니다. 예를 들어 벽을 기어 오르는 모드라든가 매달리는 모드같은 것을 생각해 볼 수 있습니다. 매달리는 상황에서는 매달리는 손들을 기준으로 진자운동을 하며 ( 허리 이하의 ) 일부 본들은 랙돌 상태의 영향을 받는 것이 좋겠죠. 중력을 계산할 필요는 없을 것이구요. 이는 분명히 기존의 4 개의 이동 모드의 동작과는 다릅니다.


각각의 모드는 제약( constraint )를 가집니다. 만약 여러분이 RootMotion 을 만들었는데, 그 중에 z 축 방향으로 점프하는 동작이 포함되었다고 합시다. 그리고 이를 그냥 재생해 봅니다. 그러면 이상하게도 위로는 올라가지 않고 제자리에서 깔짝대기만 합니다. 이는 walking 모드에서는 z 축 성분을 무시하기 때문입니다( 정확하게는 애니메이션의 z 축 성분을 무시하는 것이 아니라 컨트롤러의 z 축 성분을 무시하는 것입니다 ).


RootMotion 이 아닐 때는 위로 점프하는 것이 제대로 동작할 것입니다. 이는 RootMotion 이 아닌 경우에는 컨트롤러가 바닥에 붙어 있기 때문입니다. 위의 표에서 walking 의 설명을 보십시오. "수직 속력이 0" 이라고 되어 있고, "표면위를 걷는다"고 되어 있습니다. 이러한 이동 모드의 특징을 제대로 이해해야 원하는 동작을 제대로 구현할 수 있을 것입니다.


이동 모드를 변경하는 것은 간단합니다. UCharacterMovementComponent 에는 SetMovementMode() 라는 메서드가 있습니다. 캐릭터에서 Jump() 같은 메서드를 호출하는 것은 내부적으로 이동모드의 변경을 내포하고 있습니다.



MovementMode 속성과 CustomMovementMode 속성을 설정할 수 있는 메서드입니다.

주의 : 다른 방법으로도 만들 수 있으니 이 자료를 맹신하지 마시기 바랍니다.

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

주의 : Max 2013 과 2015 에서 테스트했습니다.

주의 : 맥스 스크립트는 이번에 처음 만들어 봅니다. 이상한 부분이 있을 수 있으니 양해 부탁드리고 문제점이 있으면 지적해 주십시오.


개요



언리얼의 [ 루트 모션 ]은 "Root" 라는 이름을 가진 본이 스켈레톤 구조의 최상위에 위치할 것을 요구합니다. 


그런데 우리 나라의 경우에는 Biped 만을 사용하기 때문에 최상위 본은 항상 "Bip01" 입니다. 애니메이터에게 물어 봤더니 Biped layer 같은 것을 사용하려면 Bip01 본 상위에 뭔가 붙여서는 안 된다고 하더군요. 


외국물 좀 먹은 다른 애니메이터에게 물어 봤더니 쉐도우 리깅이나 스크립트를 사용해서 루트 모션을 만들어 낸다고 합니다. 그리고 그 애니메이터의 경우에는 Biped 를 사용하지 않아도 애니메이션 만들 방법은 많기 때문에 굳이 그런 고민을 안 한다고 합니다( 그리고 마야를 많이 쓴다고 하더군요 ).


애니메이터가 Biped 에서 루트 모션을 만드는 과정을 관찰했더니 이건 완전히 생노가다였습니다. 그런데 우리나라 애니메이터들은 Biped 이외에는 사용할 생각이 없기 때문에, 이를 맥스스크립트로 자동화시켜야겠다는 생각을 했습니다.


맥스 스크립트



루트 모션을 만드는 것을 관찰했더니 순서는 [ 다음 ] 과 같았습니다( 링크의 문서는 우리 회사 캐릭터 팀장이 작성했습니다. 물론 위에서 언급했듯이 이게 루트 모션을 만드는 유일한 방법은 아닙니다  ).


저는 그 과정을 보고 최종적으로 "Root 본이 Bip01 의 TM 을 따라 가고, 반대로 Bip01 은 Root 본에 종속되어 상대적인 TM 을 가지면 된다" 라는 결론을 내렸습니다. 그래서 다음과 같은 알고리즘을 세웠습니다.


  1. 제거할 축 입력받음.
  2. "Root" 본 생성.
  3. "Bip01" 본 획득.
  4. Root 본에 Position_XYZ 컨트롤러 바인딩하고 프레임 개수만큼 키를 생성.
  5. Bip01 을 따라 가는 Position_Constraint 컨트롤러 "tempController" 생성.
  6. 전체 프레임을 돌면서 "tempController" 값을 Root 본에 복사함( Bip01 을 복사하는 것과 동일함 ).
  7. Biped 에 "RootMotionLayer" 레이어를 생성하고 현재 레이어로 설정함.
  8. 새로운 레이어의 position 컨트롤러를 획득해서 전체 프레임 개수만큼 키를 생성.
  9. 새로운 레이어의 키의 position 에 "tempController" 컨트롤러에서 특정 축값을 제거한 값을 삽입.
  10. Root 본을 Bip01 의 부모로 설정.
  11. FBX 로 익스포트( 수동 ).
  12. 익스포트 후에 "RootMotionLayer" 레이어 및 "Root" 본 제거( 수동 ).


5 ~ 10 작업은 Bip01 이 Root 에 대해 상대적인 TM 을 가지도록 블렌딩해 주는 작업입니다. 제가 Biped 에 대해서 잘 모르기 때문에 자세한 것은 설명해 드리기 힘들고, 궁금한 것은 자기 팀 애니메이터에게 문의하시면 될 것 같습니다. 



스크립트는 다음과 같습니다. 수정 : Biped Root 의 이름을 입력받을 수 있게 했습니다. 수정 : minZ 와 maxZ 를 입력받을 수 있게 했습니다. 수정 : 바닥이 될 본을 설정할 수 있게 했습니다. 이는 RemoveZ 가 꺼졌을 때만 활성화됩니다.



한계 및 할 일들



애니메이터의 이야기를 들어 보니 점프를 하거나 비대칭적인 캐릭터( 예를 들어 기린? )를 작업할 때는 직접 Dummy 를 만들어서 컨트롤러의 위치를 지정한다고 합니다. 그러므로 그런 경우에는 Root 본이 Bip01 이 아니라 Dummy 를 따라 가도록 만들어야 할 것입니다. 다이얼로그에 그런 옵션들을 추가로 넣고 코드를 수정할 수 있을 것 같습니다. 


그리고 알고리즘의 11 번이나 12 번을 자동화할 필요도 있을 것 같습니다. 아니면 차라리 새로운 파일을 자동으로 만들어서 거기에서 작업이 수행되도록 할 수도 있겠죠.


그리고 현재 스크립트는 항상 position 만을 처리하고 있는데, rotation 이나 scaling 을 처리해야 할 수도 있습니다.


수정 : 그리고 공중회전같은 것을 하면 현재 스크립트의 결과물은 이상해질 것입니다. 이럴 경우에는 다른 방법을 찾아야 합니다.


기회가 되면 루트 모션을 만드는 여러 가지 방법들에 대해서 연구해 봐야할 것 같습니다.

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


개요



언리얼 엔진에서 캐릭터를 다루기 위해서 알아야 할 가장 중요한 컴포넌트가 무엇이냐고 질문을 하신다면, 저는 "가장 중요한 것은 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 참고서 ] 를 참고해서 배포하시기 바랍니다. 그냥 빌드해서 서밋하시면 코드가 변경되었기 때문에 엔진 코드를 수정하면 게임 칸텐츠를 작업하는 측에서 엔진까지 빌드해야 하는 경우가 생깁니다.

+ Recent posts