UDN
Search public documentation:

UnrealScriptFoundationsKR
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 홈 > 언리얼스크립트 홈 > 언리얼스크립트 정석

언리얼스크립트 정석


문서 변경내역: Jeff Wilson 작성. 홍성진 번역.

개요


UnrealScript 언어는 언리얼 엔진으로 만든 게임에 전체 엔진을 다시 컴파일할 필요 없이 게임플레이 아이템을 새로 추가할 수 있도록 하기 위해, 전적으로 언리얼 엔진에서만 쓰이는 자체 제작 스크립팅 언어입니다. 이를 통해 게임플레이 프로그래머가 게임에서 사용되도록 특수 고안된 기능을 사용할 수 있으며, 이벤트 기반 게임플레이 제작을 훨씬 쉽고 효율적으로 구현해 낼 수 있습니다.

여기서는 클래스, 변수, 함수 등 언어적인 측면이 포함된 것을 다룰 수가 있는데, 그 자체에 대해서는 이 글에서 다루지 않습니다. 여기서는 특정 언어적인 기능을 꼭 다루지는 않아도, 특히 초기 단계에서 이해해 두면 크게 도움이 될 개념들을 더욱 비중있게 다루도록 하겠습니다. 전체적인 언어 기능에 대해서는 UnrealScript Reference KR 페이지를 참고해 주시기 바랍니다.

UnrealScript 란 무엇인가?


각 UnrealScript 파일을 종종 하나의 UnrealScript 라고도 일컫습니다. 파일 확장자가 .uc 인 단순한 텍스트 파일로, 하나의 UnrealScript 클래스에 대한 정의를 담고 있습니다. 클래스가 무엇인지, 엔진에서 어떻게 사용되는지, 새로운 클래스를 정의하는 방법은 어떻게 되는지 등에 대한 정보는 UnrealScript Classes KR 페이지를 확인해 주시기 바랍니다.

UnrealScript 는 어떤 텍스트 에디터 프로그램으로도 만들 수 있지만, 문법강조나 여러가지 UnrealScript 전용 기능이 있어 훨씬 편리한 텍스트 에디터도 있습니다. nFringe 나 WOTGreal 같은 IDE 역시 UnrealScript 를 사용해서 언리얼 프로젝트를 개발하기 위한 용도의 완벽 통합 솔루션을 제공하고 있습니다.

스크립트 이름과 위치


UnrealScript 파일의 이름은 항상 그것이 정의하는 클래스의 이름과 정확히 맞아 떨어져야 합니다. 파일 이름과 클래스 이름이 일치하지 않는 경우, 컴파일 프로세스 도중 에러가 발생할 것입니다.

UnrealScript 는 Development\Src 디렉토리 내 다양한 폴더에 들어 있습니다. 이 디렉토리는 디폴트로 Core, Engine, UDKBase, UnrealEd 등 여러 폴더를 포함하고 있으며, 이들 각각은 각기 다른 UnrealScript 프로젝트나 패키지를 나타냅니다. 그 각각 패키지 폴더 속의 Classes 라는 폴더에는, 해당 패키지에 속하는 모든 UnrealScript 가 들어 있습니다.

자신의 게임에 자체 제작한 커스텀 UnrealScript 를 엔진이 사용하게 하려면, Development\Src 디렉토리 안에다 Classes 폴더가 들어 있는 패키지 폴더를 하나 이상 새로 만들어야 합니다. 이 폴더(들) 안에 자신의 UnrealScript 를 넣으면 됩니다.

커스텀 UnrealScript 패키지를 엔진에서 사용할 수 있도록 준비하는 법에 대한 정보는 Custom UnrealScript Projects KR 페이지를 참고해 주시기 바랍니다.

UnrealScript 해부도


