언리얼 스크립트

필수 문법 소개 & 스크립트 버그 소개 & 팁

구조

Class

5102096957_940ff54d32_o.jpg

Property

5102699128_ae7eb8c817_o.jpg

문법

언리얼스크립트에서 특히나 알아두면 좋은것 몇개 소개해보겠다.

변수

수학함수

float Abs( float A );                  // Returns the absolute value of the number.
float Sin( float A );                  // Returns the sine of the number expressed in radius.
float Cos( float A );                  // Returns the cosine of the number expressed in radians.
float Tan( float A );                  // Returns the tangent of the number expressed in radians.
float ASin( float A );                 // Returns the inverse sine of the number expressed in radius.
float ACos( float A );                 // Returns the inverse cosine of the number expressed in radius.
float Atan( float A );                 // Returns the inverse tangent of the number expressed in radians.
float Exp( float A );                  // Returns the constant "e" raised to the power of A.
float Loge( float A );                 // Returns the log (to the base "e") of A.
float Sqrt( float A );                 // Returns the square root of A.
float Square( float A );               // Returns the square of A = A*A.
float FRand();                         // Returns a random number from 0.0 to 1.0.
float FMin( float A, float B );        // Returns the minimum of two numbers.
float FMax( float A, float B );        // Returns the maximum of two numbers.
float FClamp( float V, float A, float B );    // Returns the first number clamped to the interval from A to B.
float Lerp( float A, float B, float Alpha );  // Returns the linear interpolation between A and B.
float Smerp( float Alpha, float A, float B ); // Returns an Alpha-smooth nonlinear interpolation between A and B.
float Ceil ( float A );                // Rounds up
float Round ( float A );               // Rounds normally

참조 : https://udn.epicgames.com/Three/UnrealScriptReference

자료구조

  • Map
    // Input.uc
    var native const Map{FName,void*}     NameToPtr;

연산자

언리얼엔진3 스크립트에 미리 정의된 @, $=, ^ 등의 연산자에 대한 설명

UDN 페이지를 참조

정리

스크립트를 사용하다 보면 다양한 키워드를 볼 수 있는데 여기서는 그 키워드가 어떠한 의미를 가지고 있는지를 정리해두겠다.

config vs globalconfig

의미

config 와 globalconfig 는 변수 앞에 선언되며 서로 다른 의미를 가진다. 아래의 예제 코드를 보자.

// Destination.uc
class Destination;
 
var       config float         Distance;
var globalconfig float   GlobalDistance;
 
 
// Seoul.uc
class Seoul extends Destination;
 
// Pusan.uc
class Pusan extends Destination;

Destination 이란 parent class 가 있고 이 안에는 config, globalconfig 속성의 변수가 있다. 그리고 이것을 상속받는 Seoul 과 Pusan class 가 있다.

이 둘의 용법을 가르는 가장 큰 차이는 바로 설정파일에서 값의 override 가능 여부이다. 풀어서 설명하면 아래와 같다.

  • config 속성을 가지는 Distance 의 경우 값을 Seoul 과 Pusan 에서 각각 override 할 수 있다.
  • globalconfig 속성을 가지는 GlobalConfig 의 경우 값을 override 할 수 없다. 즉, Destination 의 값을 그대로 물려받는다. (1 Depth 까지만 물려받는 듯 하다. 2 Depth 부터는 테스트해보질 않아서 장담은 못함)

좀 더 이해를 돕기 위해 속성파일과 코드를 예로 들어보겠다.

// Config.ini, 속성파일
[Example.Destination]
Distance=20
GlobalDistance=100
 
[Example.Seoul]
Distance=40
GlobalDistance=200
 
[Example.Pusan]
Distance=60
GlobalDistance=300
 
 
// MyGameInfo.uc
class MyGameInfo extends GameInfo;
 
var Destination        Dest;
var Seoul              Seo;
var Pusan              Pus;

상기와 같이 Config.ini 라는 설정파일이 있고 MyGameInfo.uc 에서 정의된 게임이 불러지면

  • Dest.Distance 는 20, Dest.GlobalDistance 는 100 의 값을 가지고
  • Seoul.Distance 는 40, Seoul.GlobalDistance 는 100 의 값을 가지고
  • Pusan.Distance 는 60, Pusan.GlobalDistance 는 100 의 값을 가진다.

즉, globalconfig 는 child class 에서 값을 override 할 수 없다.

코드

이를 증명하는 코드는 아래와 같다.

// UnObj.cpp
void UObject::LoadConfig(
    UClass* ConfigClass/*=NULL*/,
    const TCHAR* InFilename/*=NULL*/,
    DWORD PropagationFlags/*=LCPF_None*/,
    UProperty* PropertyToLoad/*=NULL*/ )
{
 
    ...
 
    const UBOOL bGlobalConfig = ( Property->PropertyFlags & CPF_GlobalConfig ) != 0;    // GlobalConfig flag 를 알아내고
    UClass* OwnerClass = Property->GetOwnerClass();
 
    UClass* BaseClass = bGlobalConfig ? OwnerClass : ConfigClass;         // GlobalConfig 라면 OwnerClass 를 취하여
    if ( !bPerObject )
    {
        ClassSection = BaseClass->GetPathName();                          // Section 이름을 구한다.
    }
 
    ...
 
    GConfig->GetString( *ClassSection, *Key, Value, *PropFileName );      // 이런 식으로 Section 이름을 파라메터로 넣어서 설정값을 구한다.
 
    ...
 
}

