UDN
Search public documentation:

BasicGameQuickStartKR
English Translation
日本語訳
中国翻译

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 홈 > 언리얼스크립트 > 기본 게임 퀵스타트
UE3 홈 > 게임플레이 프로그래밍 > 기본 게임 퀵스타트

기본 게임 퀵스타트


개요


게임 프로젝트를 새로 짜서 실행시킬 준비를 하는 것은, 초심자에겐 어려운 일일 수 있습니다. 어디서부터 시작해야 할 지, 뭐가 어떻게 돌아가는지도 모를 수 있죠. 여기서는 게임을 새로 만드는 데 있어서의 주요 국면과 중요한 부분을 빠르게 짚어보도록 하겠습니다. 그 과정에서 어떤 종류의 게임에도 특화시킬 수 있는 매우 기본적인 게임 스켈레톤을 같이 만들어 보겠습니다.

프로젝트 셋업


새 게임 제작을 시작할 때 가장 먼저 해야 할 것은, 게임 콘텐츠와 스크립트를 담는 데 사용될 다양한 프로젝트 디렉토리를 셋업하는 것입니다. 언리얼 엔진 3는 한 번에 하나의 프로젝트로만 작업할 수 있도록 고안되었다는 점, 꼭 알아두셔야 합니다. 다수의 프로젝트를 두고자 한다면, 다수의 설치상에서 하는 것이 최선입니다. 왜냐면 단일 설치에서 다수의 프로젝트를 전환하는 것은 성가시고 헛갈리는 일이기 때문입니다.

언리얼스크립트 프로젝트

어느 게임 프로젝트건 결국 게임플레이를 이루기 위한 커스텀 클래스를 만들기 위해서는 언리얼스크립트를 필요로 하게 됩니다. 새 언리얼스크립트를 추가하려면 스크립트를 담기 위한 커스텀 UnrealScript 프로젝트를 구성해야 하며, 이것이 새로운 UnrealScript (.u 파일) 패키지 속으로 컴파일되는 것입니다.

UnrealScript 프로젝트를 만들려면 먼저 언리얼 설치 디렉토리의 ..\Development\Src 디렉토리를 찾습니다. 이 디렉토리 안에 프로젝트에 지을 이름으로 된 폴더를 새로 만듭니다. 보통 만들려는 게임의 약자에 "Game" 을 붙이곤 합니다. 예를 들어 Unreal Tournament 게임이라면 UTGame 정도가 됩니다.

여기에 생성되는 예제 게임에서, UnrealScript 프로젝트의 이름은 UDNGame 으로 하겠습니다. 이 새 폴더의 위치는 생성되고나면 ..\Developement\Src 내에 있게 됩니다:

project_directory.jpg

UDNGame 폴더 안에 Classes 라는 이름의 새 폴더가 생성됩니다. 여기에 프로젝트용 스크립트가 담기게 됩니다.

classes_directory.jpg

커스텀 UnrealScript 프로젝트 구성 관련 자세한 안내는 Custom UnrealScript Projects KR 페이지를 참고하시기 바랍니다.

Content 디렉토리

콘텐츠 없는 게임이 어디 게임일까요. 화면상에 표시할 시각 요소가, 재생할 소리가 있어야 합니다. 물론 콘텐츠를 모두 꾸려넣고 플레이어에게 보여줄 맵도 있어야죠. 일반적으로 패키지 내에 저장된 콘텐츠는 (맵도 패키지입니다) 그 자체적으로 언리얼 설치 디렉토리의 ..\[GameName]\Content 디렉토리에 저장됩니다. Content 디렉토리에는 보통 Characters, Environments, Maps, Sounds 등의 폴더가 있는데, 이를 통해 콘텐츠 패키지와 맵을 조직적으로 관리할 수 있습니다.

