저장하기, 불러오기, 롤백하기

렌파이에서는 게임 상태를 저장하거나, 저장한 상태를 불러오거나, 이전 게임 상태로 롤백하는 기능을 지원합니다. 구현된 방식은 조금 다르지만, 롤백으로는 사용자의 입력을 기다리는 각 명령문이 시작되었을 때 게임을 저장한 뒤에 사용자가 롤백했을 때 저장한 상태를 불러옵니다.

주석

되도록이면 게임 엔진 버전이 달라도 세이브 파일이 호환될 수 있도록 노력하지만 호환성은 보장되지 않습니다. 세이브 파일 호환성을 없애더라도 더 큰 이득이 있다면 호환성을 포기할 수도 있습니다.

저장하는 데이터

렌파이는 게임 상태를 저장하려 합니다. 이 때 저장하는 상태로는 내부 상태와 파이썬 상태가 있습니다.

내부 상태는 게임이 시작되고 나서 바뀌어야 하는 렌파이의 모습 전부로 이루어져있으며, 아래와 같은 데이터 등이 그 예입니다:

  • 현재 명령문 및 반환되어야 하는 명령문 전부.
  • 화면에 나타난 이미지 및 디스플레이어블.
  • 화면에 나타난 스크린 및 해당 스크린에서 사용하는 값과 변수.
  • 렌파이에서 재생중인 음악.
  • nvl 모드의 텍스트 덩어리 목록.

파이썬 상태는 저장 영역에 있는 변수 중에서 게임이 시작되고 나서 변경된 변수 및 해당 변수에서 접근할 수 있는 모든 객체로 이루어져있습니다. 중요한 것은 변수에 대한 변경점만 기록하며 객체의 필드는 바뀌어도 객체는 저장되지 않는다는 점입니다.

예제를 살펴보겠습니다.

define a = 1
define o = object()

label start:
     $ b = 1
     $ o.value = 42

위 예제 스크립트에서는 b 만 저장합니다. a가 저장되지 않는 까닭은 a는 게임이 시작되고 나서 바뀌지 않았기 때문입니다. 'o` 도 바뀌지 않았기 때문에 저장되지 않습니다. o 객체의 참조가 바뀌었을 뿐이지 변수 자체가 바뀐 것은 아닙니다.

저장하지 않는 데이터

게임이 시작하기 전에 바뀌지 않는 파이썬 변수는 저장되지 않습니다. 만약 어느 변수가 저장되어있는 다른 변수와 같은 객체를 참조하는 경우 문제가 발생할 수 있습니다. 예제를 살펴보겠습니다.

init python:
    a = object()
    a.f = 1

label start:
    $ b = a
    $ b.f = 2

    "a.f=[a.f] b.f=[b.f]"

위 예제 스크립트에서 ab 는 서로 같은 객체를 참조하고 있습니다. 그러나 저장하거나 불러오는 과정에서 이 상황이 바뀌어, ab 가 서로 다른 객체를 참조하게 됩니다. 이렇게 되면 상당히 헷갈리기 때문에 저장된 변수와 저장되지 않은 변수가 서로 같은 객체를 참조하도록 하지 말아야 합니다. (위와 같은 상황이 발생할 가능성은 낮지만 저장되지 않은 변수와 저장된 필드가 같은 객체를 가리킬 때 문제가 일어날 수 있습니다.)

저장되지 않는 상태의 종류에는 여러가지가 있습니다. :

제어 흐름 경로
렌파이는 현재 명령문과 반환해야 하는 명령문만을 저장합니다. 어떻게 해당 명령문을 실행하게 되었는지는 기억하지 않습니다. (변수를 배정하는 등) 코드가 게임에 추가되었다면 해당 코드는 실행되지 않습니다.
디스플레이어블에 이미지 이름을 매핑한 것
매핑은 저장이 안 되기 때문에 게임을 다시 불러올 때는 이미지가 다른 이미지로 바뀔 수도 있습니다. 이로 인해 게임이 점점 진행되면서 이미지가 새 이미지 파일로 바뀔 수 있는 것입니다.
환경설정 변수, 스타일, 스타일 속성
환경설정 변수와 스타일은 게임의 일부로서 저장되지 않습니다. 그러므로 환경설정 변수와 스타일은 init 블록에서만 바꿔야 하며 게임이 시작되고 난 후에는 건드리지 말아야합니다.