MyActor.uc
  
  /*********************
   * Class Declaration *
   *********************/
  
  
  class MyActor extends Actor;
  
  
  /************************************
   * Instance Variables/Structs/Enums *
   ************************************/
  
  
  enum MyEnum
  {
  	ME_None.
  	ME_Some,
  	ME_All
  }
  
  struct MyStruct
  {
  	var int IntVal;
  	var float FloatVal;
  }
  
  var int IntVar;
  
  var float FloatVar;
  
  var bool BoolVar;
  
  var Actor ActorVar;
  
  var MyEnum EnumVar;
  
  var MyStruct StructVar;
  
  
  /**********************
   * Functions & States *
   **********************/
  
  
  function MyFunction()
  {
  	local int TempInt;
  
  	if(ActorVar != none && BoolVar)
  	{
  		TempInt = IntVar;
  	}
  }
  
  state NewState
  {
  	function MyFunction()
  	{
  		local float TempFloat;
  
  		if(ActorVar != none && BoolVar)
  		{
  			TempFloat = FloatVar;
  		}
  	}
  }
  
  
  /**********************
   * Default Properties *
   **********************/
  
  
  defaultproperties
  {
  	IntVar=5
  	FloatVar=10.0
  	BoolVar=true
  }
  

Class Declaration 클래스 선언
모든 UnrealScript 는 클래스 선언으로 시작합니다. 여기서 이 스크립트에 연결된 클래스 이름이 무엇인지, 그게 어떤 클래스를 상속했는지를 나타내고, 선언 말미에 추가되는 옵션인 클래스 지정자를 통해 여러가지 다른 면을 제어할 수도 있습니다. 이 클래스 선언 부분이, 클래스 시작 부분에 저작권 명시 등 추가하고자 하는 코멘트보다 앞서서, 먼저 와야 합니다.
Instance Variables/Structs/Enums 인스턴스 변수/구조체/열거형
인스턴스 변수 선언이 클래스 선언 뒤에 옵니다. 어떤 프로퍼티가 포함되는지 클래스한테 알려줍니다.
Functions & States 함수와 스테이트(상태)
함수와 스테이트 선언은 클래스의 대부분을 차지합니다. 클래스가 어떠한 동작을 할 수 있는지를 나타냅니다.
Default Properties 디폴트 프로퍼티
defaultproperties 블록은 항상 스크립트의 마지막을 장식합니다. 클래스에서 선언된 어느 인스턴스 변수든 그 기본값을 지정하는 곳입니다.

스크립트와 클래스 대 오브젝트와 액터


클래스란 본질적으로 게임에서 엔진이 사용할 아이템에 대한 청사진입니다. 그 아이템이 가질 수 있는 프로퍼티와 행위를 정의하는데, 바로 변수함수 를 통해서죠. 이러한 청사진은 그 아이템에 대한 개별적이고 고유한 인스턴스를 만드는 데 사용됩니다. 한 클래스의 고유 인스턴스는 일반적인 객체 지향형 프로그래밍 계에서 오브젝트라 불립니다. 이 개념이 UnrealScript 에서는 더욱 강화되었는데, Object 가 바로 계층구조에서 베이스 클래스이기도 하기 때문입니다. 즉 모든 클래스는 어느 시점에서든 Object 를 상속하기 때문에, 어느 클래스의 어느 인스턴스도 엄밀히 말하면 오브젝트인 것입니다. 오브젝트에는 월드의 다른 플랙 캐논(Flak Cannon) 인스턴스에 영향을 끼치지 않고 수정할 수 있는 프로퍼티, 실행할 수 있는 행위나 동작을 자체적으로 갖고 있습니다.