UDK를 사용할 때, 이 서브 폴더에는 예제 콘텐츠가 채워져 있습니다. 그냥 아주 쉽게 게임의 커스텀 콘텐츠 패지지와 맵을 똑같은 폴더에 저장하기만 해도 모든것이 완벽하게 잘 돌아갈 것입니다. 그러나 커스텀 콘텐츠와 다른 콘텐츠를 따로 관리하고 싶다면, 그냥 Content 디렉토리에 새 폴더를, 그저 별개의 이름으로 만들어 주고, 그 폴더에 패키지를 저장하면 됩니다. 심지어 그 안에다가도 폴더를 추가로 만들어, Content 디렉토리에서와 유사한 방식으로 패키지를 관리할 수 있습니다.

게임플레이 클래스


예제 게임의 프레임워크는 거의 완전히 언리얼스크립트로 작성된 중요 게임플레이 클래스 몇으로 구성될 것이며, 커스텀 UnrealScript 프로젝트에 저장할 것입니다. 이 클래스의 개념은 잘 돌아가면서 범용 또는 흔한 게임플레이 경험을 만들어 내는 것입니다. 아직 최종 게임을 만들어 내기에는 이른 시점입니다. 실제적인 게임 제작을 시작하는 데 기반이 될 수 있는 프로젝트를 구성해 보고자 함입니다.

Camera

어느 게임이든 가장 핵심적인 요소 중 하나는 플레이어가 세상을 바라보는 관점입니다. 언리얼에서 플레이어 시점의 위치와 방향은 PlayerController 클래스의 GetPlayerViewPoint() 함수에서 처리됩니다. 디폴트로 플레이어 카메라 오브젝트가 있다면, 계산을 처리하도록 허용된 것입니다. 위치와 방향을 계산하는 데는 두 방법 중 하나를 쓸 수 있습니다:

  • UpdateCamera() 함수에서 Pawn 에 있는 CalcCamera() 를 호출
  • UpdateCamera() 함수에서 시점을 직접 계산

기본 예제 게임은 3인칭 시점을 사용할 것입니다. 전술한대로, CalcCamera() 함수를 사용하여 Pawn 클래스에서 구현할 수도 있으나, 카메라 로직을 별도의 클래스에 캡슐화시켜 유지하고자 함은 물론, 카메라 클래스를 사용하면 포스트 프로세싱이나 카메라 애니메이션과 같은 카메라 기능 전부를 접근할 수 있다는 사실 때문에라도, 우리 예제에서는 카메라의 위치를 처리하는 데 커스텀 Camera 클래스를 사용할 것입니다. 이 예제가 비록 3인칭 시점이기는 하나, 카메라 위치와 회전을 계산하는 로직만 바꿔주면 쉽게 다른 종류의 카메라도 구현할 수 있습니다.

Camera Technical Guide KR 페이지에서 카메라에 대한 세부적인 설명은 물론, 다른 시점 구현에 대한 예제도 살펴볼 수 있습니다.

class UDNPlayerCamera extends Camera;

var Vector CamOffset;
var float CameraZOffset;
var float CameraScale, CurrentCameraScale; /** 디폴트 카메라 거리에의 곱수 */
var float CameraScaleMin, CameraScaleMax;