렌파이가 저장 기능을 수행하는 경우

렌파이는 인터렉션이 일어나는 상황에서 가장 마지막 인터렉션을 일으키는 명령문이 시작될 때 저장을 합니다.

여기서 기억해야할 점은 명령문이 시작 될 때 저장을 한다는 사실입니다. 인터렉션이 여러 번 발생하는 명령문을 실행하는 중에 불러오거나 롤백하면, 명령문이 시작되었을 때 활성화된 상태가 렌파이가 저장하는 상태가 됩니다.

파이썬을 이용해 정의한 명령문에서 이런 문제가 발생할 수 있습니다.:

python:
     i = 0
     while i < 10:
          i += 1
          narrator("카운트는 이제 [i].")

위 코드에서 사용자가 중간에 게임을 저장하고 불러오면 루프가 새로 시작됩니다. 파이썬 대신 이 문제는 렌파이에서 비슷한 코드를 작성하는 것으로 피할 수 있습니다.:

$ i = 0
while i < 10:
     $ i += 1
     "카운트는 이제[i]."

렌파이가 저장할 수 있는 데이터

렌파이는 게임 상태를 저장하기 위해 파이썬 피클 시스템을 사용합니다. 피클 모듈로 저장할 수 있는 데이터는 다음과 같습니다.:

  • 기본 타입 객체. True, False, None, 정수, 문자열, 부동소수점, 복소수, 유니코드.
  • 복합 타입 객체. 리스트, 튜플, 세트, 딕셔너리.
  • 사용자 정의 객체, 클래스, 함수, 메소드, 묶인 메소드. 이들을 온전히 저장하려면 이들의 원래 이름 그대로 사용할 수 있어야 한다.
  • 캐릭터, 디스플레이어블, 트랜스폼, 트랜지션 객체.

다음과 같은 타입은 피클할 수 없습니다.:

  • 렌더Render 객체
  • 반복자Iterator 객체.
  • 파일 유사File-like 객체.
  • 내부 함수 및 람다Lambda.

기본적으로 렌파이는 게임을 저장할 때 cPickle 모듈을 사용합니다. config.use_cpickle 변수값을 바꾸면 피클 모듈을 대신 사용합니다. 게임이 느려지지만 저장할 때 발생하는 에러를 더 잘 보고합니다.

저장 기능과 변수

고급 세이브 시스템에서 사용할 수 있는 변수는 하나입니다.:

save_name = ...

각 세이브에 저장할 문자열. 세이브에 이름을 지정해 세이브들이 서로 다르다는 정보를 사용자에게 알려줄 수 있다.

여러가지 고급 액션이나 기능은 스크린 액션 페이지에서 확인할 수 있습니다. 다음은 하급 세이브 및 로드 액션 목록입니다.

renpy.can_load(filename, test=False)

filename 을 불러올 수 있다면 참을, 아니라면 거짓을 반환한다.

renpy.copy_save(old, new)

`old`에 있는 세이브를 `new`로 복사한다. (`old`가 없다면 작동하지 않는다.)

renpy.list_saved_games(regexp='.', fast=False)

저장된 게임을 목록으로 만든다. 각 게임 세이브가 반환하는 튜플에는 다음과 같은 데이터가 들어있다.:

  • 세이브 파일이름filename.
  • 파일에 저장한 기타 정보extra_info .
  • 게임을 저장할 때 사용된 스크린샷을 표시하는
디스플레이어블.
  • UNIX 시간을 기준으로 게임이 저장된 초단위 시간.
regexp
리스트를 걸러내기 위해 파일이름에 대조할 정규 표현식.
fast
참이면 튜플 대신에 파일 이름이 반환된다.
renpy.list_slots(regexp=None)

비어있지 않은 세이브 슬롯의 리스트를 반환한다. regexp 가 존재한다면, regexp 로 시작하는 슬롯만 반환된다. 슬롯은 문자열 순서대로 정렬된다.

