セーブ、ロード、ロールバック link

Ren'Py はゲーム状態のセーブ、ゲーム状態のロード、そして以前のゲーム状態へのロールバックをサポートしています。少し流行とは違いますが、ロールバックはユーザーとのインタラクションを持つ各ステートメントの開始でセーブし、ユーザーがロールバックするとセーブをロードするものとして考えられます。

注釈

通常リリース間でのセーブの互換性を保とうとはしますが、この互換性は保証されません。十分大きな利益があればセーブの互換性を破壊する決定をします。

何がセーブされるか link

Ren'Py はゲーム状態のセーブを試みます。これは内部の状態と python の状態の両方を含みます。

内部状態は一旦ゲームが開始されてから Ren'Py が変更を試みるすべての要素で構成され、以下を含みます :

  • 現在のステートメントと呼び出し先から戻るすべてのステートメント

  • 表示されている画像と displayable

  • 表示されているスクリーンとそれらのスクリーン内部の変数の値

  • Ren'Py が再生している曲

  • NVLモードのテキストブロックのリスト

Python の状態は、store 内の変数のうちゲーム開始時から変更されたもの、及び、それらの変数から到達可能なすべてのオブジェクトから成ります。ここで、変数への変更という点が重要であり、オブジェクト内のフィールドへの変更は保存の対象にならないという点に注意してください。

この例では

define a = 1
define o = object()

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

b だけがセーブされます。 a は一旦ゲームが開始されてからは変更されていないのでセーブされません。 o はそれが参照するオブジェクトは変更されましたが、変数自体は変更されていないのでセーブされません。

何がセーブされないか link

Python 変数のうち、ゲーム開始時から変更されていないものは保存されません。これは、保存される変数が別の保存されないオブジェクトを参照(エイリアス)している場合に問題となりえます。例

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

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

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

ab はエイリアスです。セーブとロードはおそらくこのエイリアスを破壊し、 ab に違うオブジェクトを参照させます。これはとても面倒なことになり得るので、保存されたあるいは保存されない変数のエイリアスは避けるべきです。 ( これは直接的には滅多にないことですが、保存されない変数と保存されるフィールドのエイリアスで起こるかもしれません )

その他いくつかの種類の、セーブされない状態を示します:

制御フローのパス

Ren'Py がセーブするものは、現在のステートメントと、呼び出し先から戻るために必要なステートメントだけです。どうやってそこに到達したかは記憶しません。重要なことは、(変数の代入のような)コードがゲームに追加された場合、それは実行されないことです。

画像名の displayable へのマッピング

このマッピングはセーブされないので、画像はゲームをもう一度ロードするときに新しい画像に変更しているかもしれません。これにより画像はゲームの更新とともに新しいファイルに変更できるようになります。

設定変数、スタイル、スタイルプロパティー

設定変数とスタイルはゲームの一部としてはセーブされません。それ故、それらは init ブロックでのみ変更され、一旦ゲームが開始されたらそのままであるべきです。

どこで Ren'Py はセーブされるか link

セーブは最も外側のインタラクションコンテキストにある Ren'Py のステートメント開始点で実行されます。

ここで重要なことはセーブはステートメントの 開始点 で実行されることに気をつけることです。複数回実行されるステートメントの内部でロードやロールバックが起きると、ステートメント開始時にアクティブだった状態になります。

このことは python で定義されたステートメントで問題になり得ます。 このようなコードでは

python:

     i = 0

     while i < 10:

          i += 1

          narrator("The count is now [i].")

ユーザーが内部でセーブし、ロードすると、ループは最初から始まります。 python ではなく Ren'Py で同一のコードを行えばこの問題は避けられます

$ i = 0

while i < 10:

     $ i += 1

     "The count is now [i]."

Ren'Py は何をセーブできるか link

Ren'Py はゲーム状態をセーブするために python pickle システムを使用しています。 このモジュールは以下をセーブできます :

  • True, False, None, int, str, float, complex, str, unicode オブジェクトのような基本的な型

  • list, tuple, set, dict のようなコレクション型

  • ユーザー定義オブジェクト、クラス、関数、メソッド、 bound method 。これらの関数の保存を成功させるためには、それらをオリジナルの名前で利用可能なままにしておく必要があります。

  • Character, Displayable, Transform, Transition オブジェクト

ある特定の型は pickle できません :

  • Render オブジェクト

  • Iterator オブジェクト

  • File-like オブジェクト

  • 内部関数とラムダ関数

デフォルトで Ren'Py はゲームを保存するために cPickle module を使用します。 config.use_cpickle を設定すると代わりに Ren'Py は pickle module を使用します。これはゲームを遅くしますが、セーブエラーを送るにはよりよいです。

セーブ関数と変数 link

高級なセーブシステムによって使用される一つの変数があります。

save_name = ... link

これは各セーブに保存される文字列です。セーブに名前を与え、ユーザーがそれらを区別するのを助けるために使われます。

高級なセーブ用のアクションが アクション で説明されています。さらに以下の低級なセーブロード用の関数があります。

ロード後のデータ保持 link