function UpdateViewTarget(out TViewTarget OutVT, float DeltaTime)
{
   local vector      HitLocation, HitNormal;
   local CameraActor   CamActor;
   local Pawn          TPawn;

   local vector CamStart, CamDirX, CamDirY, CamDirZ, CurrentCamOffset;
   local float DesiredCameraZOffset;

   // 보간 도중 나가는 뷰타겟 업데이트 중지
   if( PendingViewTarget.Target != None && OutVT == ViewTarget && BlendParams.bLockOutgoing )
   {
      return;
   }

   // 뷰타겟 상의 디폴트 FOV
   OutVT.POV.FOV = DefaultFOV;

   // 카메라 액터를 통해 보기
   CamActor = CameraActor(OutVT.Target);
   if( CamActor != None )
   {
      CamActor.GetCameraView(DeltaTime, OutVT.POV);

      // CameraActor 로부터 상 비율 획득
      bConstrainAspectRatio   = bConstrainAspectRatio || CamActor.bConstrainAspectRatio;
      OutVT.AspectRatio      = CamActor.AspectRatio;

      // CameraActor 가 사용된 PostProcess 세팅을 덮어쓰려 하는지 확인
      CamOverridePostProcessAlpha = CamActor.CamOverridePostProcessAlpha;
      CamPostProcessSettings = CamActor.CamOverridePostProcess;
   }
   else
   {
      TPawn = Pawn(OutVT.Target);
      // Pawn ViewTarget 에 카메라 위치를 불러줄 기회를 줍니다.
      // Pawn 이 카메라 뷰를 덮어쓰지 않는다면, 자체 디폴트를 가지고 진행
      if( TPawn == None || !TPawn.CalcCamera(DeltaTime, OutVT.POV.Location, OutVT.POV.Rotation, OutVT.POV.FOV) )
      {
         /**************************************
          * 3인칭 시점 계산
          * UTPawn 구현 차용
          **************************************/
         OutVT.POV.Rotation = PCOwner.Rotation;
         CamStart = TPawn.Location;
         CurrentCamOffset = CamOffset;

         DesiredCameraZOffset = 1.2 * TPawn.GetCollisionHeight() + TPawn.Mesh.Translation.Z;
         CameraZOffset = (DeltaTime < 0.2) ? DesiredCameraZOffset * 5 * DeltaTime + (1 - 5*DeltaTime) * CameraZOffset : DesiredCameraZOffset;

         CamStart.Z += CameraZOffset;
         GetAxes(OutVT.POV.Rotation, CamDirX, CamDirY, CamDirZ);
         CamDirX *= CurrentCameraScale;

         TPawn.FindSpot(Tpawn.GetCollisionExtent(),CamStart);
         if (CurrentCameraScale < CameraScale)
         {
            CurrentCameraScale = FMin(CameraScale, CurrentCameraScale + 5 * FMax(CameraScale - CurrentCameraScale, 0.3)*DeltaTime);
         }
         else if (CurrentCameraScale > CameraScale)
         {
            CurrentCameraScale = FMax(CameraScale, CurrentCameraScale - 5 * FMax(CameraScale - CurrentCameraScale, 0.3)*DeltaTime);
         }
         if (CamDirX.Z > TPawn.GetCollisionHeight())
         {
            CamDirX *= square(cos(OutVT.POV.Rotation.Pitch * 0.0000958738)); // 0.0000958738 = 2*PI/65536
         }
         OutVT.POV.Location = CamStart - CamDirX*CurrentCamOffset.X + CurrentCamOffset.Y*CamDirY + CurrentCamOffset.Z*CamDirZ;
         if (Trace(HitLocation, HitNormal, OutVT.POV.Location, CamStart, false, vect(12,12,12)) != None)
         {
            OutVT.POV.Location = HitLocation;
         }
      }
   }

   // Apply camera modifiers at the end (view shakes for example)
   ApplyCameraModifiers(DeltaTime, OutVT.POV);
}

defaultproperties
{
   CamOffset=(X=12.0,Y=0.0,Z=-13.0)
   CurrentCameraScale=1.0
   CameraScale=9.0
   CameraScaleMin=3.0
   CameraScaleMax=40.0
}

PlayerController

어느 게임이고 또다른 중요 요소는 플레이어의 입력을 어떻게 처리할 것인지, 게임 제어로 어떻게 옮길 것인지, 화면상의 메인 캐릭터를 직접 조종할 것인지, 포인트-앤-클릭 인터페이스를 사용할 것인지 다른 방식을 사용할 것인지를 정하는 것입니다. 꽤나 직관적이게도, 플레이어가 게임을 콘트롤하는 방식을 결정하는 것을 담당하는 클래스 이름은 PlayerController 입니다.

베이스 PlayerController 구현은 플레이어 입력을 받아 이동으로 옮겨 주기에 플레이어를 뛰어다니게 하는 데는 충분합니다. 예제에서 사용된 커스텀 PlayerController 클래스는 단지 위에서 만든 커스텀 Camera 클래스를 할당해 주는 것입니다. 물론 게임에 살을 붙여가면서, 말할 것도 없니 이 클래스에 변경을 가하고 게임 특정 구현에 필요한 로직을 추가시키고 해야 할 것입니다.

Characters Technical Guide KR 페이지에서 PlayerController 클래스에 대한 세부적인 설명과, 새로운 유형의 게임에 맞도록 특화시키는 법을 찾아보실 수 있습니다.