renpy.load(filename)

filename 에서 게임 상태를 불러온다. 불러온 후에는 이 함수를 불렀던 이전 상태로 돌아갈 수 없다.

renpy.newest_slot(regexp=None)

가장 최신 세이브 슬롯(수정된 시간이 가장 최근인 세이브 슬롯)의 이름을 반환한다. 없다면 None을 반환한다.

regexp 가 존재한다면, regexp 로 시작하는 슬롯만 반환된다.

renpy.rename_save(old, new)

세이브 파일 이름을 old 에서 new 로 바꾼다. (`old`가 없다면 작동하지 않는다.)

renpy.save(filename, extra_info='')

세이브 슬롯에 게임 상태를 저장한다.

filename
세이브 슬롯의 이름을 지정하는 문자열. 변수 이름과는 다르게 이 문자열은 파일 이름과 대략적으로 일치한다.
extra_info
세이브 파일에 저장되어야 할 추가 문자열. 보통 이 값은 save_name 변수의 값이다.

이 함수를 호출하기 전에 renpy.take_screenshot() 를 먼저 호출해야 한다.

renpy.slot_json(slotname)

`slotname`의 json 정보를 반환하거나, 슬롯이 비었다면 None을 반환한다.

renpy.slot_mtime(slotname)

`slot`의 수정 시간을 반환하거나, 슬롯이 비었다면 None을 반환한다.

renpy.slot_screenshot(slotname)

`slotname`의 스크린샷으로 사용되는 디스플레이어블을 반환하거나 슬롯이 비었다면 None을 반환한다.

renpy.take_screenshot(scale=None, background=False)

스크린샷을 찍는다. 이 스크린샷은 게임 세이브의 일부로서 저장된다.

filename 세이브를 지운다.

데이터를 불러온 후에 데이터 유지하기

게임이 불려왔을 때 게임 상태는 (아래에서 설명한 롤백 시스템을 사용해) 현재 명령문이 실행을 시작할 때의 게임 상태로 초기화됩니다.

간혹 이런 작동 방식이 발생하지 않아야 할 경우가 있습니다. 예를 들어 값을 수정할 수 있는 어떤 스크린이 있을 때 게임을 불러온 후에도 이 값을 유지해야하는 경우가 있습니다. 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()

불러오기가 발생할 때 현재 명령문과 다음 체크포인트를 포함하는 명령문 사이에 수정된 데이터를 유지시킨다.

롤백

최신 프로그램 대부분에 있는 되돌리기/다시하기 기능과 비슷하게 사용자는 롤백 기능으로 게임을 이전 상태로 되돌릴 수 있습니다. 롤백 이벤트가 일어나는 동안 게임 화면이나 게임에서 사용하는 변수를 시스템이 알아서 관리하긴 합니다만, 여러분이 게임을 만들 때 알아두어야 할 사항이 몇 가지 있습니다.

롤백 및 롤포워드 지원

렌파이 명령문은 대부분 알아서 롤백과 롤포워드 기능을 지원합니다. ui.interact() 함수를 직접 호출한다면 롤백과 롤포워드 기능을 수동으로 추가해야 합니다. 다음과 같은 구조로 만들 수 있습니다.:

# 롤백 하지 않는다면 None. 롤포워드 하고 있다면
# 마지막 체크포인트를 지나온 후부터 경과된 시간이 값이 된다.
roll_forward = renpy.roll_forward_info()

# 여기에 스크린을 설정한다...

# 사용자에게 인터렉션을 받는다.
rv = ui.interact(roll_forward=roll_forward)

# 인터렉션 결과를 저장한다.
renpy.checkpoint(rv)

renpy.checkpoint를 호출하고 난 뒤에는 사용자가 게임과 상호작용 할 수 없어야 합니다. (만약 상호작용 한다면 사용자가 롤백을 할 수 없을 것입니다.)

renpy.can_rollback()

롤백할 수 있다면 참을 반환한다.

renpy.checkpoint(data=None)

현재 명령문을 사용자가 롤백할 수 있는 체크포인트로 만든다. 이 함수가 한 번 호출되고 나면 현재 명령문에서는 사용자가 더 이상 인터렉션을 할 수 없어야 한다.

