티스토리 뷰

728x90

고도 게임 엔진에서 캐릭터를 움직이게 하고, 사용자의 동작에 반응하거나 제작자 미리 준비한 시나리오나 시간에 따라 어떤 작업을 수행하는 것은 고도 스크립트(GDScript)에 기반한다. 그러므로 고도 게임 엔진으로 뭔가를 만들고 싶다면 고도 스크립트의 기본적인 문법이나 사용법을 익히는 것은 필수 과정이라 할 수 있겠다.

 

물론 고도에서 제공하는 비주얼 스크립트나 C# 스크립트를 사용하는 방법도 있지만 고도에서 제공하는 대부분의 데모 프로젝트나 샘플은 GDScript를 사용하고 있다는 한계가 있다. C#에 익숙한 개발자라면 C# 스크립트를 이용하는 것도 상당히 매력적인 선택이지만 이 또한 오픈소스 닷넷 프레임워크인 모노(Mono)를 추가적으로 설치해야 한다는 제약이 있다. 

 

 

고도 스크립트의 가장 큰 특징 두 가지를 꼽는 다면 하나는 파이썬 언어와 유사하다는 점이고, 다른 하나는 동적 타입 언어(Dynamically typed)라는 점이다. 들여 쓰기를 해야 하고 문장 끝에 세미콜론(;)을 붙이지 않으며 많은 키워드도 파이썬과 유사하다. 변수의 타입이 컴파일 시점에 정해지고 다른 타입의 데이터를 대입시킬 수 없는 정적 타입 언어와 달리 동적 타입 언어는 변수의 타입이 실행 시점에 결정되므로 서로 다른 타입의 데이터를 대입해도 되는 편리함을 제공한다. 물론 엉뚱한 타입의 데이터 처리에 대한 예외 처리는 개발자가 해야 한다. GDScript와 같은 동적 언어로는 루비(Ruby), 자바 스크립트, PHP 언어 등을 들 수 있다.

 

일단 고도 스크립트에서 #문자 이후는 모두 주석 처리한다. C/C++에서 지원하는 블록 주석은 지원하지 않는다. 대신 여러 라인을 선택한 상태에서 Ctrl+K 단축키로 한 번에 주석을 적용하거나 한 번에 주석을 풀 수 있으며 주석은 아니지만 """와 """사이에 다중 라인의 문자열을 기술하는 식으로 대안적으로 사용할 수도 있다.

 

스크립트를 작성할 때 혼란을 발생시키지 않기 위한 주요 규칙이 있다. 대부분은 파이썬 스타일이다. 일단 개행 문자는 LF(Line Feed)문자만 사용하는 유닉스/리눅스 스타일을 사용한다. 윈도우는 CR+LF, 맥은 CR을 개행으로 사용하지만 개행 문자로 LF 하나만을 사용한다. 스크립트의 파일 끝에도 하나의 LF로 끝나야 한다. 스크립트 파일의 인코딩은 BOM 마크가 없는 UTF-8 인코딩을 사용한다. 한글 입력 등 다국어 처리에도 대응할 수 있다. 파이썬처럼 들여 쓰기로 블록을 구분하는데 이때 공백 문자가 아닌 탭 문자를 사용한다. 위의 그림에서 함수 정의는 첫 컬럼에서 시작하지만 블록에 속하는 다음줄 부터는 탭기호가 표시되면서 들여쓰기가 적용되고 있음을 확인할 수 있다.

 

program = [ inheritance NEWLINE ] [ className ] { topLevelDecl } ;

inheritance = "extends" ( IDENTIFIER | STRING ) { "." IDENTIFIER } ;
className = "class_name" IDENTIFIER [ "," STRING ] NEWLINE ;

topLevelDecl
    = classVarDecl
    | constDecl
    | signalDecl
    | enumDecl
    | methodDecl
    | constructorDecl
    | innerClass
    | "tool"
    ;

classVarDecl = [ "onready" ] [ export ] "var" IDENTIFIER [ ":" typeHint ]
    [ "=" expression ] [ setget ] NEWLINE ;
setget = "setget" [ IDENTIFIER ] [ "," IDENTIFIER] ;
export = "export" [ "(" [ BUILTINTYPE | IDENTIFIER { "," literal } ] ")" ] ;
typeHint = BUILTINTYPE | IDENTIFIER ;

constDecl = "const" IDENTIFIER [ ":" typeHint ] "=" expression NEWLINE ;

signalDecl = "signal" IDENTIFIER [ signalParList ] NEWLINE ;
signalParList = "(" [ IDENTIFIER { "," IDENTIFIER } ] ")" ;

enumDecl = "enum" [ IDENTIFIER ] "{" [ IDENTIFIER [ "=" INTEGER ]
    { "," IDENTIFIER [ "=" INTEGER ] } [ "," ] ] "}" NEWLINE ;

methodDecl = [ rpc ] [ "static" ] "func" IDENTIFIER "(" [ parList ] ")"
    [ "->" typeHint] ":" stmtOrSuite ;