자, 그러면 클래스가 청사진이고 오브젝트는 인스턴스라는데, 그게 실제로 무슨 뜻일까요? 언리얼 토너먼트에서 쉽게 찾을 수 있는 무기인 플랙 캐논을 예로 들어보겠습니다. 플랙 캐논에는 탄환이 있어, 주 모드로는 작은 탄환이 여러 발 나가며, 부 모드로는 커다란 탄환이 한 발 발사됩니다. 이런 게 (과도히 단순화시킨) 플랙 캐논 청사진, 또는 Flak Cannon 클래스입니다. 이제 플레이어가 월드의 플랙 캐논 픽업 위를 뛰어가면, 그 플레이어한테는 플랙 캐논 인스턴스가 생깁니다. 이게 실제로 무슨 뜻일까요? Flak Cannon 클래스를 청사진으로 해서 새로운 플랙 캐논을 만들었다는 뜻입니다. 다른 플랙 캐논과는 무관한, 자체적인 프로퍼티 값을 가진 별도의 플랙 캐논을 만든 것입니다. 즉 플레이어가 발사하면 다른 어떤 것도 아닌 바로 그 플랙 캐논 인스턴스의 탄환 수가 감소되고 탄환이 발사되는 것입니다.

이것이 객체 지향형 프로그래밍의 근본 개념으로, UnrealScript 프로그래밍을 깊게 파고들어가기 전에 이해해야 할 것입니다. 말씀드렸듯 그에 대해서는 훨씬 자세히 다룬 자료가 많이 있으니, 여기서는 더욱 자세히 들어가지 않도록 하겠습니다.

스크립트 사이의 커뮤니케이션


즉 UnrealScript 에 클래스를 만든다면, 월드는 클래스 자체가 아닌 인스턴스로 채워지는 것인데, 어떻게 하나의 스크립트가 다른 것과 서로 통신할 수 있는 것일까요? 클래스 작성 당시에는 월드에 어떤 인스턴스가 존재하게 될지 알지 못합니다. 그에 대한 예로 위에서 플랙 캐논을 들었던 같은 플레이어를 들어보겠습니다. 이 플레이어가 어떻게 다른 플랙 캐논은 아닌 딱 그 플랙 캐논한테만 발사 명령을 내릴 수 있을까요? 답은 리퍼런스 (reference, 참조) 입니다. UnrealScript 의 변수 종류 중에는 오브젝트 리퍼런스 데이터 종류가 있습니다. 종류가 Object (또는 Actor) 인 변수를 편하게 부르는 이름인데요. 오브젝트 리퍼런스 변수의 값은 해당 오브젝트 종류의 인스턴스로의 리퍼런스 입니다. 예를 들어 Controller 클래스에는 현재 제어중인 Pawn 을 리퍼런스하(가리키)는 데 사용되는 변수가 있습니다. 이 변수의 종류가 Pawn (이며 그 이름도 우연찮게 Pawn) 입니다. 이 변수에 대한 선언은 이런 식입니다:

  var Pawn Pawn;
  

위의 플랙 캐논 예에서, 플레이어에 대한 클래스(, 여기서는 실제로 Pawn)에는 플레이어가 현재 사용중인 무기로의 리퍼런스를 담을 수 있는 변수가 있습니다. 이 변수는 플레이어가 그 무기 픽업 위로 달려 플레이어한테 주어진 플랙 캐논 인스턴스를 가리킵니다. 이것이 그 인스턴스와 직접 통신하는 수단이 될 수 있습니다.