data
롤백이 실행되었을 때 renpy.roll_forward_info() 가 이 데이터를 반환한다.
renpy.in_rollback()

롤백이 실행되었다면 참을 반환한다.

renpy.roll_forward_info()

롤백 상태일 때 마지막으로 이 명령문이 실행되었을 때 renpy.checkpoint() 에 전달된 데이터를 반환한다. 롤백 상태가 아니라면 None을 반환한다.

renpy.rollback(force=False, checkpoints=1, defer=False, greedy=True, label=None)

이전 체크포인트로 게임 상태를 롤백한다.

force
참이라면 롤백이 모든 상황에서 발생한다. 그 외에는 롤백은 오직 스토어, 상황, 설정에서만 활성화될 것이다.
checkpoints
렌파이는 이 값에 지정한 횟수만큼의 호출을 통해 renpy.checkpoint로 롤백한다. 조건에 따라 가능한 만큼 롤백할 것이다.
defer
참이라면 메인 상황의 코드가 실행되기 전까지이 호출은 미뤄질 것이다.
greedy
참이라면 롤백은 이전 체크포인트 바로 앞에서 종료한다. 거짓이라면 롤백은 현재 체크포인트 바로 전에 종료한다.
label
None이 아니라면 롤백을 완료했을 때 호출될 레이블.
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 함수가 호출된 뒤에 선택지 화면으로 롤백할 수는 있습니다. 하지만 다른 선택문을 고를 수는 없게 됩니다.

fix_follback 기능을 사용하는 게임을 기획할 때 생각해보아야 할 주의사항이 몇 가지 있습니다. 렌파이는 checkpoint() 함수에 전달된 데이터를 전부 자동으로 암호화합니다. 그러나 렌파이의 특성상 파이썬 코드로 이 기능을 우회하고 예상치 못한 결과가 발생할 수 있는 방법으로 렌파이 엔진를 바꿀 수도 있습니다. 이를 방지하기 위해 추가로 코드를 작성하거나 문제가 발생할 수 있는 부분에 롤백을 막는 일은 게임 기획자에게 달려있습니다.

선택지, 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_" 접두사가 붙은 스타일 속성을 사용합니다.

롤백 고정 및 맞춤 스크린

fix_rollback 시스템을 잘 다루어야 하는 파이썬 루틴을 만들 때 알아두어야 할 점이 몇가지 있습니다. 우선 renpy.in_fixed_rollback() 함수는 현재 게임이 롤백 고정 상태이어야 하는지 결정하기 위해 사용될 수 있습니다. 둘째로, 롤백 고정 상태일 때 ui.interact() 는 어떤 액션이 실행되든 항상 roll_forward 데이터를 반환합니다. 즉, ui.interact() / renpy.checkpoint() 함수가 사용될 때는 이미 작업이 대부분 완료된 상태라는 뜻입니다.

맞춤 스크린을 간단하게 만들 수 있도록 흔하게 사용할 수 있는 두 가지 액션을 마련해두었습니다 ui.ChoiceReturn() 액션은 이 액션이 적힌 버튼이 클릭되었을 때의 값을 반환합니다. ui.ChoiceJump() 액션은 특정 스크립트 레이블로 넘어가도록 할 때 사용할 수 있습니다. 그러나 이 두 액션은 오직 스크린이 call screen 명령문으로 호출되었을 때만 제대로 작동합니다.

예제:

screen demo_imagemap:
    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 uiChoiceJump("home", "go_home", block_all=False)

예제:

python:
    roll_forward = renpy.roll_forward_info()
    if roll_forward not in ("Rock", "Paper", "Scissors"):
        roll_forward = None

    ui.hbox()
    ui.imagebutton("rock.png", "rock_hover.png", selected_insensitive="rock_hover.png", clicked=ui.ChoiceReturn("rock", "Rock", block_all=True))
    ui.imagebutton("paper.png", "paper_hover.png", selected_insensitive="paper_hover.png", clicked=ui.ChoiceReturn("paper", "Paper", block_all=True))
    ui.imagebutton("scissors.png", "scissors_hover.png", selected_insensitive="scissors_hover.png", clicked=ui.ChoiceReturn("scissors", "Scissors", block_all=True))
    ui.close()

    if renpy.in_fixed_rollback():
        ui.saybehavior()

    choice = ui.interact(roll_forward=roll_forward)
    renpy.checkpoint(choice)

