사용자 정의 문
제작자 정의 문(CDS)을 사용하면 Ren'Py 스크립트 언어에 직접 만든 문을 추가할 수 있습니다. 현재 문법이 지원하지 않는 기능을 추가할 수 있습니다.
CDS는 같은 기능의 Python 코드를 직접 쓰는 것보다 유연할 수 있습니다.
예를 들어 무작위 대사를 고르는 코드는 다음과 같습니다.
label introduction:
python:
greetings = ['안녕하세요.', '어서 오세요.', '무엇을 도와드릴까요?']
greeting = renpy.random.choice(greetings)
"[greeting]"
Ren'Py 파서는 python 블록에서 어떤 일이 일어나는지, 어떻게 실행해야 하는지 미리 알지 못합니다. 실행 전까지 이 코드를 처리하지 않으며, 예외가 발생하면 오류를 냅니다.
CDS를 사용하면 다음 작업이 가능합니다.
파싱한 문법의 정확성 검사. 예를 들어 renpy.random.choice에 전달할 리스트 항목의 텍스트가 유효한지 검사할 수 있습니다.
실행 시 잘못된 데이터 무시. 핵심 기능이 아니라면 예외를 발생시키기보다 실행을 건너뛰는 편이 나을 때가 많습니다.
함수에서 사용하는 디스플레이어블 예측 로딩.
Lint에서 추가 정보 제공. 실행 중 오류를 무시했다면 여기서 보고할 수 있습니다.
예를 들어 위의 동작을 CDS로 작성하면 다음과 같습니다.
python early:
def parse_random(lexer):
subblock_lexer = lexer.subblock_lexer()
choices = []
while subblock_lexer.advance():
with subblock_lexer.catch_error():
statement = subblock_lexer.renpy_statement()
choices.append(statement)
return choices
def next_random(choices):
return renpy.random.choice(choices)
def lint_random(parsed_object):
for i in parsed_object:
check = renpy.check_text_tags(i.block[0].what)
if check:
renpy.error(check)
renpy.register_statement(
name="random",
block=True,
parse=parse_random,
next=next_random,
lint=lint_random,
)
이제 random을 문으로 사용할 수 있습니다.
label introduction:
random:
"안녕하세요."
"어서 오세요."
"무엇을 도와드릴까요?"
CDS를 사용한다고 실행 성공이 보장되는 것은 아닙니다. 다만 문을 잘 구현할수록 Ren'Py가 의도한 동작을 더 잘 이해할 수 있습니다.
사용법
제작자 정의 문(CDS)은 다음 규칙을 따라야 합니다.
python early블록에서 정의해야 합니다.정의한 파일 안에서는 사용할 수 없습니다.
CDS를 포함하는 파일은 이를 사용하는 모든 파일보다 먼저 불러와야 합니다. Ren'Py는 파일 경로의 유니코드 정렬 순서로 불러오므로, CDS가 있는 파일명 앞에 01 같은 작은 숫자를 붙이는 것이 일반적으로 좋습니다. game/libs/와 game/mods/ 디렉터리의 특별한 동작을 포함한 자세한 순서는 스크립트 파싱 단계를 참고하세요.
제작자 정의 문은 renpy.register_statement()로 등록합니다. 이 함수는 CDS의 내용을 처리하는 다른 함수들을 받습니다.
예를 들어 따옴표 없이 텍스트 줄을 지정할 수 있는 line이라는 새 문을 만들 수 있습니다.
line e "이 따옴표들은 화면에 그대로 표시돼요." 에일린이 말했다. "백슬래시를 붙일 필요도 없죠."
parse 함수에는 파싱할 어휘 분석 결과가 전달됩니다. execute 함수는 파싱한 내용에 대한 작업을 실행하고, lint 함수는 파싱한 내용의 오류를 보고해야 합니다.
python early:
def parse_smartline(lexer):
who = lexer.simple_expression()
what = lexer.rest()
return (who, what)
def execute_smartline(parsed_object):
who, what = parsed_object
renpy.say(eval(who), what)
def lint_smartline(parsed_object):
who, what = parsed_object
try:
eval(who)
except Exception:
renpy.error("정의되지 않은 캐릭터: {}".format(who))
tte = renpy.check_text_tags(what)
if tte:
renpy.error(tte)
renpy.register_statement(
"line",
parse=parse_smartline,
execute=execute_smartline,
lint=lint_smartline,
)
API 참고서
- renpy.register_statement(name, parse=None, lint=None, execute=None, predict=None, next=None, scry=None, block=False, init=False, translatable=False, execute_init=None, init_priority=0, label=None, warp=None, translation_strings=None, force_begin_rollback=False, post_execute=None, post_label=None, predict_all=True, predict_next=None, execute_default=None, reachable=None)
사용자 정의 문을 등록합니다.
- name
문을 시작하는 이름들을 공백으로 구분한 문자열입니다. 빈 문자열이면 새로운 기본 문을 정의하며, 이 기본 문이 say 문을 대체합니다.
- block
다음 중 하나입니다.
False: 블록을 받지 않는 문입니다.
True: 블록을 받으며 직접 파싱하는 문입니다.
"possible": 블록이 있을 수도 없을 수도 있는 문입니다.
"script": 블록을 Ren'Py 스크립트 언어 문들의 블록으로 해석합니다. 이를 통한 제어 흐름 구현은 next를 참고하세요.
"script-possible": 블록이 있으면 "script"처럼, 없으면 False처럼 처리합니다.
"atl": 블록을 ATL 트랜스폼으로 해석합니다. execute에 추가 인수로 전달합니다.
"atl-possible": 블록이 있으면 "atl"처럼, 없으면 False처럼 처리합니다.
- parse
Lexer 객체를 받는 함수입니다. 문을 파싱하고 객체 하나를 반환해야 합니다. 이 객체는 다른 모든 함수에 인수로 전달됩니다.
- lint
문을 검사하기 위해 호출합니다. parse가 반환한 객체를 인수 하나로 받습니다. 오류는 renpy.error를 호출하여 보고해야 합니다.
- execute
문이 실행될 때 호출하는 함수입니다. parse가 반환한 객체를 인수 하나로 받습니다. ATL 블록이 있으면 ATL 트랜스폼을 atl 키워드 인수로 전달합니다.
- execute_init
초기화 시점에 우선순위 0으로 호출하는 함수입니다. parse가 반환한 객체를 인수 하나로 받습니다.
- predict
문에서 사용하는 이미지를 예측하기 위해 호출하는 함수입니다. parse가 반환한 객체를 인수 하나로 받으며, 문에서 사용하는 디스플레이어블 리스트를 반환해야 합니다.
- next
다음 문을 결정하기 위해 호출하는 함수입니다.
block이 "script"가 아니면 parse가 반환한 객체만 인수로 받습니다. block이 "script"이면 블록의 첫 문을 나타내는 객체도 추가 인수로 받습니다.
이 함수는 이동할 레이블 이름 문자열, 블록으로 제어를 넘기기 위한 두 번째 인수, 또는 바로 다음 문으로 진행하기 위한 None을 반환해야 합니다. parse 함수 안에서 호출한
Lexer.renpy_statement()나Lexer.renpy_block()의 결과를 반환할 수도 있습니다.- label
이 문의 레이블을 결정하기 위해 호출하는 함수입니다. 문자열을 반환하면 그 문자열을 문의 레이블로 사용하며, 다른 레이블처럼 호출하거나 이동할 수 있습니다.
- warp
워프 중 이 문을 실행할지 결정하기 위해 호출하는 함수입니다. 함수가 있고 참을 반환하면 워프 중 실행하며, 그렇지 않으면 실행하지 않습니다.
- scry
Ren'Py 내부에서 사용합니다.
- init
초기화 시점에 실행해야 하는 문이면 True입니다. 문이 이미 init 블록 안에 있지 않으면 자동으로 init 블록 안에 넣습니다.
execute_init 함수가 있다면 일반적으로 사용하지 않는 것이 좋습니다. 문을 init 블록으로 감싸면 execute_init과 execute가 같은 시점에 호출되기 때문입니다.
- translatable
참이면 문을 번역 블록에 포함합니다. 일반적으로 뒤따르는 say 문을 담은 블록에 포함합니다. 한 줄 문에서만 참으로 설정할 수 있습니다. 대사와 함께 변경해야 할 수 있는
nvl clear,voice같은 문에 사용합니다.- init_priority
init과 execute_init 함수가 만드는 init 블록의 초기화 우선순위를 결정하는 정수입니다. 파싱한 블록을 받아 정수를 반환하는 함수일 수도 있습니다.
- translation_strings
파싱한 블록을 받아 호출하는 함수입니다. 문자열 리스트 또는 (문자열 줄 번호, 문자열) 튜플 리스트를 반환해야 합니다. 이 문자열들이 번역 가능한 것으로 보고됩니다.
- force_begin_rollback
menu나call screen처럼 빠른 건너뛰기를 끝낼 가능성이 있는 문에서는 참으로 설정해야 합니다.- post_execute
이 문 다음에 오는 문의 일부로 실행할 함수입니다. post_execute 함수를 추가하면 RPYC 파일의 내용이 바뀌므로 강제 컴파일이 필요합니다.
- post_label
후속 실행문의 레이블을 결정하기 위해 호출하는 함수입니다. 문자열을 반환하면 그 문자열을 문의 레이블로 사용하며, 다른 레이블처럼 호출하거나 이동할 수 있습니다. 고유한 복귀 지점을 만드는 데 사용할 수 있습니다.
- predict_all
True이면 이 문의 모든 하위 파싱 결과와 다음 문을 예측합니다.
- predict_next
이 문 다음에 실행될 문의 레이블을 인수 하나로 받아 호출됩니다.
이 문 이후 실행 가능한 문들을 예측하는 데 사용합니다. 레이블 또는 SubParse 객체의 리스트를 반환해야 합니다. predict_all이 참이면 호출하지 않습니다.
- execute_default
default 문과 같은 시점에 호출하는 함수입니다. 초기화 단계 이후이면서 게임 시작 전, 저장 파일을 불러올 때, 롤백 이후, lint 실행 전, 그리고 그 밖의 시점에도 호출될 수 있습니다.
parse가 반환한 객체를 인수 하나로 받습니다.
- reachable
이 문이 lint의 도달 가능성 분석에 참여하는 방식을 사용자 지정하기 위해 호출하는 함수입니다.
기본적으로 문 자체에 도달할 수 있으면 그 문의 사용자 정의 블록, Lexer.renpy_block()으로 만든 하위 파싱 블록, 다음 문도 도달 가능합니다. label 함수가 있는 문도 도달 가능합니다.
reachable 함수로 이를 사용자 지정할 수 있습니다. 다섯 인수를 받으며, 아래에서 레이블은 문자열이나 불투명 객체일 수 있습니다.
parse 함수가 반환한 객체.
문에 도달할 수 있으면 참인 불리언.
문의 레이블.
다음 문의 레이블. 다음 문이 없으면 None.
block이 "script"이면 블록 첫 문의 레이블. 블록이 없으면 None.
다음을 포함할 수 있는 집합을 반환해야 합니다.
도달 가능한 문의 레이블 또는 subparse 객체.
True: 본질적으로 도달 가능한 문은 아니지만 lint에서 보고하지 않도록 합니다. 다른 문이 도달 가능하다고 보고하면 도달 가능한 문이 됩니다.
None: 무시합니다.
is_reachable의 두 값 모두로 여러 번 호출될 수 있으므로, 도달 가능 여부에 따라 동작을 바꿀 수 있습니다. 예를 들어 현재 문에 도달할 수 있는 경우에만 다음 문에도 도달할 수 있게 할 수 있습니다.
경고
이름으로 빈 문자열을 사용해 say 문을 재정의하는 것은 일반적으로 좋지 않습니다. Ren'Py 기본 문을 교체하면 그 동작이 문에 대응하는 함수에 의존하기 때문입니다. say 문의 대응 함수는 id와 번역 시스템을 지원하지 않습니다. 따라서 기본 문을 재정의한 게임은 이 기능을 전부 다시 구현하지 않는 한 사용할 수 없습니다.
Lexer 객체
사용자 정의 문의 parse 함수는 Lexer 객체의 인스턴스를 받습니다.
- class Lexer
- property init
어휘 분석기가 init 문 안에 있으면 참이고, 그렇지 않으면 거짓입니다.
- property init_priority
init이 참이면 init 블록의 우선순위입니다.
- error(msg)
- 매개변수:
msg (str) -- 발견한 파싱 오류 목록에 추가할 메시지입니다.
현재 위치와 함께 msg를 발견한 파싱 오류 목록에 추가합니다. 현재 문의 파싱은 중단하지만 이후 파싱을 막지는 않습니다.
- require(thing, name=None)
thing의 파싱을 시도하고 불가능하면 오류를 보고합니다.
thing이 문자열이면
match()로 파싱을 시도합니다.그렇지 않으면 thing은 이 Lexer 객체의 다른 메서드여야 하며, 인수 없이 호출됩니다.
name을 지정하지 않으면 메서드 이름 또는 문자열인 경우 thing을 메시지에 사용합니다. 지정하면 name을 사용합니다.
- eol()
- 반환값:
어휘 분석기가 줄 끝에 있으면 True, 그렇지 않으면 False입니다.
- 반환 형식:
bool
- expect_eol()
줄 끝이 아니면 오류를 발생시킵니다.
- expect_noblock(stmt)
이 문이 블록을 받지 않음을 나타내기 위해 호출합니다. 블록이 있으면 오류를 발생시킵니다. stmt는 문자열이어야 하며 오류 메시지에 추가됩니다.
- expect_block(stmt)
이 문에 비어 있지 않은 블록이 필요함을 나타내기 위해 호출합니다. stmt는 문자열이어야 하며 오류 메시지에 추가됩니다.
- has_block()
- 반환값:
현재 줄에 비어 있지 않은 블록이 있으면 True, 그렇지 않으면 False입니다.
- 반환 형식:
bool
- match(re)
임의의 정규 표현식 문자열과 일치하는 부분을 찾습니다.
이 어휘 분석기의 모든 일치 탐색 기능은 이 함수를 기반으로 구현됩니다. 먼저 공백을 건너뛰고 줄에서 일치하는 부분을 찾습니다. 성공하면 해당 텍스트를 반환합니다. 실패하면 None을 반환하며 어휘 분석기의 상태는 바뀌지 않습니다.
- keyword(s)
s를 키워드로 찾아 일치시킵니다.
- name()
이름을 찾아 일치시킵니다. 내장 키워드는 일치하지 않습니다.
- word()
- 반환값:
일치한 단어의 텍스트입니다.
- 반환 형식:
str
키워드를 포함한 모든 단어를 찾아 일치시킵니다.
- image_name_component()
이미지 이름의 구성 요소를 찾아 일치시킵니다. 단어와 달리 이미지 이름의 구성 요소는 숫자로 시작할 수 있습니다.
- string()
Ren'Py 문자열을 찾아 일치시킵니다.
- integer()
- 반환값:
찾은 정수가 담긴 문자열입니다.
- 반환 형식:
str
정수를 찾아 일치시킵니다.
- float()
- 반환값:
찾은 부동소수점 숫자가 담긴 문자열입니다.
- 반환 형식:
str
부동소수점 숫자를 찾아 일치시킵니다.
- label_name(declare=False)
절대 또는 상대 레이블 이름을 찾아 일치시킵니다. declare가 참이면 전역 레이블 이름을 설정합니다. 실제 레이블을 선언하는 것은 아닙니다. 문이 label 함수에서 그 이름을 반환하여 선언해야 합니다.
- simple_expression()
단순 Python 표현식을 찾아 문자열로 반환합니다. 변수 이름이 올 것으로 예상할 때 자주 사용합니다. 결과를 변경하는 것은 권장하지 않습니다. 이후에 결과를 평가하는 것이 올바른 사용법입니다.
- delimited_python(delim)
delim, 예를 들어 ':'로 끝나는 Python 표현식을 찾습니다. 구분자까지 조건식이 올 것으로 예상할 때 자주 사용합니다. 결과를 변경하는 것은 권장하지 않으며, 이후에 평가하는 것이 올바른 사용법입니다. 구분자 전에 줄 끝에 도달하면 오류를 발생시킵니다.
- arguments()
인수 목록을 담는 괄호 앞에서 호출해야 합니다. 괄호가 없으면 None을 반환하고, 있으면 함수 호출의 인수를 나타내는 객체를 반환합니다. 이 객체의
evaluate메서드는 선택적인 scope 딕셔너리를 받아 튜플을 반환합니다. 첫 요소는 위치 인수 튜플, 두 번째 요소는 키워드 인수 딕셔너리입니다.
- rest()
공백을 건너뛴 뒤 줄의 나머지를 반환합니다.
- checkpoint()
어휘 분석기의 현재 상태를 나타내는 불투명 객체를 반환합니다.
- revert(o)
o가 checkpoint()에서 반환한 객체이면, 어휘 분석기를 checkpoint() 호출 당시의 상태로 되돌립니다. 역추적에 사용합니다.
- subblock_lexer()
- 반환값:
현재 줄에 연결된 블록의 Lexer입니다.
- advance()
하위 블록의 어휘 분석기에서 다음 줄로 이동합니다. 첫 줄을 파싱하려면 그 전에 호출해야 합니다. 블록 안의 줄로 이동했으면 True, 모든 줄을 지나쳤으면 False를 반환합니다.
- renpy_statement()
현재 줄을 Ren'Py 스크립트 문으로 파싱하며, 불가능하면 오류를 발생시킵니다. 불투명 객체를 반환하며,
renpy.register_statement()에 전달한 next 함수에서 반환하거나renpy.jump()또는renpy.call()에 전달할 수 있습니다. 이 객체는 문의 파싱 결과 일부로만 저장해야 합니다.이 메서드가 반환한 문의 실행이 끝나면 제작자 정의 문 다음의 문으로 제어가 넘어갑니다. post_execute로 만든 문일 수도 있습니다.
- renpy_block(empty=False)
현재 블록의 남은 모든 줄을 Ren'Py 스크립트로 파싱하고, 블록 첫 문에 대응하는 SubParse를 반환합니다. 블록의 모든 문이 실행된 뒤 이 제작자 정의 문 다음의 문으로 제어가 넘어가도록 연결합니다.
현재 블록을 파싱한다는 점에 유의하세요. 보통 원하는 것은 현재 문의 하위 블록 파싱이며, 그 올바른 방법은 다음과 같습니다.
def mystatement_parse(l): l.require(':') l.expect_eol() l.expect_block("mystatement") child = l.subblock_lexer().renpy_block() return { "child" : child }
- empty
True이면 빈 블록도 파싱할 수 있습니다. 빈 블록은
pass문 하나가 있는 블록과 같습니다.False이면 빈 블록에서 오류가 발생합니다.
- catch_error()
with 문과 함께 사용하는 컨텍스트 데코레이터입니다. 컨텍스트 블록 안의 어휘 분석기 오류를 잡아 보고한 뒤 블록 다음부터 계속 실행합니다.
다음은 하나의 하위 블록에서 여러 오류를 보고하는 사용 예입니다.
def mystatement_parse(l): l.require(':') l.expect_eol() l.expect_block("mystatement") strings = [ ] ll = l.subblock_lexer() while ll.advance(): with ll.catch_error(): strings.append(ll.require(ll.string)) ll.expect_noblock("string inside mystatement") ll.expect_eol() return { "strings" : strings }
다음 함수로 임의의 문자열을 어휘 분석할 수 있습니다.
- renpy.lex_string(text: str, filename: str = '<string>', linenumber: int = 1, advance: bool = True) renpy.lexer.Lexer
지정한 텍스트를 어휘 분석할 수 있는 Lexer 객체를 반환합니다.
- text
어휘 분석할 텍스트입니다.
- filename
오류 보고에 사용할 파일명입니다.
- linenumber
오류 보고에 사용할 줄 번호입니다.
- advance
참이면 어휘 분석기의 .advance() 메서드를 호출합니다.
Lint 유틸리티 함수
다음 함수는 lint 함수를 작성할 때 유용합니다.
- renpy.check_text_tags(s, check_unclosed=False)
s에 있는 텍스트 태그가 올바른지 검사합니다. 오류가 있으면 오류 문자열을, 없으면 None을 반환합니다.
- renpy.error(msg)
문자열 msg를 사용자에게 오류로 보고합니다. 해당하는 경우 파싱 또는 lint 오류로 기록하고, 그 밖의 경우 예외로 발생시킵니다.
- renpy.try_compile(where, expr, additional=None)
표현식 컴파일을 시도하고 실패하면 lint.txt에 오류를 기록합니다.
- where
표현식이 있는 위치를 나타내는 문자열입니다. "Could not evaluate expr in where." 형태의 오류 메시지를 만드는 데 사용합니다.
- expr
컴파일을 시도할 표현식입니다.
- additional
지정하면 오류 메시지에 추가할 정보 한 줄입니다.
- renpy.try_eval(where, expr, additional=None)
표현식 평가를 시도하고 실패하면 lint.txt에 오류를 기록합니다.
- where
표현식이 있는 위치를 나타내는 문자열입니다. "Could not evaluate expr in where." 형태의 오류 메시지를 만드는 데 사용합니다.
- expr
평가를 시도할 표현식입니다.
- additional
지정하면 오류 메시지에 추가할 정보 한 줄입니다.