버그

배열에서 랜덤인덱스 초기화 문제

언리얼스크립트에서는 초기화되지 않은 배열에 특정 인덱스로 바로 접근하여 value 를 assign 함으로써, 자동으로 배열을 적절하게1) 늘리고 value 를 assign 해주는 기능이 있다.

이것은 초기화defaultproperties 섹션 안이나, 일반적인 함수function 내부에나 동일하게 동작한다. 하지만 나열자enumerator를 사용하여 배열에 접근할 경우, 초기화defaultproperties는 조금 더 조심스럽게 사용해야 한다. 만약 배열인덱스 접근자인 [ ] 사이에 공백이 들어가게 되면 제대로 초기화되지 않는 문제가 생긴다. 아래 예를 보자.

// 다음의 나열자와
enum ENums
{
    E_Num0,
    E_Num1,
    E_Num2,
    E_Num3,
    E_Num4,
    E_Num5,
    E_Num6,
    ...
};
 
// 다음과 같은 배열이 있다고 가정하고
var Array<int>     IntArray;
 
// 다음은 올바른 초기화 섹션의 예이다.
defaultproperties
{
    IntArray[E_Num1]=100       // 자동으로 배열 크기를 2로 늘려준 후, 1번 인덱스에 100을 대입
    IntArray[E_Num5]=500       // 자동으로 배열 크기를 6으로 늘려준 후, 5번 인덱스에 500을 대입
}
 
// 다음은 틀린 초기화 섹션의 예이다.
defaultproperties
{
    IntArray[ E_Num1 ]=100     // 처음은 waning 이 발생하지 않는다. 그러나 0번 인덱스에 100이란 value가 저장된다.
    IntArray[ E_Num5 ]=500     // 초기화 되지 않고 다음과 같은 wanring 을 내뱉는다. "Warning, redundant data: 블라블라"
}

인덱스 접근자 사이에 공백을 넣으면 첫번째 초기화 시점엔 아무런 에러가 나지 않다가2) 두번째 시도에 warning을 뱉는다.

위의 경우는 그나마 warning 이라도 뱉어주니 다른 방법을 모색할 수 있다. 하지만 다음과 같은 경우는 골때리게도 warning 없이 아주 잘 작동하는 것처럼 보이게 된다.

// 다음과 같은 구조체 배열이 있다고 가정하고
struct Nums
{
    var Array<int>   IntArray;
};
var Nums  MyNums;
 
// 다음은 올바른 초기화 섹션의 예이다.
defaultproperties
{
    MyNums=(IntArray[E_Num1]=100, IntArray[E_Num5]=500)     // 의도한대로 작동할 것이다.
}
 
// 다음은 틀린 초기화 섹션의 예이다.
defaultproperties
{
    // 역시나 0번 인덱스에 100이란 value가 저장되고 다시 0번 인덱스에 500이란 value가 덮어씌워진다.
    // 그러나!! warning이 발생하지 않는다. 여기서부터 재앙은 시작된다.
    MyNums=(IntArray[ E_Num1 ]=100, IntArray[ E_Num5 ]=500)   
}

나열자enumerator를 사용하지 않고 바로 숫자를 사용하여 초기화 한다면 위의 경우 문제가 되지 않는다. 하지만 대부분의 스마트한 프로그래머들은 나열자나 혹은 const형의 특정 변수로 배열을 초기화할 것이기 때문에 위의 경우를 반드시 숙지해두어야 한다.

함수function 내부에서 사용되는 경우 또한 위의 위험에서 안전하다. 즉, 나열자를 사용하여 접근한다 해도 문제가 되지 않는다.

기타

UE3 스크립트 빌드방법

UnrealEngine3 의 실행파일은 Editor 및 ScriptCompiler 를 포함한다. 물론 이것은 최종 배포단계에서 제외시킬 수 있다. 개발단계에는 이 모든 것이 하나의 실행파일에 통합되어 있음을 기억하자.

기본

다음의 커맨드를 커맨드라인에서 입력한다. (실행파일은 ExampleGame.exe 라고 가정)

ExampleGame.exe make                  // 변경된 스크립트 파일만 빌드. release 모드
ExmapleGame.exe make -full            // 모든 스크립트 파일을 release 모드로 재 빌드
ExampleGame.exe make -debug           // debug 용도로 컴파일한다. 기본적으로 변경된 내용만 컴파일한다.
ExampleGame.exe make -debug -full     // 모든 스크립트 파일을 debug 모드로 재 빌드