class UDNPlayerController extends GamePlayerController;

defaultproperties
{
   CameraClass=class'UDNGame.UDNPlayerCamera'
}

Pawn

PlayerController 클래스는 게임을 제어하는 데 플레이어 입력을 어떻게 처리하는지, 즉 이 예제에서는 직접 캐릭터를 움직이도록 하는 역할을 하고 있으나, 캐릭터에 대한 시각적인 표현 및 그것이 물리 세계와 상호작용하는 방식을 결정하기 위한 로직은 Pawn 클래스에 캡슐화되어 있습니다. 이 예제의 커스텀 Pawn 클래스에는 환경과 상호작용하기 위한 특수 로직은 추가하지 않을 것이나, 캐릭터의 시각적 표현 구성을 담당하게 될 것입니다. 즉 게임의 캐릭터를 표시하는 데 사용되는 스켈레탈 메시, 애니메이션, 피직스 애셋을 설정한다는 뜻입니다.

Characters Technical Guide KR 페이지에서 Pawn 클래스의 세부적인 설명 및 Pawn 클래스가 PlayerController 클래스와 협업하는 방법에 대해 확인해 보실 수 있습니다.

class UDNPawn extends Pawn;

var DynamicLightEnvironmentComponent LightEnvironment;

defaultproperties
{
   WalkingPct=+0.4
   CrouchedPct=+0.4
   BaseEyeHeight=38.0
   EyeHeight=38.0
   GroundSpeed=440.0
   AirSpeed=440.0
   WaterSpeed=220.0
   AccelRate=2048.0
   JumpZ=322.0
   CrouchHeight=29.0
   CrouchRadius=21.0
   WalkableFloorZ=0.78

   Components.Remove(Sprite)

   Begin Object Class=DynamicLightEnvironmentComponent Name=MyLightEnvironment
      bSynthesizeSHLight=TRUE
      bIsCharacterLightEnvironment=TRUE
      bUseBooleanEnvironmentShadowing=FALSE
   End Object
   Components.Add(MyLightEnvironment)
   LightEnvironment=MyLightEnvironment

   Begin Object Class=SkeletalMeshComponent Name=WPawnSkeletalMeshComponent
       //내 메시 프로퍼티
      SkeletalMesh=SkeletalMesh'CH_LIAM_Cathode.Mesh.SK_CH_LIAM_Cathode'
      AnimTreeTemplate=AnimTree'CH_AnimHuman_Tree.AT_CH_Human'
      PhysicsAsset=PhysicsAsset'CH_AnimCorrupt.Mesh.SK_CH_Corrupt_Male_Physics'
      AnimSets(0)=AnimSet'CH_AnimHuman.Anims.K_AnimHuman_BaseMale'
      Translation=(Z=8.0)
      Scale=1.075
      //일반 메시 프로퍼티
      bCacheAnimSequenceNodes=FALSE
      AlwaysLoadOnClient=true
      AlwaysLoadOnServer=true
      bOwnerNoSee=false
      CastShadow=true
      BlockRigidBody=TRUE
      bUpdateSkelWhenNotRendered=false
      bIgnoreControllersWhenNotRendered=TRUE
      bUpdateKinematicBonesFromAnimation=true
      bCastDynamicShadow=true
      RBChannel=RBCC_Untitled3
      RBCollideWithChannels=(Untitled3=true)
      LightEnvironment=MyLightEnvironment
      bOverrideAttachmentOwnerVisibility=true
      bAcceptsDynamicDecals=FALSE
      bHasPhysicsAssetInstance=true
      TickGroup=TG_PreAsyncWork
      MinDistFactorForKinematicUpdate=0.2
      bChartDistanceFactor=true
      RBDominanceGroup=20
      bUseOnePassLightingOnTranslucency=TRUE
      bPerBoneMotionBlur=true
   End Object
   Mesh=WPawnSkeletalMeshComponent
   Components.Add(WPawnSkeletalMeshComponent)

   Begin Object Name=CollisionCylinder
      CollisionRadius=+0021.000000
      CollisionHeight=+0044.000000
   End Object
   CylinderComponent=CollisionCylinder
}