parList = parameter { "," parameter } ;
parameter = [ "var" ] IDENTIFIER [ ":" typeHint ] [ "=" expression ] ;
rpc = "remote" | "master" | "puppet"
    | "remotesync" | "mastersync"  | "puppetsync";

constructorDecl = "func" IDENTIFIER "(" [ parList ] ")"
    [ "." "(" [ argList ] ")" ] ":" stmtOrSuite ;
argList = expression { "," expression } ;

innerClass = "class" IDENTIFIER [ inheritance ] ":" NEWLINE
    INDENT [ inheritance NEWLINE ] topLevelDecl { topLevelDecl } DEDENT ;

stmtOrSuite = stmt | NEWLINE INDENT suite DEDENT ;
suite = stmt { stmt };

stmt
    = varDeclStmt
    | ifStmt
    | forStmt
    | whileStmt
    | matchStmt
    | flowStmt
    | assignmentStmt
    | exprStmt
    | assertStmt
    | yieldStmt
    | preloadStmt
    | "breakpoint" stmtEnd
    | "pass" stmtEnd
    ;
stmtEnd = NEWLINE | ";" ;

ifStmt = "if" expression ":" stmtOrSuite { "elif" expression ":" stmtOrSuite }
    [ "else" ":" stmtOrSuite ] ;
whileStmt = "while" expression ":" stmtOrSuite;
forStmt = "for" IDENTIFIER "in" expression ":" stmtOrSuite ;

matchStmt = "match" expression ":" NEWLINE INDENT matchBlock DEDENT;
matchBlock = patternList ":" stmtOrSuite { patternList ":" stmtOrSuite };
patternList = pattern { "," pattern } ;

pattern = literal | BUILTINTYPE | CONSTANT | "_" | bindingPattern
    | arrayPattern | dictPattern ;
bindingPattern = "var" IDENTIFIER ;
arrayPattern = "[" [ pattern { "," pattern } [ ".." ] ] "]" ;
dictPattern = "{" [ keyValuePattern ] { "," keyValuePattern } [ ".." ] "}" ;
keyValuePattern = STRING [ ":" pattern ] ;

flowStmt
    = "continue" stmtEnd
    | "break" stmtEnd
    | "return" [ expression ] stmtEnd
    ;

assignmentStmt = subscription ( "=" | "+=" | "-=" | "*=" | "/="
| "%=" | "&=" | "|=" | "^=" ) expression stmtEnd;
varDeclStmt = "var" IDENTIFIER [ "=" expression ] stmtEnd;

assertStmt = "assert" "(" expression [ "," STRING ] ")" stmtEnd ;
yieldStmt = "yield" "(" [ expression "," expression ] ")" ;
preloadStmt = "preload" "(" CONSTANT ")" ;

(* 아래 목록에서 뒤에 있는 것이 우선순위가 높다. *)
exprStmt = expression stmtEnd ;
expression = cast [ "[" expression "]" ] ;
cast = ternaryExpr [ "as" typeHint ];
ternaryExpr = logicOr [ "if" logicOr "else" logicOr ] ;
logicOr = logicAnd { ( "or" | "||" ) logicAnd } ;
logicAnd = logicNot { ( "and" | "&&" ) logicNot };
logicNot = ( "!" | "not" ) logicNot | in;
in = comparison { "in" comparison };
comparison = bitOr { ( "<" | ">" | "<=" | ">=" | "==" | "!=" ) bitOr } ;
bitOr = bitXor { "|" bitXor } ;
bitXor = bitAnd { "^" bitAnd } ;
bitAnd = bitShift { "&" bitShift } ;
bitShift = minus { ( "<<" | ">>" ) minus } ;
minus = plus { "-" plus } ;
plus = factor { "+" factor } ;
factor = sign { ( "*" | "/" | "%" ) sign } ;
sign = ( "-" | "+" ) sign | bitNot ;
bitNot = "~" bitNot | is ;
is = call [ "is" ( IDENTIFIER | BUILTINTYPE ) ] ;
call
    = (attribute [ "(" [ argList ] ")" ])
    | "." IDENTIFIER "(" [ argList ] ")"
    | "$" ( STRING | IDENTIFIER { '/' IDENTIFIER } );
attribute = subscription { "." IDENTIFIER } ;
subscription = primary [ "[" expression "]" ] ;
primary = "true" | "false" | "null" | "self" | literal | arrayDecl
    | dictDecl | "(" expression ")" ;

literal = STRING | NUMBER | IDENTIFIER | BUILTINTYPE
    | "PI" | "TAU" | "NAN" | "INF" ;
arrayDecl = "[" [ expression { "," expression } "," ] "]" ;
dictDecl = "{" [ keyValue { "," keyValue } "," ] "}" ;
keyValue 
    = expression ":" expression
    | IDENTIFIER "=" expression
    ;

위의 스크립트는 GDScript의 문법을 EBNF 형식으로 표현한 것이다. 각 문장이 어떻게 다루어지는지 그 문법적 요소를 세밀하게 살펴볼 수 있는 것으로, 다만, GDScript 자체가 이 기술 자료의 EBNF를 그대로 가지고 파서를 제작한 것이 아니기 때문에 약간의 차이가 있을 수 있다고 한다.

 