이는 UnrealScript 에서 실제로 작업할 모든 것에 대한 중심이 되는, 매우 중요한 개념입니다. 한 오브젝트가 다른 오브젝트의 프로퍼티 값을 접근해야 하거나, 다른 오브젝트한테 어떤 동작을 하라고 하려는 경우, 그 오브젝트는 다른 것으로의 리퍼런스가 필요합니다. 물론, 월드 내 통신하려는 인스턴스로의 리퍼런스를 어떻게 구하지? 의문이 들 것입니다. 단순히 리퍼런스에 대한 오브젝트 리퍼런스 변수를 선언하는 것만으로는 충분치 않습니다. 오브젝트 리퍼런스 변수는 그저 리퍼런스를 담는 그릇에 지나지 않거든요. 거기에 무언가 지정하기 전까지 그 값은 none 입니다. 그 변수에 실제로 원하는 값, 리퍼런스하려는 인스턴스를 채워줘야 합니다. 아쉽게도 여기엔 확고부동한 규칙이 있습니다. 월드 내 오브젝트의 인스턴스에 대한 리퍼런스를 구하는 방법은 여러가지 있으며, 그 방법은 전적으로 두 오브젝트 사이의 특정 관계와 상황에 따라 달라집니다. 예를 조금 들자면:

  • 헬퍼 함수 (Helper function) - 공통으로 사용되는 오브젝트로의 리퍼런스를 반환하는 특정 클래스에 헬퍼 함수가 제공되는 경우가 있습니다. 예를 들어 GFxMoviePlayer 클래스에는 무비를 소유하는 PlayerController 로의 리퍼런스를 반환하는 =GetPC() 함수가 있습니다.
  • 이벤트 (Event) - 한 액터는 Touch 이벤트처럼 다른 곳의 이벤트가 발동되도록 만듭니다. 많은 경우 이벤트를 트리거하는 액터는 엔진이 해당 이벤트로 자동 전송해 주는 리퍼런스가 있습니다. 이것을 이벤트 안에서 사용하거나, 변수에 저장해 나중에 사용할 수 있습니다.
  • 스폰 (Spawn) - Spawn() 함수는 만드는 액터로의 리퍼런스를 반환합니다. 한 액터가 다른 액터를 스폰시킨 후 즉시든 나중에든 스폰시킨 액터와 통신할 수 있으려면, Spawn() 함수가 반환한 리퍼런스를 사용하거나, 그 리퍼런스를 변수에 저장하면 됩니다.
  • 제삼자 (Third-party) - 다른 클래스 내에 이미 리퍼런스되고있는 오브젝트를 리퍼런스해야 하는 경우가 꽤 자주 있습니다. 그 리피런스를 빌려 용도에 맞게 쓰면 되겠죠. 이런 것이 흔히 필요하고 사용되는 곳은 현재 게임타입, 즉 GameInfo 클래스의 인스턴스 입니다. 게임타입을 직접 리퍼런스하는 유일한 클래스는 WorldInfo, 그 Game 변수를 통해서인데, 다른 위치에서 접근해야 할 때가 종종 있습니다. 다행히도 모든 Actor 에는 WorldInfo 변수를 통해 현재 WorldInfo 인스턴스로의 리퍼런스를 갖습니다. 즉 GameInfo 인스턴스로의 리퍼런스 접근 권한을 얻기 위해서 WorldInfo 리퍼런스를 사용하면 된다는 뜻입니다. 이 방법은 막 시작하신 분들이 자주 간과하는 방법입니다. 찾고 있는 것에 대한 리퍼런스가 이미 있을 수가 있으니, 어떤 오브젝트가 리퍼런스되고 있는지 항상 확인해 주십시오.
  • 반복처리 (Iteration) - 반복처리 함수는 검색같은 것을 할 때 사용합니다. 일정 (반복) 범위를 지정해서 그 결과를 오브젝트에 대한 리퍼런스 형태로 반환합니다. 그에 대한 작업을 이터레이터에서 바로 할 수도 있고, 변수에 저장하여 나중에 사용할 수도 있습니다.
  • 에디터 - 경우에 따라서는 레벨 디자이너가 에디터에서 액터에 대한 리퍼런스를 설정하도록 놔둘 수도 있습니다. 그를 위해서는 리퍼런스를 담기 위한 수정가능 변수를 만들기만 하면 됩니다.

오브젝트 리퍼런스가 생기면, 종종 점 표기법 (dot notation)이라 불리는 특수 문법을 통해 (protected/private 로 지정된 것만 아니면) 해당 오브젝트에 속하는 변수와 함수에 접근할 수 있습니다. 점 표기법이라는 것은, 오브젝트 리퍼런스 뒤에 점 또는 마침표(.)를 찍은 뒤 접근 또는 실행하려는 변수나 함수 이름을 써 주는 것을 말합니다.

위에서 언급한 Controller 의 Pawn 을 들어 봅시다. Controller 의 Pawn 에 있는 Health 변수를 접근하려면, 이렇게 하면 됩니다:

  Pawn.Health
  