HUD

HUD 클래스는 게임에 대한 정보를 플레이어에게 표시해 주는 것을 담당하는 클래스입니다. 표시되는 정보와 그 방식은 게임에 따라 매우 다릅니다. 그렇기에 예제 구현에서는 Canvas 오브젝트 또는 스케일폼 GFx 자체 커스텀 HUD를 만들 수 있도록 검정 석판을 제공해 보겠습니다.

HUD Technical Guide KR 페이지에서 Canvas 오브젝트나 언리얼 엔진 3의 스케일폼 GFx 통합을 둘 다 사용하여 HUD 를 만드는 법에 대한 세부 정보를 확인할 수 있습니다.

class UDNHUD extends MobileHUD;

defaultproperties
{
}

Gametype

게임타입은 게임의 심장입니다. 게임의 규칙과 게임의 진행을 계속시킬지 말지를 결정하는 조건을 확정합니다. 확실히 이는 전적으로 게임-전용입니다. 게임타입은 PlayerControllers, Pawns, HUD 등에 대해 어떤 클래스를 사용할 지 엔진에 알려주는 역할을 하기도 합니다. 게임타입의 예제 구현은 단지 defaultproperties 안에 이 클래스를 지정하고, 나머지 구현은 당신께 맡기는 것입니다.

Gametype Technical Guide KR 페이지에서 게임타입에 대한 깊이있는 개념 설명과, 커스텀 게임타입에 맞도록 어느 부분을 커스터마이징해야할 지에 대한 통찰을 얻을 수 있을 것입니다.

class UDNGame extends FrameworkGame;

defaultproperties
{
   PlayerControllerClass=class'UDNGame.UDNPlayerController'
   DefaultPawnClass=class'UDNGame.UDNPawn'
   HUDType=class'UDNGame.UDNHUD'
   bDelayedStart=false
}

컴파일하기


ALERT! 중요: 스크립트를 컴파일하려면 에디터를 닫아야 합니다. 열려있으면 지금 닫으시기 바랍니다.

게임내에서 사용가능한 게임플레이 프레임워크를 이루는 패키지속으로 언리얼스크립트 프로젝트를 컴파일하려면, 엔진은 프로젝트를 인지해야 합니다. 이 작업은 DefaultEngine.ini[UInrealEd.EditorEngine] 부분에 있는 EditPackages 배열에 프로젝트를 추가하면 됩니다. UDNGame 프로젝트 추가를 위한 구문은 다음과 같습니다.

+EditPackages=UDNGame

UDK를 사용하는 경우, 이 부분에 포함되어 있는 프로젝트가 이미 몇 개 있음을 알 수 있습니다. UTGame 이나 UTGameContent 프로젝트같은 것 말입니다. 이 프로젝트는 지우려는데, 이들은 UDK에 제공된 UT3 예제 게임일 뿐이고, 다시 강조하지만 이 예제에서의 개념은 귀하의 게임에 사용할 수 있는 베어본 프레임워크를 만들고자 함이기 때문입니다. 만들려는 게임이 UT 게임과 유샤하다면, 그 프로젝트들은 놔두고 거기서 클래스 제작을 시작해야 할 것입니다. 그것은 전적으로 만들려 하는 게임의 종류에 따라 결정내릴 사안인 것입니다. [UnrealEd.EditorEngine] 부분의 최종적인 모습은 이와 같을 것입니다:

[UnrealEd.EditorEngine]
+EditPackages=UTGame
+EditPackages=UTGameContent
+EditPackages=UDNGame

강제로 스크립트를 컴파일시키는 방법도 없지 않습니다:

  • Make 커맨드릿 실행
  • UnrealFrontend 를 통해 실행
  • 게임이나 에디터 실행

위의 방법 중 하나로 스크립트를 컴파일시킬 수 있습니다. 그러나 그냥 에디터를 실행시켜서 자동으로 스크립트를 컴파일시킬 것입니다. 에디터를 실행시키면 다음과 같은 대화창을 볼 수 있을 것입니다:

compile_auto.jpg

예를 선택하면 컴파일 프로세스 상태를 표시하는 콘솔창이 나타납니다.

