セーブ、ロード、ロールバック 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

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

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

renpy.can_load(filename, test=False) link

filename がセーブスロットに存在するときに True を返し、それ以外の時に False を返します。

renpy.copy_save(old, new) link

セーブを old から new へコピーします。 ( old が存在しなければ何もしません。 )

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

セーブをリストアップします。各セーブに対して以下をタプルに含んで返します :

  • セーブのファイル名です。

  • 渡された extra_info です。

  • ゲームセーブ時に使用されたスクリーンショットを表示する displayable

  • ゲームが開始された時間を UNIX epoch からの秒数で返します。

regexp

リストを絞り込むためのファイル名の最初に対してマッチする正規表現

fast

fast が True なら、タプルの代わりにファイル名が返されます。

renpy.list_slots(regexp=None) link

空でないセーブスロットのリストが返されます。 regexp が存在すれば regexp で始まるスロットのみが返されます。スロットは文字列の順に並べられます。

renpy.load(filename) link

セーブスロット filename からゲームの状態をロードします。ファイルが正しくロードされると、この関数は復帰しません。

renpy.newest_slot(regexp=None) link

最も新しいのセーブスロットの名前 (セーブスロットとともに、最近の更新日時) を返すか、(マッチする)セーブがないときは None を返します。

regexp が存在すれば regexp で始まるスロットのみが返されます。

renpy.rename_save(old, new) link

セーブを old から new へリネームします。 ( old が存在しなければ何もしません。 )

renpy.save(filename, extra_info='') link

セーブスロットにゲーム状態をセーブします。

filename

セーブスロットの名前の文字列です。変数名は filename ですが、ファイル名の一部に対応するだけです。

extra_info

セーブファイルにセーブされる追加の文字列です。通常これは save_name の値です。

renpy.take_screenshot() がこの関数の前に呼び出されるべきです。

renpy.slot_json(slotname) link

slotname の json 情報を返すか、スロットが空のときは None を返します。

renpy.slot_mtime(slotname) link

slot の更新日時を返すか、スロットが空のときは None を返します。

renpy.slot_screenshot(slotname) link

slotname に対するスクリーンショットで利用可能な画面を返すか、スロットが空のときは None を返します。

renpy.take_screenshot(scale=None, background=False) 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
renpy.retain_after_load() link

現在のステートメントと次のチェックポイントを含むステートメントの間で変更されたデータを、ロード時に保持します。

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

renpy.can_rollback() link

ロールバック可能なら True を返します。

renpy.checkpoint(data=None) link

現在のステートメントをユーザーがロールバックできるチェックポイントにします。一旦この関数が呼ばれると、現在のステートメントではもうユーザーへのインタラクションはありません。

data

この data はゲームがロールバック中に renpy.roll_forward_info() によって返されます。

renpy.get_identifier_checkpoints(identifier) link

HistoryEntry オブジェクトの rollback_identifier を指定して、その identifier に到達するのに renpy.rollback() が通過する必要のあるチェックポイントの数を返します。

renpy.in_rollback() link

ゲームがロールバック中なら True を返します。

renpy.roll_forward_info() link

ロールバック時は最後にこのステートメントを実行したときに renpy.checkpoint() に渡されたデータが返されます。ロールバックの外では None を返します。

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

ゲームの状態を最後のチェックポイントまで巻き戻します。

force

True の場合、いかなる状況であってもロールバックが発生します。それ以外の場合は、store、context、config で有効化されている場合のみ発生します。

checkpoints

Ren'Py は、ここで指定された renpy.checkpoint の数だけロールバックします。この条件を満たす範囲で、可能な限りロールバックします。

defer

True の場合、呼び出しはメインコンテキストのコードが実行されるまで遅延されます。

greedy

True ならロールバッグは以前のチェックポイントの直後まで実行され、 False なら現在のチェックポイントの直前まで実行されます。

label

None を指定するか、ロールバックが完了した時に呼び出されるラベルを指定します。

abnormal

デファルトは True で、トランジション後に実行されるスクリプトはトランジションをスキップするアブノーマルなモードで実行されます。アブノーマルモードはインタラクションが始まると終了します。

renpy.suspend_rollback(flag) link

ロールバックはそれが停止している間の区間をスキップします。

flag:

flag が True なら、ロールバックは停止されます。 False ならロールバックは再開されます。

ロールバックの禁止 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

renpy.block_rollback() link

ゲームが現在のステートメントより前にロールバックすることを防ぎます。

renpy.fix_rollback() link

ユーザーが現在のステートメントより前にした決定を変更できなくします。

renpy.in_fixed_rollback() link

現在ロールバックが実行され、現在のコンテキストが renpy.fix_rollback() ステートメントが実行されるより前なら True を返します。

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

value を返す選択肢用のアクションで、固定ロールバックを考慮した方法でボタンを管理します。 ( 動作の詳細は block_all を参照 )

label

ボタンのラベルとなるテキストです。 imagebutton や hotspot にもこれは与えられます。このラベルは現在のスクリーン内の選択肢のユニークな識別子として使用されます。また、 location と共に、この選択肢が選択されたことがあるかどうかを保存するために使用されます。

value

ジャンプする場所

location

現在のスクリーンに対してユニークな場所の識別子です。

block_all

False なら、そのボタンは選択されていたなら選択状態で、選択されていなかったら無効になります。

True なら固定ロールバック中ボタンは常に無効です。

None なら config.fix_rollback_without_choice 変数から値はとられます。

スクリーンのすべての要素に True が与えられると、 クリックが出来なくなります。 ( ロールフォワードはまだ動作します。 ) これは ui.interact() の前に ui.saybehavior() を呼び出して変更可能です。

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

value を返す選択肢用のアクションで、固定ロールバックを考慮した方法でボタンを管理します。 ( 動作の詳細は block_all を参照 )

label

ボタンのラベルとなるテキストです。 imagebutton や hotspot にもこれは与えられます。このラベルは現在のスクリーン内の選択肢のユニークな識別子として使用されます。また、 location と共に、この選択肢が選択されたことがあるかどうかを保存するために使用されます。

value

その選択肢が選択されると返される値

location

現在のスクリーンに対してユニークな場所の識別子です。

block_all

False なら、そのボタンは選択されていたなら選択状態で、選択されていなかったら無効になります。

True なら固定ロールバック中ボタンは常に無効です。

None なら config.fix_rollback_without_choice 変数から値はとられます。

スクリーンのすべての要素に True が与えられると、 クリックが出来なくなります。 ( ロールフォワードはまだ動作します。 ) これは ui.interact() の前に ui.saybehavior() を呼び出して変更可能です。

NoRollback link

class NoRollback link

このクラスを継承したクラスのインスタンスはロールバックに参加しません。 NoRollback クラスのインスタンスからアクセス可能なオブジェクトはそれが他のルートからもアクセス可能な場合のみロールバックに参加します。

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."