비슷하게 Pawn 한테 주무기를 발사하라 이르고 싶은 경우, StartFire() 함수를 사용하면 되겠지요:

  Pawn.StartFire(0);
  

오브젝트 리퍼런스 변수가 오브젝트에 대한 리퍼런스를 담기는 하지만, 오브젝트를 리퍼런스하는 데 꼭 오브젝트 리퍼런스 변수가 필요한 것은 아닙니다. 헬퍼 함수의 결과를 변수에 할당하여 그것을 리퍼런스로 사용할 수도 있지만, 그냥 그 함수의 결과를 바로 사용하는 것도 가능합니다.

이에 대한 대모로 위에 언급한 GFxMoviePlayer 클래스의 GetPC() 함수를 사용해 봅시다. 이 함수는 스케일폼 무비를 소유하는 PlayerController 를 반환합니다. 자 그럼 PlayerController 가 조정하는 Pawn 에 담겨있는 현재 플레이어 체력에 접근해 봅시다. 위에서처럼 콘트롤러에 있는 Pawn 변수를 사용할 수도 있지만, 이번에는 GFxMoviePlayer 클래스 안에 있으니 먼저 콘트롤러를 리퍼런스해(가리켜) 줘야 합니다. 이 리퍼런스를 변수에 저장할 필요는 없습니다. 그냥 GetPC() 함수 호출시 직접 점 표기법을 사용해 주면 됩니다.

  GetPC().Pawn.Health
  

이제 플레이어의 무기를 발사하는 버튼이 UI 에 있다고 가정합시다. 방금 했던 것처럼 하면 됩니다.

  GetPC().Pawn.StartFire(0);
  

이것이 가능한 이유는 GetPC() 자체가 본질적으로는 PlayerController 에 대한 리퍼런스라 생각할 수 있기 때문입니다. GFxMoviePlayer 클래스에서 그 함수의 반환값으로 선언된 부분을 보면 알 수 있습니다:

  /**
   * 이 무비를 소유하는 플레이어 콘트롤러를 구하는 헬퍼 함수
   *
   * @return 이 무비를 소유하는 LocalPlayerOwnerIndex 에 상응하는 PlayerController 반환
   */
  event PlayerController GetPC()
  {
  	local LocalPlayer LocalPlayerOwner;
  
  	LocalPlayerOwner = GetLP();
  	if (LocalPlayerOwner == none)
  	{
  		return none;
  	}
  	return LocalPlayerOwner.Actor;
  }
  

기존 스크립트 사용 방법


언리얼 엔진에는 Development\Src 디렉토리의 여러 패키지에 이미 엄청난 양의 UnrealScript 클래스가 구현되어 있습니다. 이 클래스들은 다 무엇이고, 게임에서 어떻게 사용할 수 있는지 이해하는 것이 중요합니다. 이 클래스들이 제공되는 일차적인 이유는, 기본적이고 범용적인 함수성을 제공하기 위해서입니다. 가끔 그 구분이 모호하기는 하지만, 본질적으로 엔진 자체의 일부이지 "게임"의 일부는 아닙니다. 그를 이루는 주요 시스템과 클래스 다수에 대해서는 여기 UDN 의 여러가지 "테크니컬 가이드"에 설명되어 있습니다. 사용할 수 있는 클래스 전부를 더욱 완벽히 이해하려면, 스스로 코드를 분석하고 주석을 확인하는 등 실제 스크립트를 파 보는 연구를 해야 할 수도 있습니다. 이런 작업에 아주 좋은 툴로는 UnCodeX 라는 것이 있습니다. 스크립트에서 자바문서 스타일의 문서를, 쉽게 살펴볼 수 있는 형태로 만들어 줍니다.

가장 먼저 이해할 점은, 일반적인 경우 기존 스크립트를 수정하지는 말아야 한다는 점입니다. 네이티브 클래스나 네이티브 패키지 안의 클래스를 수정하고도 엔진을 다시 컴파일하지 않으면 심각한 결과를 초래할 수 있으며, 보통은 게임 실행 도중 엔진에 크래시가 나게 됩니다.