compile_output.jpg

프로세스가 성공했다면 Success - 0 error(s), 0 warning(s) 메시지를 볼 수 있을 것입니다. 에러가 있었다면, 찾아 고치기 쉽도록 그 에러가 무엇인지, 발생한 줄 번호와 파일명은 무엇인지 등이 에러 메시지에 표시됩니다.

compile_error.jpg

위의 이미지에서 보면 에러가 발생한 파일명은 확실합니다. 줄 번호는 파일명 뒤의 괄호 () 안 번호입니다. 에러에 대한 설명도 잘 나타나 있습니다. 이 에러는 등호 (=) 이후에 온 항목이 잘못되었다 하고 있습니다. 오탈자때문일 수도, 선언되지 않은 변수이기 때문일 수도 있습니다. 위와 같은 절차를 통해 에러를 고치고 다시 컴파일하면 성공 메시지를 볼 수 있을 것입니다.

테스트하기


예제에서 만든 게임 프레임워크를 테스트해 보기 위해서는, 다음 두 가지 방법 중 하나를 선택할 수 있습니다:

  • 언리얼 에디터 내 테스트 맵의 WorldInfo 프로퍼티에서 PIE Gametype 을 UDNGame 으로 설정
  • DefaultGame.ini 환경설정 파일 안에 엔진의 기본 게임타입을 UDNGame 으로 설정

Map Gametype

UDNGame 게임타입을 언리얼 에디터나 엔진에서 플레이할 때 특정 맵에 사용되는 게임타입으로 설정하려면, 보기 메뉴에서 월드 프로퍼티 를 선택합니다. WorldInfo 프로퍼티창이 표시될 것입니다.

default_worldproperties.jpg

Game Type 카테고리를 펼친 다음 Gametype for PIE 프로퍼티를 찾습니다. 사용가능한 게임타입 목록에서 UDNGame 을 선택합니다.

default_selectgame.jpg

이제 Play in PIE 또는 Play from here 기능을 사용하여 에디터에서 맵을 실행할 수 있으며, 커스텀 게임타입을 사용하게 되어 완전히 콘트롤가능한 캐릭터가 3인칭 시점으로 표시될 것입니다.

default_ingame.jpg

Default Game TypeUDNGame 으로 설정하여, 이 맵이 (아래 설명된) .ini 파일의 디폴트 세팅에 관계없이 강제로 커스텀 게임타입을 사용하도록 할 수 있습니다.

Default Gametype

주: .ini 파일을 편집할 때는 에디터가 닫혀 있어야 합니다.

엔진의 디폴트 게임타입으로 UDNGame 게임타입을 설정하려면, 언리얼 설치 디렉토리 아래의 ..\[GameName]\Config 에 있는 Defaultgame.ini 환경설정 파일을 엽니다. 여기에 지정하면, 맵에 대한 게임타입 접두사가 없거나 맵에 지정된 게임타입이 없을 때 이 게임타입이 사용됩니다. [Engine.GameInfo] 부분에는 UDNGame 게임타입에 할당할 수 있는 프로퍼티가 셋 있습니다. PlayerController 클래스에 할당할 수 있는 프로퍼티도 있습니다. 마지막으로 곧 지울 UT3 예제 게임에 대해서만 사용되는 게임타입 접두사 구성용 줄도 몇 있습니다.

[Engine.GameInfo] 부분의 최종적인 모습은 이와 같습니다:

[Engine.GameInfo]
DefaultGame=UDNGame.UDNGame
DefaultServerGame=UDNGame.UDNGame
PlayerControllerClassName=UDNGame.UDNPlayerController
GameDifficulty=+1.0
MaxPlayers=32
DefaultGameType="UDNGame.UDNGame"

이제 커스텀 게임타입이 사용됩니다. (맵 URL 이나 월드 프로퍼티의 Default Game Type 을 덮어쓰지 않는 한) 언리얼 에디터의 PC 에서 플레이 기능이나 엔진에서 실행하는 맵에 대해서는, 3인칭 관점에서 완전히 제어되는 캐릭터로 나오는 것입니다.

default_ingame.jpg