EBNF 문법을 보는 방법은 최상단 노드인 program을 구성하는 "inheritance", "className", "topLevelDecl"를 하위 레벨로 확장시켜가며 읽는 것인데 중간에 있는 NEWLINE처럼 대문자로 기술한 것은 더 이상 확장되지 않는 터미널 요소를 의미한다. 버티컬 바(|)는 선택을 의미한다. 상속을 설명하는 "inheritance = "extends" ( IDENTIFIER | STRING ) { "." IDENTIFIER } ;"를 보면 상속을 기술하려면 "extends"를 기술하고 그다음에 아이디가 오거나 문자열이 올 수 있다는 의미이다. 대괄호([])는 옵션 항목으로 있을 수도, 없을 수도 있다는 의미이며, 중괄호({})는 반복을 의미한다. 

 

위에서 기술한 EBNF 문법을 기반으로 고도 스크립트의 세부 문법 요소들을 정리해 본다.

 

■ 아이디(IDENTIFIER)

변수 이름, 클래스 이름 등에 적용된다. 영문자와 숫자, 밑줄(_)을 아이디로 사용할 수 있고 숫자가 맨 앞에 올 수는 없다. 대소문자를 가리므로 AAA와 aaa는 서로 다른 아이디로 취급된다.

 

■ 예약어(Keyword)

EBNF에서 "func", "if" 처럼 따옴표로 묶어서 기술하고 있는 것으로 이것들은 아이디로 사용할 수 없다. 정리하면 다음과 같다.

☞ 흐름 제어 : if, elif, else, for, while, match

 루프 중단, 계속 : break, continue

빈문장 : pass

함숫값 리턴 : return

정의문 : class(내부 클래스), class_name(클래스 타입 및 아이콘), extends(상속), signal(시그널), func(함수), static(정적 함수), const(상수), enum(열거형), var(변수), setget(세터 및 게터)

상속 및 타입 확인 : is

타입 변환(캐스팅) :  as

현재 클래스 : self

편집기내 코드 실행 :  tool

노드 초기화 : onready

속성 창에 노출시키기 : export

디버거 중단점 : breakpoint

크래스 사저 로드 : preload

협력 루틴 생성 : yield

조건 확인 : assert

RPC 네트워크 지원 : remote, master, puppet, remotesync, mastersync, puppetsync

시스템 상수 : PI(파이 원주율), TAU(타우 원주율), INF(무한 값), NAN(숫자 아님)

 

연산자(Operators)

EBNF에서 "+=", "in"처럼 따옴표로 묶어서 기술하고 있는 것으로 아이디로 사용할 수 없으며, 주의할 점은 연산자들 간에 우선 수위가 있다는 점이다. 아래 목록에서 숫자가 작은 연산자가 먼저 수행된다. 대입 연산자가 우선 수위가 가장 낮으므로 맨 나중에 수행된다.

0. 배열 참조 : A[idx]

1. 속성 참조 : A.attr

2. 함수 호출 : func()

3. 상속 및 타입 확인 : is

4. 비트 NOT : ~

5. 단항 부정 : -

6. 곱셈, 나눗셈, 나머지 : *, /, %

7. 덧셈, 배열 붙이기 : +

8. 뺄셈 : -

9. 비트 시프트 : <<, >>

10. 비트 AND : &

11. 비트 XOR : ^

12. 비트 OR : |

13. 비교 : <, >, ==, !=, >=, <= (같지 않다가 <>가 아님에 주의)

14. 포함 확인 : in

15. 논리 NOT : !, not

16. 논리 AND : &&, and

17. 논리 OR : ||, or

18. 삼항 연산자 : [참값] if [조건] else [거짓 값] (C/C++을 비롯한 다른 언어의 삼항 연산자와 다른 형식에 주의)

19. 타입 변환(캐스팅) :  as

20. 대입 연산자 : =, +=, -=, *=, /=, %=, &=, |=, <<=, >>=

 

 리터럴 

EBNF에서 리터럴은 literal = STRING | NUMBER | IDENTIFIER | BUILTINTYPE | "PI" | "TAU" | "NAN" | "INF" ;로 표현하고 있으며 각 요소는 더 이상 확장되지 않는 터미널 요소다. 데이터 값 자체를 의미한다. 유형별 표현 방법은 아래와 같다.

 

문자열 : "스트링". 다음과 같은 다른 언어처럼 백 슬래시(\)를 에스케이프 문자로 사용한다.

    \n(개행), \t(탭), \r(CR), \a(Beep), \b(백스페이스), \f(FF), \v(VT), \", \', \\, \uXXXX(16진의 유니코드)

다중 라인 문자열 : """다중 라인"""

정수 : 십진수(123), 16진수(0xa10f), 2진수(0b10001)

실수 : 소수(3.1415), 지수 표현(0.314e-3)

 시스템 상수 : PI, TAU, NAN, INF

논리 값 : true, false

 

 

728x90
댓글
글 보관함
최근에 올라온 글
최근에 달린 댓글
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31