UnrealScript 는 객체 지향형인데, 다른 클래스를 상속, UnrealScript 문법으로는 확장 (extend)하는 각 클래스는 부모-자손 관계를 이룬다는 뜻입니다. 자손 클래스는 부모 클래스에 있는 모든 변수, 함수, 스테이트 등을 상속받습니다. 즉 자신의 커스텀 클래스를 만들 때, 기존 스크립트를 수정하기 보다는 확장해서 그것을 시작점으로 삼아 작업하는 것이 좋습니다. 이게 좋은 점은, 부모 클래스에서 자손 클래스로 전해내리는 것이 제한이 없어 원하는 대로 변수나 함수를 추가할 수 있을 뿐만 아니라, UnrealScript 는 원하는 동작을 정확히 할 수 있도록 어떤 상속 함수도 덮어쓸 수 있으니 부모 클래스에서 전해내려줄 함수를 구현하는 방식에도 제한이 없다는 데 있습니다.

어떤 경우에는 클래스를 확장하기만 하고 전혀 수정하지 않는 것 자체가 제약이 아닌가 싶을 수도 있습니다. 흔히 모든 자손 클래스에서 사용할 수 있도록 기존의 베이스 클래스에 변수를 추가하는 것이 낫다고 생각하기 때문입니다. 그러나 기존 클래스를 수정하지 않고도 시스템을 디자인할 수 있는 대안은 얼마든지 있게 마련입니다.

요점: 기존 클래스는 절대 수정하지 마시고, 확장해서 함수성을 덮어쓰시기 바랍니다.

확장할 클래스


UnrealScript 의 클래스 디자인에 있어 전형적인 접근법은, (이를테면 모든 몬스터의 베이스 클래스인 Pawn 클래스처럼) 필요한 함수성 대부분이 있는 기존 클래스를 확장해서 (예로 Minotaur 몬스터) 클래스를 새로 만드는 것입니다. 이러한 접근법으로 틀을 새로 발명할 필요는 전혀 없이, 바꿀 필요가 없는 함수성은 모두 그대로 놔두고서 특화시키고자 하는 함수성만 새로 추가해 주기만 하면 됩니다. 이러한 접근법은 특히나 언리얼의 UI 를 구현할 때 좋은데, 내장 AI 시스템에는 커스텀 크리처에 레고 블록처럼 사용할 수 있는 기본 함수성이 엄청 많기 때문입니다.

계층구조에서 얼마나 높이 확장해 올라갈 지는 상황에 따라 크게 달라집니다. 불필요한 것들을 대량으로 덮어쓰지 않기 위해 어떤 함수성을 다시 구현해야 하느냐 마느냐 선택의 기로에 놓이는 것도 다반사입니다. 어떠한 방식이 더 나을지는 전적으로 자신의 선택에 달렸습니다. 가장 바람직한 것이라면, 다시금 강조하지만, 가장 비슷한 클래스 또는 구현하려는 클래스와 공통점이 가장 많은 클래스를 선택하여 거기서부터 시작하는 것입니다.

오브젝트 계층구조


UnrealScript 작업을 시작하기 전, 언리얼 내 오브젝트 사이의 하이 레벨 관계를 이해하는 것이 중요합니다. 언리얼의 구조는 다른 대다수 게임의 구조에서 주로 출발한 것입니다. 언리얼은 오브젝트 도표, 직렬화(serialization), 오브젝트 수명, 다형성(polymorphism)과 같은 하이 레벨 객체 지향 개념을 지원하여 잘 정의된 오브젝트 모델이라는 점에서, (COM/ActiveX 와 매우 유사하게) 순수히 객체 지향적입니다. 역사적으로 대부분의 게임은 주요 함수성이 오브젝트 레벨에서 하드코딩되어 확장불가능한 단일 구조로 디자인되어 왔습니다. Doom 이나 Quake 같은 게임은 콘텐츠 레벨에서의 확장성이 매우 뛰어나기는 했지만 말입니다. 언리얼의 객체 지향 형태에는 큰 장점이 있습니다. 주요 함수성과 오브젝트 종류를 Unreal 에 실시간으로 추가할 수 있으며, (이를테면) 기존 코드를 다수 수정하지 않고도 서브클래싱 형태를 취해 확장할 수 있다는 것입니다. 이러한 형태의 확장성은, 언리얼 커뮤니티가 만들어 내는 추가 기능이 모두 상호작용할 수 있도록 북돋워 주기에, 매우 강력한 면을 보입니다.