:!: release 로 빌드 후 debug 로 다시 빌드하고 싶을 경우 -debug -full 로 재 빌드해야 한다. -debug 로는 변경된 것이 없다고 판단하여 다시 빌드하지 않는다.

빌드 순서 결정

UnrealScript 는 빌드 순서를 (종속성에 따라) 스마트하게 결정하지 못한다. 따라서 우리가 직접적으로 빌드 순서를 정해주어야 한다.

엔진에서 기본적으로 정해 준 순서대로 빌드해도 무관하지만 엔진의 코어부분들을 수정해야 할 경우 종속성 여부 따라 스크립트 빌드시 에러가 발생할지도 모른다. 따라서 알아두면 좋다.

엔진에서 기본적으로 정의되어 있는 곳은 Engine/Config/BaseEngine.ini 이다. 이 파일의 [UnrealEd.EditorEngine] 섹션을 보면 아래 라인들을 볼 수 있고

BaseEngine.ini
[UnrealEd.EditorEngine]
...
EditPackages=Core
EditPackages=Engine
EditPackages=IpDrv
EditPackages=GFxUI
EditPackages=GameFramework
...

이 순서대로 빌드된다.

2012/02/04 13:12 · z3moon

스크립트 recompile 여부 판별

언리얼스크립트를 수정한 후, 게임을 실행하면 스크립트 소스가 최신버전이 아니니 다시 컴파일할 것인지를 물어본다. 이것을 어떤 식으로 판별하는지 알아보자.

이를 수행하는 함수는 AreScriptPackagesOutOfDate (UMakeCommandlet.cpp) 이며 대략적인 시퀀스는 다음과 같다.

  1. 스크립트소스가 있는 경로3), 스크립트패키지(.u 확장자의 바이너리) 가 있는 경로4), 스크립트패키지 이름 리스트5) 정보를 얻는다.
  2. 얻어온 스크립트패키지 이름 리스트에서 OnlineSubsystem 으로 시작하는 패키지와 IpDrv 는 제외된다.
  3. 이제 하나의 패키지에 대해서 다음의 내용을 수행한다.
    1. 생성된 패키지의 나이 (파일이 생성된 후로부터 지금까지의 경과 시간) 를 구한다.
    2. 소스가 있는 디렉토리의 나이를 구한다. 이때 디렉토리의 나이는 그 안의 모든 파일들을 순회하며 가장 나이가 적은 놈으로 택한다.
    3. 소스 디렉토리의 나이가 생성된 패키지의 나이보다 적다면, 재컴파일이 필요하다고 판별한다.
  4. ModPackages 에 대해서도 위의 과정을 수행하여 재컴파일 여부를 검사한다.

defaultproperties 에서 배열 of 배열 초기화

다음과 같은 구조가 있다고 치자.

struct StructA
{
    var int                Cnt;
    var Vector2D           Range;
};
 
struct StructB
{
    var Name               Name;
    var Array<StructA>     StructAs;
};
 
var Array<StructB>     StructBs;

StructBs 은 배열을 가지는 배열인데 이것을 defaultproperties 에서 초기화할 때 다음과 같이 해야한다. (세가지 방법이 있다.)

// 1.한줄 표현
defaultproperties
{
    StructBs(0)=(Name="First",StructAs=((Cnt=0,Range=(X=0,Y=1)),(Cnt=1,Range=(X=1,Y=2))))
    StructBs(1)=(...)
}
 
// 2.여러줄 표현
defaultproperties
{
    StructBs(0)={(
        Name="First",
        StructAs=(
            (
                Cnt=0,
                Range=(X=0,Y=1)
            ),
            (
                Cnt=1,
                Range=(X=1,Y=2)
            )
        )
    )}
    StructBs(1)={(
        ...
    )}
}
 
// 3.여러줄 표현 + 명시적 인덱스
defaultproperties
{
    StructBs(0)={(
        Name="First",
        StructAs[0]={(
            Cnt=0,
            Range=(X=0,Y=1)
        )}
        StructAs[1]={(
            Cnt=1,
            Range=(X=1,Y=2)
        )}
    )}
    StructBs(1)={(
        ...
    )}
}

뒤로 갈수록 보기 쉬워진다는 장점이 있다.

from http://wiki.beyondunreal.com/Legacy:Default_Properties

참조 : https://udn.epicgames.com/Three/UnrealScriptDefaultProperties

참조

1) 접근 index가 배열의 length-1 보다 크다면 배열의 크기를 자동으로 index+1 만큼 늘려준다.
2) 그러나 제대로 초기화 되지 않고 0번 인덱스에 초기화 될 것이다.
3) Default 로는 ..\..\Development\Src 이며, [UnrealEd.EditorEngine] 섹션 → EditPackagesInPath 필드로 정의 가능
4) Default 로는 ..\..\UTGame\Script 이며, [UnrealEd.EditorEngine] 섹션 → EditPackagesOutPath 필드로 정의 가능
5) Core, Engine, GameFramework, UnrealEd, … 등