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
日本語訳
中国翻译
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
언리얼스크립트 정석
문서 변경내역: Jeff Wilson 작성. 홍성진 번역.
개요
UnrealScript 란 무엇인가?
.uc
인 단순한 텍스트 파일로, 하나의 UnrealScript 클래스에 대한 정의를 담고 있습니다. 클래스가 무엇인지, 엔진에서 어떻게 사용되는지, 새로운 클래스를 정의하는 방법은 어떻게 되는지 등에 대한 정보는 UnrealScript Classes KR 페이지를 확인해 주시기 바랍니다.
UnrealScript 는 어떤 텍스트 에디터 프로그램으로도 만들 수 있지만, 문법강조나 여러가지 UnrealScript 전용 기능이 있어 훨씬 편리한 텍스트 에디터도 있습니다. nFringe 나 WOTGreal 같은 IDE 역시 UnrealScript 를 사용해서 언리얼 프로젝트를 개발하기 위한 용도의 완벽 통합 솔루션을 제공하고 있습니다.
스크립트 이름과 위치
Development\Src
디렉토리 내 다양한 폴더에 들어 있습니다. 이 디렉토리는 디폴트로 Core, Engine, UDKBase, UnrealEd 등 여러 폴더를 포함하고 있으며, 이들 각각은 각기 다른 UnrealScript 프로젝트나 패키지를 나타냅니다. 그 각각 패키지 폴더 속의 Classes
라는 폴더에는, 해당 패키지에 속하는 모든 UnrealScript 가 들어 있습니다.
자신의 게임에 자체 제작한 커스텀 UnrealScript 를 엔진이 사용하게 하려면, Development\Src
디렉토리 안에다 Classes
폴더가 들어 있는 패키지 폴더를 하나 이상 새로 만들어야 합니다. 이 폴더(들) 안에 자신의 UnrealScript 를 넣으면 됩니다.
커스텀 UnrealScript 패키지를 엔진에서 사용할 수 있도록 준비하는 법에 대한 정보는 Custom UnrealScript Projects KR 페이지를 참고해 주시기 바랍니다.
UnrealScript 해부도
/********************* * 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 인스턴스 변수/구조체/열거형
- 인스턴스 변수 선언이 클래스 선언 뒤에 옵니다. 어떤 프로퍼티가 포함되는지 클래스한테 알려줍니다.
- Default Properties 디폴트 프로퍼티
-
defaultproperties
블록은 항상 스크립트의 마지막을 장식합니다. 클래스에서 선언된 어느 인스턴스 변수든 그 기본값을 지정하는 곳입니다.
스크립트와 클래스 대 오브젝트와 액터
Object
가 바로 계층구조에서 베이스 클래스이기도 하기 때문입니다. 즉 모든 클래스는 어느 시점에서든 Object
를 상속하기 때문에, 어느 클래스의 어느 인스턴스도 엄밀히 말하면 오브젝트인 것입니다. 오브젝트에는 월드의 다른 플랙 캐논(Flak Cannon) 인스턴스에 영향을 끼치지 않고 수정할 수 있는 프로퍼티, 실행할 수 있는 행위나 동작을 자체적으로 갖고 있습니다.
자, 그러면 클래스가 청사진이고 오브젝트는 인스턴스라는데, 그게 실제로 무슨 뜻일까요? 언리얼 토너먼트에서 쉽게 찾을 수 있는 무기인 플랙 캐논을 예로 들어보겠습니다. 플랙 캐논에는 탄환이 있어, 주 모드로는 작은 탄환이 여러 발 나가며, 부 모드로는 커다란 탄환이 한 발 발사됩니다. 이런 게 (과도히 단순화시킨) 플랙 캐논 청사진, 또는 Flak Cannon 클래스입니다. 이제 플레이어가 월드의 플랙 캐논 픽업 위를 뛰어가면, 그 플레이어한테는 플랙 캐논 인스턴스가 생깁니다. 이게 실제로 무슨 뜻일까요? Flak Cannon 클래스를 청사진으로 해서 새로운 플랙 캐논을 만들었다는 뜻입니다. 다른 플랙 캐논과는 무관한, 자체적인 프로퍼티 값을 가진 별도의 플랙 캐논을 만든 것입니다. 즉 플레이어가 발사하면 다른 어떤 것도 아닌 바로 그 플랙 캐논 인스턴스의 탄환 수가 감소되고 탄환이 발사되는 것입니다.
이것이 객체 지향형 프로그래밍의 근본 개념으로, UnrealScript 프로그래밍을 깊게 파고들어가기 전에 이해해야 할 것입니다. 말씀드렸듯 그에 대해서는 훨씬 자세히 다룬 자료가 많이 있으니, 여기서는 더욱 자세히 들어가지 않도록 하겠습니다.
스크립트 사이의 커뮤니케이션
Pawn
을 리퍼런스하(가리키)는 데 사용되는 변수가 있습니다. 이 변수의 종류가 Pawn
(이며 그 이름도 우연찮게 Pawn
) 입니다. 이 변수에 대한 선언은 이런 식입니다:
var Pawn Pawn;
none
입니다. 그 변수에 실제로 원하는 값, 리퍼런스하려는 인스턴스를 채워줘야 합니다. 아쉽게도 여기엔 확고부동한 규칙이 있습니다. 월드 내 오브젝트의 인스턴스에 대한 리퍼런스를 구하는 방법은 여러가지 있으며, 그 방법은 전적으로 두 오브젝트 사이의 특정 관계와 상황에 따라 달라집니다. 예를 조금 들자면:
- 헬퍼 함수 (Helper function) - 공통으로 사용되는 오브젝트로의 리퍼런스를 반환하는 특정 클래스에 헬퍼 함수가 제공되는 경우가 있습니다. 예를 들어
GFxMoviePlayer
클래스에는 무비를 소유하는PlayerController
로의 리퍼런스를 반환하는=GetPC()
함수가 있습니다. - 이벤트 (Event) - 한 액터는
Touch
이벤트처럼 다른 곳의 이벤트가 발동되도록 만듭니다. 많은 경우 이벤트를 트리거하는 액터는 엔진이 해당 이벤트로 자동 전송해 주는 리퍼런스가 있습니다. 이것을 이벤트 안에서 사용하거나, 변수에 저장해 나중에 사용할 수 있습니다. - 스폰 (Spawn) -
Spawn()
함수는 만드는 액터로의 리퍼런스를 반환합니다. 한 액터가 다른 액터를 스폰시킨 후 즉시든 나중에든 스폰시킨 액터와 통신할 수 있으려면,Spawn()
함수가 반환한 리퍼런스를 사용하거나, 그 리퍼런스를 변수에 저장하면 됩니다. - 제삼자 (Third-party) - 다른 클래스 내에 이미 리퍼런스되고있는 오브젝트를 리퍼런스해야 하는 경우가 꽤 자주 있습니다. 그 리피런스를 빌려 용도에 맞게 쓰면 되겠죠. 이런 것이 흔히 필요하고 사용되는 곳은 현재 게임타입, 즉
GameInfo
클래스의 인스턴스 입니다. 게임타입을 직접 리퍼런스하는 유일한 클래스는 WorldInfo, 그Game
변수를 통해서인데, 다른 위치에서 접근해야 할 때가 종종 있습니다. 다행히도 모든 Actor 에는WorldInfo
변수를 통해 현재WorldInfo
인스턴스로의 리퍼런스를 갖습니다. 즉GameInfo
인스턴스로의 리퍼런스 접근 권한을 얻기 위해서WorldInfo
리퍼런스를 사용하면 된다는 뜻입니다. 이 방법은 막 시작하신 분들이 자주 간과하는 방법입니다. 찾고 있는 것에 대한 리퍼런스가 이미 있을 수가 있으니, 어떤 오브젝트가 리퍼런스되고 있는지 항상 확인해 주십시오. - 반복처리 (Iteration) - 반복처리 함수는 검색같은 것을 할 때 사용합니다. 일정 (반복) 범위를 지정해서 그 결과를 오브젝트에 대한 리퍼런스 형태로 반환합니다. 그에 대한 작업을 이터레이터에서 바로 할 수도 있고, 변수에 저장하여 나중에 사용할 수도 있습니다.
- 에디터 - 경우에 따라서는 레벨 디자이너가 에디터에서 액터에 대한 리퍼런스를 설정하도록 놔둘 수도 있습니다. 그를 위해서는 리퍼런스를 담기 위한 수정가능 변수를 만들기만 하면 됩니다.
Health
변수를 접근하려면, 이렇게 하면 됩니다:
Pawn.Health
StartFire()
함수를 사용하면 되겠지요:
Pawn.StartFire(0);
GFxMoviePlayer
클래스의 GetPC()
함수를 사용해 봅시다. 이 함수는 스케일폼 무비를 소유하는 PlayerController 를 반환합니다. 자 그럼 PlayerController 가 조정하는 Pawn 에 담겨있는 현재 플레이어 체력에 접근해 봅시다. 위에서처럼 콘트롤러에 있는 Pawn
변수를 사용할 수도 있지만, 이번에는 GFxMoviePlayer
클래스 안에 있으니 먼저 콘트롤러를 리퍼런스해(가리켜) 줘야 합니다. 이 리퍼런스를 변수에 저장할 필요는 없습니다. 그냥 GetPC()
함수 호출시 직접 점 표기법을 사용해 주면 됩니다.
GetPC().Pawn.Health
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 는 원하는 동작을 정확히 할 수 있도록 어떤 상속 함수도 덮어쓸 수 있으니 부모 클래스에서 전해내려줄 함수를 구현하는 방식에도 제한이 없다는 데 있습니다.
어떤 경우에는 클래스를 확장하기만 하고 전혀 수정하지 않는 것 자체가 제약이 아닌가 싶을 수도 있습니다. 흔히 모든 자손 클래스에서 사용할 수 있도록 기존의 베이스 클래스에 변수를 추가하는 것이 낫다고 생각하기 때문입니다. 그러나 기존 클래스를 수정하지 않고도 시스템을 디자인할 수 있는 대안은 얼마든지 있게 마련입니다.
요점: 기존 클래스는 절대 수정하지 마시고, 확장해서 함수성을 덮어쓰시기 바랍니다.
확장할 클래스
오브젝트 계층구조
- Object
- 언리얼 내 모든 클래스의 부모 클래스입니다.
Object
클래스의 모든 함수는 어디서나 접근할 수 있는데, 모든 것이Object
에서 파생되기 때문입니다.Object
는 실제로 무언가를 전혀 하지는 않는다는 점에서 추상 베이스 클래스입니다. 모든 함수성은 (텍스처 맵인)Texture
, (텍스트 단락인)TextBuffer
, (다른 오브젝트의 클래스를 설명하는)Class
와 같은 서브클래스가 제공합니다. - Actor (extends Object)
- 언리얼 내 모든 독립형 게임 오브젝트의 부모 클래스입니다.
Actor
클래스에는 액터 이동, 다른 액터와의 상호작용, 환경과의 교류, 기타 게임 관련된 작업을 하는 데 필요한 함수성이 모두 들어 있습니다. - Pawn (extends Actor)
- 언리얼 내 모든 크리처와 플레이어의 부모 클래스로, 하이 레벨 AI 와 플레이어 콘트롤 기능을 합니다.
- Class (extends Object)
- 오브젝트 클래스를 설명하는 특수한 종류의 오브젝트입니다. 처음에는 헛갈릴 수 있습니다. 클래스란 오브젝트이며, 특정 오브젝트를 설명하는 클래스이기도 하다는 것. 그러나 개념은 확실하며,
Class
오브젝트를 다루는 경우는 많이 있습니다. 예를 들어 UnrealScript 로 액터를 새로 스폰할 때, 그 액터의 클래스를Class
오브젝트로 지정할 수 있습니다.
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 목록에 지정된 순서대로 엄격히 컴파일됩니다. 그렇기에 각 패키지는 자신과 이전에 컴파일된 패키지에 있는 오브젝트만 참조할 수 있고, 아직 컴파일되지 않은 패키지에 있는 것은 안됩니다. 패키지 사이에 순환 참조를 할 필요가 있는 경우, 해법은 두 가지 있습니다:
- 처음 컴파일되는 .u 패키지에 베이스 클래스 세트를, 두 번째 컴파일되는 .u 패키지에 자손 클래스 세트를 넣고, 베이스 클래스는 절대로 자손 클래스를 참조하지 못하도록 합니다. 괜찮은 프로그래밍 꼼수긴 한데, 보통 돌아갑니다.
주: 어떤 클래스C
가 나중에 컴파일되는 패키지에 있는 오브젝트O
를 참조해야 한다 칩시다. 해당 클래스는 두 부분으로 넣을 수 있습니다: 첫 패키지에는MyO
변수를 정의하는(, 그러면서 defaultproperties 에MyO
의 디폴트 값이 들어있지는 않은) 추상 베이스 클래스 정의C
를 넣고, 둘째 패키지에는 거기서만 가능한MyO
디폴트 값을 적절히 정의하는 서브클래스D
를 넣습니다. - 두 .u 패키지가 리퍼런스로 뒤엉켜 떼려야 뗄 수 없는 경우, 하나의 패키지로 병합시킵니다. 패키지란 코드 모듈성의 한 단위로 간주되는 것이고, 나눌 수 없는 클래스 세트를 여러 패키지에 나눠 넣는다고 (메모리 절약같은) 현실적 이득도 없으니, 이치에 맞다 하겠습니다.
- 처음 컴파일되는 .u 패키지에 베이스 클래스 세트를, 두 번째 컴파일되는 .u 패키지에 자손 클래스 세트를 넣고, 베이스 클래스는 절대로 자손 클래스를 참조하지 못하도록 합니다. 괜찮은 프로그래밍 꼼수긴 한데, 보통 돌아갑니다.