Object
언리얼 내 모든 클래스의 부모 클래스입니다. Object 클래스의 모든 함수는 어디서나 접근할 수 있는데, 모든 것이 Object 에서 파생되기 때문입니다. Object 는 실제로 무언가를 전혀 하지는 않는다는 점에서 추상 베이스 클래스입니다. 모든 함수성은 (텍스처 맵인) Texture, (텍스트 단락인) TextBuffer, (다른 오브젝트의 클래스를 설명하는) Class 와 같은 서브클래스가 제공합니다.
Actor (extends Object)
언리얼 내 모든 독립형 게임 오브젝트의 부모 클래스입니다. Actor 클래스에는 액터 이동, 다른 액터와의 상호작용, 환경과의 교류, 기타 게임 관련된 작업을 하는 데 필요한 함수성이 모두 들어 있습니다.
Pawn (extends Actor)
언리얼 내 모든 크리처와 플레이어의 부모 클래스로, 하이 레벨 AI 와 플레이어 콘트롤 기능을 합니다.
Class (extends Object)
오브젝트 클래스를 설명하는 특수한 종류의 오브젝트입니다. 처음에는 헛갈릴 수 있습니다. 클래스란 오브젝트이며, 특정 오브젝트를 설명하는 클래스이기도 하다는 것. 그러나 개념은 확실하며, Class 오브젝트를 다루는 경우는 많이 있습니다. 예를 들어 UnrealScript 로 액터를 새로 스폰할 때, 그 액터의 클래스를 Class 오브젝트로 지정할 수 있습니다.

UnrealScript 로는 어느 Object 클래스 코드도 작성할 수도 있지만, 99% 경우 Actor 에서 파생되는 클래스 코드를 작성하게 됩니다. 대부분의 유용한 UnrealScript 함수성은 게임 관련된 것으로, 액터를 다룹니다.

UnrealScript 프로그래밍 전략