ゲームがロードされると、ゲームの状態は現在のステートメントが処理を開始した時の状態まで ( 後述するロールバックシステムを用いて ) リセットされます。

いくつかの場合では、これは不適切かもしれません。例えば値を変更出来るスクリーンがある時は、ゲームのロード後もその値を保持したいかもしれません。 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

ロールバック link

ロールバックはユーザーがほとんどの現代的なアプリケーションで利用可能なアンドゥーやリドゥーとほとんど同様に、以前の状態にゲームを再現できるようにします。ロールバックの間システムは外見とゲーム変数の維持に注意していますが、ゲーム作成時にはいくつか配慮するべきところがあります。

ロールバックとロールフォワードのサポート link

ほとんどの 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 が呼び出された後はゲームとユーザーとのインタラクションがないことが重要です。 ( もしあれば、ユーザーはロールバック出来なくなります。 )

ロールバックの禁止 link

警告

ロールバックの禁止はユーザーフレンドリーではありません。ユーザーが間違えて意図しない選択肢をクリックしたら、そのミスを訂正できなくなります。ロールバックはセーブとロードに等しいので、ユーザーはさらにセーブを強要されてゲームの印象が悪くなります。

一部またはすべての場面でロールバックを無効化可能です。ロールバックが全く望まれていないなら、 config.rollback_enabled を利用して簡単に無効化できます。

より一般的なのはロールバックの一時的な禁止です。これは renpy.block_rollback() 関数によって行われます。呼び出されると Ren'Py がその位置より前にロールバック出来ないようにします。例えば

label final_answer:
    "Is that your final answer?"

menu:
    "Yes":
        jump no_return
    "No":
        "We have ways of making you talk."
        "You should contemplate them."
        "I'll ask you one more time..."
        jump final_answer

label no_return:
    $ renpy.block_rollback()

    "So be it. There's no turning back now."

ラベル no_return に着いたら、 Ren'Py は選択肢へのロールバックを許可しなくなります。

固定ロールバック link

固定ロールバックは自由なロールバックとロールバックの完全な禁止の中間に当たります。ロールバックは可能ですが、ユーザーは決定を変更出来ません。固定ロールバック以下の例で示すように、renpy.fix_rollback() 関数で実行されます。

label final_answer:
    "Is that your final answer?"
menu:
    "Yes":
        jump no_return
    "No":
        "We have ways of making you talk."
        "You should contemplate them."
        "I'll ask you one more time..."
        jump final_answer

label no_return:
    $ renpy.fix_rollback()

    "So be it. There's no turning back now."

fix_rollback 関数が呼び出された後もユーザーが選択肢へロールバックすることはまだ出来ます。しかし、違った選択をすることは出来ません。

fix_rollback についてゲームデザイン時にいくつか配慮すべき点があります。Ren'Py は自動的に checkpoint() に与えられたすべてのデータをロックするよう気をつけますが、 Ren'Py の性質のために python コードを書いてこれを突破し、結果を予測出来ない方法でこれらを変更することが可能です。プログラムを使用する場所でロールバックを禁止するか、これをうまく扱うために追加のコードを書くかはゲームデザイナー次第です。

選択肢や renpy.input(), renpy.imagemap() のような、組込みのユーザーに対するインタラクションは固定ロールバックでも十分に動作するようにデザインされています。

固定ロールバックのスタイル link

fix_rollback は menu や imagemap の機能を変更するので、外見にこのことを反映するのが賢明です。このためにはmenu button のウィジェットの状態がどうやって変更されるのかを理解することが重要です。 config.fix_rollback_without_choice を通して選択される2つのモードがあります。

デフォルトでは「selected」で、それゆえ「 selected_ 」を接頭辞に持つスタイルプロパティーを有効化します。それ以外のすべてのボタンは無効化され、「 insensitive_ 」を接頭辞に持つプロパティーを使用して表示されます。結果としてこれは選択肢にただ一つの選択可能な選択肢を残します。

config.fix_rollback_without_choice が False に設定されると、すべてのボタンが無効化されます。これはつまり選択された選択肢はスタイルプロパティーに「selected_insensitive_ 」の接頭辞を使用し、一方その他のボタンは「 insensitive_ 」を接頭辞に持つプロパティーを使用することを意味します。

固定ロールバックとスクリーンのカスタム link

固定ロールバックでも正常に動作する python の作業を書くためには、知っておくべきことが少しあります。まず最初に、 renpy.in_fixed_rollback() 関数はゲームが現在固定ロールバック中かどうかを判断するために使用可能です。第二に固定ロールバック中は ui.interact() はどんなアクションが処理されても、常に与えられた roll_forward データを返します。これはつまり ui.interact()/renpy.checkpoint() 関数が使用されると、動作のほとんどが決定されるということです。

カスタムスクリーンの作成を簡単にするために、ほとんど共通して使用される2つのアクションが提供されます。 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]!"

ロールバックの禁止と固定用関数 link

NoRollback link

init python:

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

label start:
    $ o = MyClass()

    "Welcome!"

    $ o.value += 1

    "o.value is [o.value]. It will increase each time you rolllback and then click ahead."