저장, 불러오기, 롤백
Ren'Py는 게임 상태 저장, 불러오기, 이전 상태로의 롤백을 지원합니다. 실제 구현은 조금 다르지만, 롤백은 사용자와 상호작용하는 각 문을 시작할 때 게임을 저장하고 사용자가 되돌릴 때 그 저장을 불러오는 것으로 생각할 수 있습니다.
참고
일반적으로 배포 버전 사이의 저장 호환성을 유지하려고 하지만 보장하지는 않습니다. 충분히 큰 이점이 있다면 저장 호환성을 깨기로 결정할 수 있습니다.
저장되는 내용
Ren'Py는 내부 상태와 Python 상태를 포함한 게임 상태를 저장하려고 시도합니다.
내부 상태는 게임 시작 후 변경되도록 설계된 Ren'Py의 모든 요소이며, 다음을 포함합니다.
현재 문과 복귀 가능한 모든 문.
표시 중인 이미지와 디스플레이어블.
표시 중인 스크린과 내부 변수 값.
재생 중인 음악.
NVL 모드 텍스트 블록 목록.
Python 상태는 게임 시작 후 바뀐 스토어 변수와 그 변수에서 도달할 수 있는 모든 객체입니다. 중요한 것은 변수 자체의 변경입니다. 객체의 필드만 변경해서는 해당 객체가 저장되지 않습니다.
default 문으로 설정한 변수는 항상 저장됩니다.
다음 예제에서는
define a = 1
define o = object()
default c = 17
label start:
$ b = 1
$ o.value = 42
b와 c만 저장됩니다. A는 게임 시작 후 바뀌지 않아 저장하지 않습니다. O도 참조하는 객체만 바뀌고 변수 자체는 바뀌지 않아 저장하지 않습니다.
저장되지 않는 내용
게임 시작 후 바뀌지 않은 Python 변수는 저장하지 않습니다. 저장하는 변수와 저장하지 않는 변수가 같은 객체를 참조하는 별칭 관계라면 큰 문제가 될 수 있습니다. 다음 예제에서는
init python:
a = object()
a.f = 1
label start:
$ b = a
$ b.f = 2
"a.f=[a.f] b.f=[b.f]"
a와 b가 별칭 관계입니다. 저장하고 불러오면 이 관계가 깨져 a와 b가 서로 다른 객체를 참조할 수 있습니다. 혼란을 일으킬 수 있으므로 저장하는 변수와 저장하지 않는 변수 사이의 별칭 관계는 피하는 것이 좋습니다. 직접 겪는 일은 드물지만, 저장하지 않는 변수와 저장하는 필드 사이에서 생길 수 있습니다.
저장하지 않는 상태는 그 밖에도 있습니다.
- 제어 흐름 경로
현재 문과 복귀해야 할 문만 저장하며, 그 위치에 도달한 경로는 기억하지 않습니다. 따라서 변수 대입을 포함해 게임에 새로 추가한 문은 실행되지 않습니다.
- 이미지 이름과 디스플레이어블의 연결
이 연결은 저장하지 않으므로 다시 불러오면 이미지가 새로운 이미지로 바뀔 수 있습니다. 게임이 발전하면서 이미지 파일을 바꿀 수 있게 합니다.
- 설정 변수, 스타일, 스타일 속성
설정 변수와 스타일은 게임의 일부로 저장하지 않습니다. 따라서
init블록에서만 바꾸고 게임 시작 후에는 그대로 두어야 합니다.
저장이 이루어지는 지점
저장은 가장 바깥쪽 상호작용 컨텍스트의 Ren'Py 문 시작 시점에 이루어집니다.
저장이 문의 시작 시점에 이루어진다는 점이 중요합니다. 여러 번 상호작용하는 문 중간에서 불러오거나 롤백하면 문을 시작했을 때의 상태로 돌아갑니다.
Python으로 정의한 문에서는 문제가 될 수 있습니다. 다음 코드에서
python:
i = 0
while i < 10:
i += 1
narrator("현재 카운트는 [i]입니다.")
도중에 저장하고 불러오면 반복문이 처음부터 다시 시작합니다. Python 대신 Ren'Py 스크립트로 반복하면 피할 수 있습니다.
$ i = 0
while i < 10:
$ i += 1
"현재 카운트는 [i]입니다."
Ren'Py가 저장할 수 있는 것
Ren'Py는 Python pickle 시스템으로 게임 상태를 저장합니다. 이 모듈은 다음을 저장할 수 있습니다.
True, False, None, int, str, float, complex, str, 유니코드 객체 등의 기본 형식.
list, tuple, set, dict 같은 복합 형식.
제작자 정의 객체, 클래스, 함수, 메서드, 바인딩된 메서드. 함수의 피클 직렬화가 성공하려면 원래 이름으로 계속 접근할 수 있어야 합니다.
Character, Displayable, Transform, Transition 객체.
Ren'Py가 저장할 수 없는 것
다음 형식은 피클로 직렬화할 수 없습니다.
Render 객체.
이터레이터 객체.
제너레이터 객체.
async,await로 만드는 것과 같은 코루틴 태스크와 퓨처.파일 유사 객체.
네트워크 소켓과 이를 포함하는 객체.
내부 함수와 람다.
이 목록이 전부는 아닐 수 있습니다.
직렬화할 수 없는 객체도 초기화 변수, 함수 내부 네임스페이스, python hide 블록처럼 Ren'Py가 저장하지 않는 네임스페이스 안에서만 사용한다면 사용할 수 있습니다.
예를 들어 다음과 같이 파일 객체를 사용하면
$ monika_file = open(config.gamedir + "/monika.chr", "w")
$ monika_file.write("삭제하지 마세요.\r\n")
$ monika_file.close()
세 Python 문 사이의 어느 시점에서든 f가 저장될 수 있어 작동하지 않습니다. python hide 블록에 넣으면 작동합니다.
python hide:
monika_file = open(config.gamedir + "/monika.chr", "w")
monika_file.write("삭제하지 마세요.\r\n")
monika_file.close()
물론 Python의 with 문을 쓰는 편이 더 깔끔합니다.
python hide:
with open(config.gamedir + "/monika.chr", "w") as monika_file:
monika_file.write("삭제하지 마세요.\r\n")
async, await, asyncio 등으로 만든 코루틴도 비슷합니다. 다음 코드가 있으면
init python:
import asyncio
async def sleep_func():
await asyncio.sleep(1)
await asyncio.sleep(1)
아래 코드는
$ sleep_task = sleep_func()
$ asyncio.run(sleep_task)
sleep_task를 저장할 수 없어 문제가 생깁니다. 하지만 변수에 대입하지 않으면
$ asyncio.run(sleep_func())
문제없이 실행됩니다.
저장 파일 구조
1-1.save 같은 Ren'Py 저장 파일은 다음을 담은 압축 아카이브입니다.
게임 상태: 게임 상태를 나타내는 직렬화된 Python 객체.
메타데이터: 저장 내용을 설명하는 필드의 딕셔너리. 접근 방식에 따라 사용할 수 있는 필드가 다릅니다.
renpy.slot_json():_save_name,_renpy_version,_version,_game_runtime,_ctime과config.save_json_callbacks로 추가한 사용자 정의 필드의 딕셔너리를 반환합니다.FileJson():key가 기본값인None이면 사용자 정의 필드를 포함하여renpy.slot_json()와 같은 메타데이터 딕셔너리를 반환합니다.key를"_save_name"처럼 지정하면 해당 필드 값을 반환합니다.renpy.list_saved_games():_save_name을extra_info로,mtime을time으로,_screenshot을screenshot으로 담은 튜플을 반환하며 다른 메타데이터 필드는 포함하지 않습니다.
표준 메타데이터 필드는 다음과 같습니다.
_save_name:renpy.save()의extra_info로 설정한 문자열입니다. 보통 전역save_name변수이며, 예로 "Chapter 1"이 있습니다. 비어 있으면 빈 문자열입니다._renpy_version: 저장 파일을 만든 Ren'Py 버전의 정수 리스트입니다. 예를 들어 Ren'Py 8.3.7.25031702는[8, 3, 7, 25031702]입니다._version: 저장 시점의config.version으로 정한 게임 버전입니다. 예: "1.0"._game_runtime: 사용자 입력을 기다린 초입니다. 저장 시점에renpy.get_game_runtime()이 반환한 값입니다._ctime: 저장 파일 생성 시점의 UNIX 타임스탬프입니다. 에포크 이후의 초입니다._screenshot: 저장 스크린샷의 디스플레이어블입니다.renpy.list_saved_games()또는FileScreenshot()으로 접근할 수 있습니다.
메타데이터는 JSON으로 저장하므로 문자열, 숫자, 리스트, 딕셔너리 등 JSON이 표현할 수 있는 값으로 제한됩니다. save_name 또는 extra_info로 _save_name을 사용할 때 [], {} 같은 특수 문자가 저장/불러오기 UI의 텍스트 파서에 직접 해석되면 오류나 예상치 못한 표시가 발생할 수 있습니다. 복잡한 메타데이터는 config.save_json_callbacks로 안전하게 저장하거나, Text로 표시할 때 substitute False를 사용하세요.
트레이스백 저장
처리되지 않은 예외가 발생하면 가능할 경우 게임 상태와 트레이스백을 함께 저장합니다. 슬롯 이름은 _tracesave-1부터 _tracesave-10까지입니다. 트레이스백은 메타데이터에 저장하며 다음과 같이 얻을 수 있습니다.
traceback = renpy.slot_json("_tracesave-1")["_traceback"]
renpy.get_save_data()로 트레이스백 저장이 생성될 당시의 게임 변수도 얻을 수 있습니다.
새 함수 renpy.get_save_data()는 저장을 불러오지 않고 특정 슬롯의 데이터를 가져옵니다. 트레이스백 저장과 함께 사용하면 오류 상태로 들어가지 않고 게임 데이터를 가져올 수 있습니다.
저장 함수와 변수
상위 수준 저장 시스템에서 사용하는 변수는 save_name 하나입니다.
각 저장에 함께 보관하는 문자열입니다. 저장에 이름을 붙여 사용자가 구분하게 할 수 있습니다.
저장별 데이터를 더 사용자 지정하려면 JSON 부가 데이터 시스템의 config.save_json_callbacks를 참고하세요.
여러 상위 수준 저장 액션과 함수는 스크린 액션에 정의되어 있습니다. 그 밖에 다음 하위 수준 저장 및 불러오기 기능이 있습니다.
- renpy.can_load(filename, test=False)
filename의 저장 슬롯이 있으면 참, 없으면 False를 반환합니다. 예를 들어 '3-2'는 3페이지의 2번 슬롯입니다.
- renpy.copy_save(old, new)
old의 저장을 new로 복사합니다. old가 없으면 아무 일도 하지 않습니다.
- renpy.get_save_data(filename)
filename 슬롯에서 게임 데이터를 가져오되 실제 게임 상태는 바꾸지 않습니다. 예를 들어 '3-2'는 3페이지의 2번 슬롯입니다. 기본 스토어 기준 변수 이름을 저장 당시의 값에 연결하는 딕셔너리를 반환합니다.
- renpy.list_slots(regexp=None)
비어 있지 않은 저장 슬롯 리스트를 반환합니다. regexp가 있으면 regexp와 일치하는 시작 부분을 가진 슬롯만 반환합니다. 문자열 순서로 정렬합니다.
유용한 정규 표현식 목록은
list_saved_games()를 참고하세요.
- renpy.load(filename)
filename 슬롯에서 게임 상태를 불러옵니다. 예를 들어 '3-2'는 3페이지의 2번 슬롯입니다. 성공하면 이 함수는 반환하지 않습니다.
- renpy.newest_slot(regexp=None)
수정 시간이 가장 최근인 최신 저장 슬롯 이름을 반환합니다. 일치하는 저장이 없으면 None입니다.
regexp가 있으면 regexp와 일치하는 시작 부분을 가진 슬롯만 반환합니다.
유용한 정규 표현식 목록은
list_saved_games()를 참고하세요.
- renpy.rename_save(old, new)
저장 이름을 old에서 new로 바꿉니다. old가 없으면 아무 일도 하지 않습니다.
- renpy.save(filename, extra_info='', *, extra_json=None)
filename 슬롯에 게임을 저장합니다.
- filename
저장 슬롯 이름 문자열입니다. 변수 이름과 달리 실제 파일명과는 느슨하게 대응합니다. 예를 들어 '3-2'는 3페이지의 2번 슬롯입니다.
- extra_info
메타데이터의
_save_name필드에 저장할 문자열입니다.renpy.slot_json()의 "_savename" 키,key=None또는key="_save_name"인FileJson(), 또는renpy.list_saved_games()의extra_info필드로 얻을 수 있습니다. 보통 전역save_name변수와 함께 사용합니다. 예:"Chapter 1". 비어 있으면_save_name은 빈 문자열입니다.- extra_json
저장 파일에 넣을 추가 메타데이터 딕셔너리입니다. 기본 메타데이터와
config.save_json_callbacks의 메타데이터에 병합합니다.- 예제::
$ save_name = "Chapter 1" $ renpy.save("1-1", extra_info=save_name)
추가 또는 복잡한 메타데이터는
config.save_json_callbacks로 메타데이터 딕셔너리에 사용자 정의 필드를 추가하여 저장하세요.이 함수 전에
renpy.take_screenshot()을 호출해야 합니다.
- renpy.slot_json(slotname)
slotname 저장의 메타데이터 딕셔너리를 반환합니다.
_save_name,_renpy_version,_version,_game_runtime,_ctime과 저장 시config.save_json_callbacks로 추가한 사용자 정의 필드를 포함합니다. 빈 슬롯이면 None입니다. 저장/불러오기 스크린 액션인FileJson()은 사용자 정의 필드를 제외한 일부 필드를 제공합니다.예제:
def show_game_runtime(slot): metadata = renpy.slot_json(slot) name = metadata.get('_save_name', '') runtime = metadata.get('_game_runtime', 0) hours = int(runtime // 3600) minutes = int((runtime % 3600) // 60) seconds = int(runtime % 60) runtime_formatted = f"{hours:02d}:{minutes:02d}:{seconds:02d}" renpy.notify("저장 이름: [name], 게임 실행 시간: [runtime_formatted]")
- renpy.slot_mtime(slotname)
slot의 수정 시간을 반환합니다. 빈 슬롯이면 None입니다.
- renpy.slot_screenshot(slotname)
slotname의 스크린샷으로 사용할 디스플레이어블을 반환합니다. 빈 슬롯이면 None입니다.
- renpy.take_screenshot()
스크린샷을 찍습니다. 저장한 게임의 일부로 보관합니다.
- renpy.unlink_save(filename)
지정한 이름의 저장 슬롯을 삭제합니다. 예를 들어 '3-2'는 3페이지의 2번 슬롯입니다.
불러오기 후 데이터 유지
게임을 불러오면 아래의 롤백 시스템을 사용해 현재 문의 실행이 시작될 때의 상태로 돌아갑니다.
이 동작을 원하지 않을 수도 있습니다. 예를 들어 스크린에서 값을 편집할 수 있다면 불러온 뒤에도 값을 유지하고 싶을 수 있습니다. renpy.retain_after_load를 호출하면 다음 체크포인트 상호작용이 끝나기 전에 저장하고 불러올 때 데이터를 되돌리지 않습니다.
데이터는 유지해도 제어는 현재 문의 시작으로 돌아갑니다. 새 데이터를 가진 상태로 문을 다시 실행합니다.
예를 들면 다음과 같습니다.
screen edit_value:
hbox:
text "[value]"
textbutton "+" action SetVariable("value", value + 1)
textbutton "-" action SetVariable("value", value - 1)
textbutton "+" action Return(True)
label start:
$ value = 0
$ renpy.retain_after_load()
call screen edit_value
- renpy.retain_after_load()
현재 문과 다음 체크포인트가 있는 문 사이에서 수정한 데이터를 불러오기 시 유지합니다.
롤백
롤백은 최신 응용 프로그램 대부분의 실행 취소/다시 실행처럼 이전 게임 상태로 돌아가는 기능입니다. 시스템이 화면과 게임 변수의 상태를 관리하지만, 게임을 만들 때 고려할 사항이 있습니다.
어떤 데이터가 롤백되나요?
초기화 단계 이후 변경한 변수와 그 변수에서 도달하는 되돌릴 수 있는 형식의 객체에 적용됩니다. 간단히 말해 Ren'Py 스크립트에서 만든 list, dict, set과 스크립트에 정의한 클래스의 인스턴스는 되돌릴 수 있습니다. Python이나 Ren'Py 내부에서 만든 데이터는 보통 되돌릴 수 없습니다.
구체적으로 Ren'Py 스크립트에 포함된 Python이 실행되는 스토어에서는 object, list, dict, set을 되돌릴 수 있는 동등한 형식으로 교체합니다. 이 형식을 상속한 객체도 되돌릴 수 있습니다. renpy.Displayable도 되돌릴 수 있는 object를 상속합니다.
사용을 편리하게 하기 위해 Ren'Py 스크립트 파일의 Python 코드를 다음과 같이 바꿉니다.
list, dict, set 리터럴을 되돌릴 수 있는 형식으로 자동 변환합니다.
list, dict, set 컴프리헨션도 자동 변환합니다.
확장 언패킹 등 list, dict, set을 만드는 다른 Python 구문도 결과를 변환합니다. 다만 성능 때문에 추가 키워드 인수의 딕셔너리를 만드는 함수와 메서드의 이중 별표 매개변수는 변환하지 않습니다.
다른 형식을 상속하지 않는 클래스는 되돌릴 수 있는 object를 자동 상속합니다.
또한 다음을 적용합니다.
되돌릴 수 있는 형식의 메서드와 연산자는 list, dict, set을 생성하면 되돌릴 수 있는 객체를 반환하도록 바뀌어 있습니다.
list, dict, set을 반환하는 내장 함수도 되돌릴 수 있는 형식을 반환합니다.
Python 코드를 호출하면 일반적으로 되돌릴 수 있는 객체가 생성되지 않습니다. 다음은 롤백에 참여하지 않을 수 있는 객체를 얻는 경우입니다.
str.split처럼 내장 형식의 메서드를 호출한 경우.
가져온 Python 모듈에서 객체를 만들고 Ren'Py에 반환한 경우. 예를 들어 collections.defaultdict 인스턴스는 롤백에 참여하지 않습니다.
별도 설명이 없는 Ren'Py API 반환 객체.
이 데이터를 롤백해야 한다면 참여하는 형식으로 변환하는 것이 좋습니다. 예를 들면 다음과 같습니다.
# Calling list inside Python-in-Ren'Py converts a non-revertable list
# into a revertable one.
$ attrs = list(renpy.get_attributes("eileen"))
롤백과 롤포워드 지원
대부분의 Ren'Py 문은 롤백과 롤포워드를 자동 지원합니다. ui.interact()를 직접 호출한다면 다음 구조로 직접 지원을 추가해야 합니다.
# This is None if we're not rolling back, or else the value that was
# passed to checkpoint last time if we're rolling forward.
roll_forward = renpy.roll_forward_info()
# Set up the screen here...
# Interact with the user.
rv = ui.interact(roll_forward=roll_forward)
# Store the result of the interaction.
renpy.checkpoint(rv)
renpy.checkpoint를 호출한 뒤에는 사용자와 상호작용하지 않아야 합니다. 그렇지 않으면 롤백이 불가능해질 수 있습니다.
- renpy.can_rollback()
롤백할 수 있으면 참을 반환합니다.
- renpy.checkpoint(data=None, *, hard=True)
현재 문을 사용자가 돌아갈 수 있는 체크포인트로 만듭니다. 호출한 뒤 현재 문에서는 사용자와 더 이상 상호작용하지 않아야 합니다.
저장용 현재 스크린샷도 지웁니다.
- data
게임이 롤백 중일 때
renpy.roll_forward_info()가 반환할 데이터입니다.- hard
참이면 롤백이 멈추는 강한 체크포인트입니다. 거짓이면 멈추지 않는 약한 체크포인트입니다.
- renpy.get_identifier_checkpoints(identifier)
HistoryEntry의 rollback_identifier를 받아 그 지점까지 돌아가는 데 필요한
renpy.rollback()의 checkpoints 값을 반환합니다. 롤백 기록에 없으면 None입니다.
- renpy.in_rollback()
게임이 롤백되었으면 참을 반환합니다.
- renpy.roll_forward_info()
롤백 중이면 이 문이 마지막으로 실행될 때
renpy.checkpoint()에 전달한 데이터를 반환합니다. 롤백 중이 아니면 None입니다.
- renpy.rollback(force=False, checkpoints=1, defer=False, greedy=True, label=None, abnormal=True)
게임 상태를 마지막 체크포인트로 되돌립니다.
- force
참이면 어떤 상황에서든 롤백합니다. 그렇지 않으면 스토어, 컨텍스트, 설정에서 모두 롤백이 활성화된 경우에만 수행합니다.
- checkpoints
이 횟수의 renpy.checkpoint 호출을 거슬러 올라갑니다. 이 조건 안에서 가능한 만큼 돌아갑니다.
- defer
참이면 주 컨텍스트로 제어가 돌아올 때까지 호출을 미룹니다.
- greedy
참이면 이전 체크포인트 바로 뒤에서, 거짓이면 현재 체크포인트 바로 앞에서 롤백을 끝냅니다.
- label
None이 아니라면 롤백 완료 시 호출할 레이블입니다.
- abnormal
기본값인 참이면 전환 후 실행하는 스크립트가 비정상 모드로 실행되어 원래 발생할 화면 전환을 건너뜁니다. 상호작용이 시작되면 이 모드가 끝납니다.
- renpy.suspend_rollback(flag)
롤백이 중지되었던 구간은 건너뜁니다.
- flag:
flag가 참이면 롤백을 중지하고, 거짓이면 재개합니다.
롤백 차단
경고
롤백 차단은 사용자에게 불편합니다. 실수로 다른 선택지를 클릭해도 바로잡을 수 없습니다. 롤백은 저장과 불러오기에 해당하므로 사용자가 더 자주 저장해야 하며, 게임 몰입을 방해합니다.
롤백을 일부 또는 전부 비활성화할 수 있습니다. 전혀 원하지 않으면 config.rollback_enabled로 끄면 됩니다.
더 흔한 경우는 부분 차단입니다. renpy.block_rollback()을 호출하면 해당 지점 이전으로 돌아가지 못하게 합니다. 예를 들면 다음과 같습니다.
label final_answer:
"그게 최종 답변인가?"
menu:
"예":
jump no_return
"아니요":
"우리에겐 네 입을 열게 할 방법이 있지."
"어떤 방법일지 잘 생각해 봐."
"한 번만 더 묻겠다..."
jump final_answer
label no_return:
$ renpy.block_rollback()
"좋다. 이제 돌이킬 수 없다."
no_return 레이블에 도달하면 메뉴까지 롤백할 수 없습니다.
롤백 고정
롤백 고정은 자유로운 롤백과 완전 차단의 중간 선택입니다. 롤백은 허용하되 이전 결정을 바꾸지 못하게 합니다. 다음처럼 renpy.fix_rollback()을 사용합니다.
label final_answer:
"그게 최종 답변인가?"
menu:
"예":
jump no_return
"아니요":
"우리에겐 네 입을 열게 할 방법이 있지."
"어떤 방법일지 잘 생각해 봐."
"한 번만 더 묻겠다..."
jump final_answer
label no_return:
$ renpy.fix_rollback()
"좋다. 이제 돌이킬 수 없다."
fix_rollback 호출 뒤에도 메뉴로 돌아갈 수는 있지만 다른 선택지를 고를 수는 없습니다.
몇 가지 주의 사항이 있습니다. Ren'Py는 checkpoint()에 전달한 데이터를 자동 잠급니다. 그러나 범용 시스템이므로 이를 우회해 예측하기 어려운 변경을 하는 스크립트도 가능합니다. 특히 call screen은 고정 롤백과 잘 맞지 않습니다. 문제가 되는 지점에서 롤백을 차단할 책임은 제작자에게 있습니다.
메뉴, renpy.input(), renpy.imagemap()의 내장 상호작용 기능은 fix_rollback과 완전히 연동되도록 설계되었습니다.
고정 롤백의 스타일
fix_rollback은 메뉴와 이미지맵의 기능을 바꾸므로 모양에도 반영하는 것이 좋습니다. 이를 위해 메뉴 버튼의 위젯 상태 변화를 이해해야 합니다. config.fix_rollback_without_choice로 두 모드 중 하나를 선택할 수 있습니다.
기본 모드는 선택했던 항목을 "selected"로 설정하여 "selected_" 속성을 활성화합니다. 다른 버튼들은 비활성화하고 "insensitive_" 속성으로 표시합니다. 결과적으로 선택 가능한 항목 하나만 남습니다.
config.fix_rollback_without_choice가 False이면 모든 버튼을 비활성화합니다. 선택했던 항목은 "selected_insensitive_", 다른 항목들은 "insensitive_" 속성을 사용합니다.
고정 롤백과 사용자 정의 스크린
흔한 사용 사례를 돕는 두 액션이 있습니다. ui.ChoiceReturn()은 버튼을 클릭하면 값을 반환합니다. ui.ChoiceJump()는 스크립트 레이블로 이동합니다. 단, call screen 문으로 스크린을 호출했을 때만 제대로 작동합니다.
예제:
screen demo_imagemap():
roll_forward True
imagemap:
ground "imagemap_ground.jpg"
hover "imagemap_hover.jpg"
selected_idle "imagemap_selected_idle.jpg"
selected_hover "imagemap_hover.jpg"
hotspot (8, 200, 78, 78) action ui.ChoiceJump("swimming", "go_swimming", block_all=False)
hotspot (204, 50, 78, 78) action ui.ChoiceJump("science", "go_science_club", block_all=False)
hotspot (452, 79, 78, 78) action ui.ChoiceJump("art", "go_art_lessons", block_all=False)
hotspot (602, 316, 78, 78) action ui.ChoiceJump("home", "go_home", block_all=False)
screen rps():
roll_forward True
hbox:
imagebutton:
idle "rock.png"
hover "rock_hover.png"
selected_insensitive "rock_hover.png"
action ui.ChoiceReturn("rock", "바위", block_all=True)
imagebutton:
idle "paper.png"
hover "paper_hover.png"
selected_insensitive "paper_hover.png"
action ui.ChoiceReturn("paper", "보", block_all=True)
imagebutton:
idle "scissors.png"
hover "scissors_hover.png"
selected_insensitive "scissors_hover.png"
action ui.ChoiceReturn("scissors", "가위", block_all=True)
if renpy.in_fixed_rollback():
textbutton "다음으로":
action Return(renpy.roll_forward_info())
# required because of the block_all=True in all the other buttons
label dough:
call screen rps
$ chosen = _return
$ renpy.fix_rollback()
m "[chosen]!"
fix_rollback과 연동하는 Python 코드를 작성할 때 알아둘 사항이 있습니다. 첫째, renpy.in_fixed_rollback()으로 현재 고정 롤백 상태인지 확인할 수 있습니다. 둘째, 이 상태에서 ui.interact()는 어떤 액션을 실행했든 전달된 roll_forward 데이터를 반환합니다. 따라서 ui.interact()/renpy.checkpoint()를 사용하면 작업 대부분이 처리됩니다.
롤백 차단 및 고정 함수
- renpy.block_rollback()
현재 문 이전으로 롤백하지 못하게 합니다.
- renpy.fix_rollback()
현재 문 이전에 내린 결정을 바꾸지 못하게 합니다.
- renpy.in_fixed_rollback()
현재 롤백 중이고 현재 컨텍스트가 이미 실행한 renpy.fix_rollback() 문 이전이면 참을 반환합니다.
- ui.ChoiceJump(label, value, location=None, block_all=None, sensitive=True, args=None, kwargs=None)
고정 롤백에 맞게 버튼 상태를 관리하면서 value를 반환하는 선택지 액션입니다. 동작은 block_all 설명을 참고하세요.
- label
버튼의 문구입니다. 이미지 버튼이나 핫스폿에서는 아무 값이어도 됩니다. 현재 스크린의 선택지를 구분하는 고유 식별자로 쓰며, location과 함께 선택 여부를 저장합니다.
- value
이동할 위치입니다.
- location
현재 선택지 스크린의 고유 위치 식별자입니다.
- block_all
거짓이면 선택했던 버튼에 selected 역할을, 그렇지 않은 버튼에 insensitive 역할을 부여합니다.
참이면 고정 롤백 중 항상 비활성 상태가 됩니다.
None이면
config.fix_rollback_without_choice에서 값을 가져옵니다.스크린의 모든 항목에 참을 지정하면 클릭할 수 없게 됩니다. 롤포워드는 계속 작동합니다.
- ui.ChoiceReturn(label, value, location=None, block_all=None, sensitive=True, args=None, kwargs=None)
고정 롤백에 맞게 버튼 상태를 관리하면서 value를 반환하는 선택지 액션입니다. 동작은 block_all 설명을 참고하세요.
- label
버튼의 문구입니다. 이미지 버튼이나 핫스폿에서는 아무 값이어도 됩니다. 현재 스크린의 선택지를 구분하는 고유 식별자로 쓰며, location과 함께 선택 여부를 저장합니다.
- value
선택했을 때 반환할 값입니다.
- location
현재 선택지 스크린의 고유 위치 식별자입니다.
- block_all
거짓이면 선택했던 버튼에 selected 역할을, 그렇지 않은 버튼에 insensitive 역할을 부여합니다.
참이면 고정 롤백 중 항상 비활성 상태가 됩니다.
None이면
config.fix_rollback_without_choice에서 값을 가져옵니다.스크린의 모든 항목에 참을 지정하면 클릭할 수 없게 됩니다. 롤포워드는 계속 작동합니다.
NoRollback
- class NoRollback
이 클래스와 하위 클래스의 인스턴스는 롤백에 참여하지 않습니다. NoRollback 인스턴스를 통해 도달하는 객체는 다른 경로로도 접근할 수 있을 때만 참여합니다.
- class SlottedNoRollback
이 클래스를 상속한 인스턴스는 롤백에 참여하지 않습니다.
NoRollback과 달리 연결된 딕셔너리가 없어__slots__로 메모리 사용량을 줄일 수 있습니다.NoRollback 인스턴스를 통해 도달하는 객체는 다른 경로로도 접근할 수 있을 때만 롤백에 참여합니다.
예를 들면 다음과 같습니다.
init python:
class MyClass(NoRollback):
def __init__(self):
self.value = 0
label start:
$ o = MyClass()
"환영합니다!"
$ o.value += 1
"o.value는 [o.value]입니다. 롤백한 뒤 클릭해서 다시 진행할 때마다 이 값이 증가합니다."
롤백 지원 클래스
게임의 롤백 지원을 돕는 클래스입니다. 일부 상황에서 유용할 수 있습니다.
- class MultiRevertable
MultiRevertable은 여러 종류의 되돌릴 수 있는 객체를 상속하게 하는 믹스인 클래스입니다. 먼저 MultiRevertable을, 그다음 원하는 되돌릴 수 있는 클래스들을 상속합니다.
예를 들어 다음 코드는
class MyDict(MultiRevertable, dict, object): pass
dict 내용과 객체 필드가 모두 롤백되는 클래스를 만듭니다.
- class defaultdict(default_factory, /, *args, **kwargs)
되돌릴 수 있는 collections.defaultdict입니다. 팩터리 함수를 받습니다. 없는 키에 접근하면 default_factory에 키를 인수로 전달해 호출하고 결과를 반환합니다.
default_factory 속성은 있지만 롤백에 참여하지 않으므로 변경하지 않아야 합니다.