함정을 피하면서 UnrealScript 의 강점을 제대로 활용하고, UnrealScript 코드를 효율적으로 작성하는 법에 대한 꼼수입니다.

  • UnrealScript 는 C/C++ 에 비하면 느린 언어입니다. 전형적인 C++ 프로그램의 실행 속도는 UnrealScript 보다 20 배는 빠릅니다. 저희 모든 스크립트 작성시의 기본 철학은 이렇습니다: 거의 항상 빈둥대는 스크립트만 작성한다. 다른 말로 하면, 언리얼의 물리 코드가 처리할 수 있는 기본 운동같은 반복작업이 아닌, 커스터마이징할 수 있는 "유의미한" 이벤트를 처리하는 데만 UnrealScript 를 사용한다는 것입니다. 예를 들어 프로젝타일(발사체) 스크립트를 작성할 때는 보통, 핵심적인 이벤트가 발생했을 때를 설명하는 HitWall(), Bounce(), Touch() 함수를 작성합니다. 고로 총 시간 중 95% 동안 프로젝타일 스크립트는 아무런 코드도 실행하지 않으면서, 그저 물리 코드가 이벤트을 알려 주기만을 기다립니다. 그렇기에 매우 효율적입니다. UnrealScript 가 C++ 보다 훨씬 느리기는 하지만, 보통의 레벨에서 UnrealScript 가 실행되는 시간은 CPU 시간의 평균 5-10% 에 불과합니다.
  • (FinishAnim 이나 Sleep 같은) 잠복성(latent) 함수를 가급적 많이 사용하십시오. 그 곳을 스크립트 실행 흐름의 근거지로 삼으므로써 애니메이션 주도형 또는 시간 주도형 코드를 만들 수 있으며, 이는 UnrealScript 에서 꽤나 효율적입니다.
  • 스크립트를 테스트할 때는 언리얼 로그를 주시하십시오. UnrealScript 런타임은 발생한 문제 중 치명적이지는 않은 것들을 로그에 경고로 띄워 주니 유용하게 쓸 수 있습니다.
  • 무한 재귀(recursion)에 빠질 수 있는 코드를 조심하십시오. 예를 들어 "Move" 명령은 액터를 움직이다가 뭔가에 걸리면 Bump() 함수를 호출합니다. 그러므로 Bump 함수 안에서 Move 명령을 사용한다면 무한 재귀될 소지가 있으니, 조심하십시오. 무한 재귀와 무한 루핑은 UnrealScript 가 알아서 처리하지 못하는 두 가지 에러 조건입니다.
  • 액터의 스폰과 소멸(destroy)은 서버측에서 꽤나 비싼 동작으로, 네트워크 게임에서는 네트워크 대역폭을 차지하니 훨씬 비싸집니다. 적절히 사용해 주시고, 액터는 "중량급" 오브젝트로 간주하는 것이 좋습니다. 예를 들어 파티클 시스템에 고유 액터를 100 개쯤 스폰시키고, 그걸 피직스 코드를 통해 다른 탄도체에 실어 보내지 마십시오. 매우~~~ 느립니다.
  • UnrealScript 의 객체 지향성을 가급적 많이 활용하십시오. 기존 함수와 스테이트를 덮어써서 새로운 함수성을 만들면, 다른 사람의 작업과 통합하기도 수정하기도 쉬운 깨끗한 코드가 됩니다. 액터의 클래스나 스테이트에 따라 switch() 문을 사용하는 등, 전통적인 C 기법은 피하십시오. 새로운 클래스를 추가하거나 뭔가 수정하면 깨지기 쉬운 코드가 됩니다.
  • UnrealScript .u 패키지는 .ini 파일의 EditPackages 목록에 지정된 순서대로 엄격히 컴파일됩니다. 그렇기에 각 패키지는 자신과 이전에 컴파일된 패키지에 있는 오브젝트만 참조할 수 있고, 아직 컴파일되지 않은 패키지에 있는 것은 안됩니다. 패키지 사이에 순환 참조를 할 필요가 있는 경우, 해법은 두 가지 있습니다:
    1. 처음 컴파일되는 .u 패키지에 베이스 클래스 세트를, 두 번째 컴파일되는 .u 패키지에 자손 클래스 세트를 넣고, 베이스 클래스는 절대로 자손 클래스를 참조하지 못하도록 합니다. 괜찮은 프로그래밍 꼼수긴 한데, 보통 돌아갑니다.
      주: 어떤 클래스 C 가 나중에 컴파일되는 패키지에 있는 오브젝트 O 를 참조해야 한다 칩시다. 해당 클래스는 두 부분으로 넣을 수 있습니다: 첫 패키지에는 MyO 변수를 정의하는(, 그러면서 defaultproperties 에 MyO 의 디폴트 값이 들어있지는 않은) 추상 베이스 클래스 정의 C 를 넣고, 둘째 패키지에는 거기서만 가능한 MyO 디폴트 값을 적절히 정의하는 서브클래스 D 를 넣습니다.
    2. 두 .u 패키지가 리퍼런스로 뒤엉켜 떼려야 뗄 수 없는 경우, 하나의 패키지로 병합시킵니다. 패키지란 코드 모듈성의 한 단위로 간주되는 것이고, 나눌 수 없는 클래스 세트를 여러 패키지에 나눠 넣는다고 (메모리 절약같은) 현실적 이득도 없으니, 이치에 맞다 하겠습니다.