$ renpy.fix_rollback()
m "[choice]!"

롤백 방지 및 롤백 고정 관련 함수

renpy.block_rollback()

게임에서 현재 명령문 이전으로 롤백하지 못하도록 막는다.

renpy.fix_rollback()

이 명령문을 사용하기 이전에 내린 결정은 사용자가 바꿀 수 없도록 막는다.

renpy.in_fixed_rollback()

현재 롤백 중이며 현재 상황이 renpy.fix_rollback() 명령문을 실행하기 전이라면 참을 반환한다.

ui.ChoiceJump(label, value, location=None, block_all=None)

`value`를 반환하는 선택문 액션. 롤백 고정 기능에 맞게 버튼 상태를 관리한다. (작동 방식에 대한 설명은 block_all 부분을 참조하라.)

label
버튼의 레이블 텍스트. 이미지 버튼이나 핫스팟에 적용할 때에는 텍스트가 아니어도 상관 없다. label`은 현재 스크린에서 고유한 식별자로 사용된다. `location 과 함께 이 선택문이 선택되었는지 여부를 저장할 때 사용된다.
value
버튼을 클릭했을 때 넘어갈 곳.
location
현재 선택지 화면에 지정할 고유 위치 식별자.
block_all

False면 선택된 선택지일 때 버튼은 selected 상태가 되며 선택된 선택지가 아니라면 insensitive 상태가 된다.

True면 버튼은 롤백 고정 상태일 때 항상 insensitive 상태이다.

None이면 config.fix_rollback_without_choice 변수에서 값을 받는다.

화면에 있는 항목에 전부 True값을 지정하면 선택지를 클릭할 수 없게 된다(하지만 롤포워드 기능은 사용할 수 있다). ui.interact() 를 호출하기 전에 ui.saybehavior() 함수를 호출하면 이런 작동방식을 바꿀 수도 있다.

ui.ChoiceReturn(label, value, location=None, block_all=None)

`value`를 반환하는 선택문 액션. 롤백 고정 기능에 맞게 버튼 상태를 관리한다. (작동 방식에 대한 설명은 block_all 부분을 참조하라.)

label
버튼의 레이블 텍스트. 이미지버튼이나 핫스팟에 적용할 때에는 텍스트가 아니어도 상관 없다. label`은 현재 스크린에서 고유한 식별자로 사용된다. `location 과 함께 이 선택문이 선택되었는지 여부를 저장하는 데 사용된다.
value
선택문이 선택되었을 때 반환할 값.
location
현재 선택지 화면에 지정할 고유 위치 식별자.
block_all

False면 선택된 선택지일 때 버튼은 selected 상태가 되며, 선택된 선택지가 아니라면 insensitive 상태가 된다.

True면 버튼은 롤백 고정상태일 때 항상 insensitive 상태이다.

None이면 config.fix_rollback_without_choice 변수에서 값을 받는다.

화면에 있는 항목에 전부 True값을 주면 선택지를 클릭할 수 없게 된다(하지만 롤포워드 기능은 사용할 수 있다). ui.interact() 를 호출하기 전에 ui.saybehavior() 함수를 호출하면 이런 작동방식을 바꿀 수도 있다.

롤백되지 않는 클래스 만들기

class NoRollback

이 클래스를 상속 받는 클래스의 인스턴스는 롤백 과정에 참여하지 않는다. NoRollBack 클래스의 인스턴스를 통해 접근할 수 있는 객체만이 다른 경로를 통해 접근할 수 있을 때에 롤백 과정에 참여한다.

예제:

init python:

    class MyClass(NoRollback):
        def __init__(self):
            self.value = 0

label start:
    $ o = MyClass()

    "어서와!"

    $ o.value += 1

    "o.value는 [o.value]. 네가 롤백한 다음에 클릭할 때마다 이 값은 증가할 거야."