未来の自分向けに備忘録を残します。
MODスクリプト方式での実装方法をメモします。
仕組み
- Ren'Py 製ゲームは
game/フォルダに.rpyを置くだけ` でスクリプトが追加される - ファイル名は
cheat.rpyじゃなくてもOK(mod.rpyでも同じ) - ゲーム本体(
.rpaアーカイブ)の改造は不要 - やることは スクリプト解析 → 変数・ラベル把握 → 追加
.rpy作成 の3ステップ
1. 前提知識:Ren'Py とは
Ren'Py は Python ベースのビジュアルノベルエンジン。配布物はだいたい次の構造になる。
GameTitle/
├── GameTitle.exe # ランチャー
├── renpy/ # エンジン本体(触らなくてよい)
└── game/ # ★ ゲーム定義(MOD はここ)
├── script.rpy # または archive.rpa にパックされている
├── images.rpa
├── audio.rpa
└── (ここに追加 .rpy を置く)
重要:読み込みの仕組み
起動時に Ren'Py は game/ をスキャンし、
以下のファイル群を すべて1つのスクリプト空間にマージ する。
- ディスク上の
.rpy/.rpyc -
.rpaアーカイブ内のスクリプト
renpy/loader.py より:
Files on disk should be checked before archives.
ディスク上のファイルがアーカイブより優先される。
つまり .rpa を展開・再パックしなくても、
game/ に素の .rpy を置けば MOD として動く。
「Python製ゲーム」全般との違い
| Ren'Py | 一般的な Python ゲーム | |
|---|---|---|
| スクリプト追加 |
game/xxx.rpy を置くだけ |
import / フック改造が必要なことが多い |
| シーン移動 |
label + jump でやることが多い |
自前設計に依存 |
| 変数操作 |
store.変数名 でグローバルに触れる |
クラス設計次第 |
| 開発者コンソール | 標準装備(ON にするだけ) | ほぼ無い |
「Python 製だから cheat.rpy が効く」わけではなく、
「Ren'Py だから .rpy 追加 MOD が成立する」ということ。
2. 全体ワークフロー
① エンジン判定
Ren'Py か? → game/ と .rpa の有無を確認
② スクリプト解析
archive.rpa を展開 → script.rpy から label・変数を洗い出す
③ cheat.rpy(任意の名前)を作成
- config.developer / config.console でコンソール ON
- screen でチートメニュー(任意)
- Jump / SetVariable / Function で既存イベント・変数を操作
④ game/ に配置してゲーム再起動
自動で .rpyc にコンパイルされ読み込まれる
3. スクリプトの取り出し(.rpa 展開)
多くの Ren'Py 製ゲームは script.rpy を archive.rpa にパックしている。
ディスク上に .rpy が無ければ展開が必要。
展開用 Python スクリプト例
Ren'Py の RPAv3 形式向け(ゲームによってはツールが異なる場合あり):
import zlib
import pickle
import os
import sys
def extract_rpa(rpa_path, out_dir):
with open(rpa_path, "rb") as f:
header = f.read(40)
offset = int(header[8:24], 16)
key = int(header[25:33], 16)
f.seek(offset)
index = pickle.loads(zlib.decompress(f.read()))
for k, v in index.items():
if len(v[0]) == 2:
index[k] = [(o ^ key, d ^ key) for o, d in v]
else:
index[k] = [(o ^ key, d ^ key, s) for o, d, s in v]
for name, chunks in index.items():
dest = os.path.join(out_dir, name.replace("/", os.sep))
os.makedirs(os.path.dirname(dest), exist_ok=True)
parts = []
for o, d, *_ in chunks:
f.seek(o)
parts.append(f.read(d))
with open(dest, "wb") as out:
out.write(b"".join(parts))
if __name__ == "__main__":
extract_rpa(sys.argv[1], sys.argv[2])
python extract_rpa.py game/archive.rpa game/extracted
解析で調べるもの
| 調べる対象 | 用途 | 検索例 |
|---|---|---|
label xxx: |
イベントジャンプ先 | ^label event_ |
default 変数 = |
チートで書き換える値 | ^default |
define キャラ |
参照用 | ^define |
if stage == 等 |
イベント分岐条件 | ラベル周辺を読む |
展開結果は調査用。
MOD 本体は game/cheat.rpy だけで足りる。
4. 最小構成:開発者コンソールだけ有効化
これだけでも jump や変数操作が可能。
ファイル名は何でもよい。
# game/cheat.rpy
init python:
config.developer = True
config.console = True
| 操作 | 内容 |
|---|---|
| 開き方 | Shift + O(Ren'Py デフォルト) |
| イベントジャンプ | jump event_some_label |
| ラベル確認 | renpy.has_label("event_some_label") |
| 変数設定 | money = 99999 |
| ヘルプ | help |
ローカルラベル(サブラベル)のジャンプ
サブラベルは 親.子 形式が必須。. だけの名前は使えない。
jump event_2130_random.event_laundry
jump time_mctime.event_bath
コンソールで使えないこと
- 単独のローカルラベル名(
.event_laundryだけなど) -
call前提のイベントをjumpしたときの戻り先が不自然になる場合がある
5. 実用構成:チートメニュー付き MOD
コンソールは強力だがコマンドを覚える必要がある。
screen(UI)+ overlay でゲーム内メニューを作ると再利用しやすい。
5.1 Python ヘルパー(init python)
ゲームが default で定義した変数は store 経由で触れる。
以下は一例。
変数名はソースコードをgrepしてイイ感じに見つけること。
init python:
config.developer = True
config.console = True
def cheat_fill_vitals():
store.stamina = store.max_stamina
store.action_point = store.max_action_point
def cheat_jump(label, **setup):
for key, value in setup.items():
setattr(store, key, value)
renpy.jump(label)
def cheat_goto_hour(hour, minute=0):
store.current_hour = hour
store.current_minute = minute
renpy.jump("main_loop")
ポイント:
-
store.変数名でセーブデータと共有のゲーム変数にアクセス -
renpy.jump("label")でコンソールのjumpと同等のことができる - ジャンプ前に
setattr(store, ...)で前提条件をセットできる
5.2 キー入力でメニューを開く(overlay)
init -1 python:
config.overlay_screens.append("cheat_menu_toggle")
screen cheat_menu_toggle():
zorder 199
key "shift_K_c" action ToggleScreen("cheat_menu")
overlay_screens に登録した screen はゲーム中ずっと動く。
任意のキー(例: Shift+C)でメニューの開閉ができる。
5.3 メニュー本体
screen cheat_menu():
modal True
zorder 200
default page = "main"
add Solid("#000000bb")
frame:
xalign 0.5
yalign 0.5
xsize 900
ysize 700
padding (20, 16)
vbox:
spacing 8
text "チートメニュー (Shift+C)" size 28
# タブ切り替え
hbox:
textbutton "ホーム" action SetScreenVariable("page", "main")
textbutton "ジャンプ" action SetScreenVariable("page", "jump")
viewport:
scrollbars "vertical"
mousewheel True
ysize 500
vbox:
if page == "main":
textbutton "体力満タン" action Function(cheat_fill_vitals)
textbutton "メインループへ" action [Hide("cheat_menu"), Jump("main_loop")]
elif page == "jump":
textbutton "イベントA" action [Hide("cheat_menu"), Jump("event_xxx")]
textbutton "閉じる" action Hide("cheat_menu")
5.4 ボタンアクションの定番パターン
| やりたいこと | アクション |
|---|---|
| 変数を変える | SetVariable("money", 999999) |
| Python 関数を呼ぶ | Function(cheat_fill_vitals) |
| ラベルへ飛ぶ | Jump("event_xxx") |
| 複合処理 | [SetVariable("a_stage", 3), Hide("cheat_menu"), Jump("event_xxx")] |
| 条件分岐 | If(条件, true=..., false=Notify("...")) |
注意: renpy.jump() を含む Function() の後に別アクションを並べても、jump で制御が飛ぶため後続は実行されない。
セットアップ → ジャンプは 1つの Python 関数にまとめる と安全。
def cheat_goto_night_raid():
store.stamina = store.max_stamina
store.a_stage = 3
renpy.jump("battle_a")
6. イベントジャンプの実践ノウハウ
6.1 ラベル一覧の作り方
展開した script.rpy で検索:
# ripgrep 例
rg "^label " game/extracted/script.rpy
グローバルラベルはそのまま jump できる。
ローカルラベル(label .foo:)は 親ラベル.foo 形式。
6.2 前提変数が必要なイベント
多くのイベントはラベルだけでは正しく動かない。
分岐条件を script から読み取り、事前にセットする。
例(概念):
# stage3 の続行ルートを見たい
a_stage = 3
jump event_stage_3_reveal
# バトルをすぐ開始する
action_point = 5
stamina = 100
a_stage = 3
desire = 80
jump battle_a
6.3 1回限りイベントの再視聴
not xxx_viewed フラグで再発火を防いでいるゲームが多い。
再視聴用にフラグをリセットするボタンを用意すると便利。
def cheat_reset_story_flags():
store.event_50_viewed = False
store.event_150_viewed = False
# ... 他の *_viewed フラグも同様
getattr(store, "flag", False) で参照されているフラグは default が無くても store.flag = False でリセット可能。
6.4 時間帯の移動
時刻変数を変えて main_loop に jump するパターンが通用しやすい。
def cheat_goto_hour(hour, minute=0):
store.current_hour = hour
store.current_minute = minute
renpy.jump("main_loop")
main_loop 側が current_hour で time_1900_freetime 等に分岐しているゲームで効く。
7. 動作確認チェックリスト
-
game/cheat.rpyを置いたあと ゲームを再起動したか -
同じ場所に
cheat.rpycが生成されているか(起動時に自動コンパイル) - Shift+O でコンソールが開くか
-
renpy.has_label("xxx")がTrueか - ジャンプ先で即終了する場合、前提変数が足りない可能性
- セーブデータを壊すので 試す前にセーブしているか
8. うまくいかない場合
| 症状 | 原因と対処 |
|---|---|
.rpy を置いても無反応 |
再起動していない / game/ 以外に置いている |
| コンソールが開かない |
config.developer と config.console が両方 True か確認 |
Label xxx not found |
typo、またはローカルラベルを 親.子 形式で指定していない |
| イベントは始まるがすぐ戻る | 前提変数・フラグ不足。script.rpy の if 条件を読む |
| 画像・音声が出ない | jump でシーン初期化(scene)がスキップされている |
.rpa が展開できない |
RPA バージョン違い。unrpa 等の別ツールを試す |
配布版で難しいケース
-
config.developer = Falseがビルド時に固定されている - アーカイブが暗号化・難読化されている
- Ren'Py 以外のエンジンに偽装している
9. 他ゲーム用ミニテンプレ(コピペ起点)
# game/cheat.rpy
# ファイル名は何でもよい。game/ 直下に置く。
init python:
config.developer = True
config.console = True
def cheat_jump(label):
renpy.jump(label)
init -1 python:
config.overlay_screens.append("cheat_toggle")
screen cheat_toggle():
key "shift_K_c" action ToggleScreen("cheat_menu")
screen cheat_menu():
modal True
zorder 200
frame:
xalign 0.5
yalign 0.5
xsize 600
ypadding 20
xpadding 20
vbox:
spacing 8
text "Cheat Menu" size 24 bold True
text "Shift+O: 開発者コンソール" size 16
textbutton "メインループ (要ラベル名確認)" action [Hide("cheat_menu"), Jump("main_loop")]
textbutton "変数リセット例 (要変数名確認)" action SetVariable("money", 99999)
null height 10
textbutton "閉じる" action Hide("cheat_menu")
あとは script.rpy の調査結果でボタンを増やしていくだけ。
10. 設計のベストプラクティス
ファイル構成
game/
├── cheat.rpy # MOD 本体(Git 管理推奨)
└── extracted/ # 調査用(配布・コミット不要)
└── script.rpy
メニュー設計
タブ分けすると探しやすい。
| タブ | 内容 |
|---|---|
| リソース | 体力・お金・スタミナなど |
| ステージ | stage・フラグリセット |
| 時間帯 | hour 変更 + main_loop jump |
| イベント | キャラ別・ED別の label jump |
| その他 | ランダムイベント・ミニゲーム |
ドキュメントを残すこと
チートメニューと併せて、ラベル一覧・変数一覧のテキストファイルをゲームルートに置くと、数ヶ月後の自分が助かる。
- 操作手順(Shift+C / Shift+O)
- ラベル早見表
- 変数セット例
- BAD END / 続行ルートの分岐条件
11. 今回のチート制作で得た知見
やったこと
-
archive.rpaからscript.rpyを展開して変数・ラベルをソースコードから調査 -
game/cheat.rpyを新規作成して動作することを確認(元の配布物には存在しない) - 開発者コンソール(Shift+O)+ チートメニュー(Shift+C)を実装
- ラベル早見表
イベントジャンプ.mdを制作
やっていないこと
-
archive.rpaの再パック -
Game.exeやrenpy/の改造 - ゲーム本体スクリプトの直接編集
Ren'Py の変数まわりで頻出する単語
-
default var = 0… ゲームが定義。セーブに含まれる -
store.var… 実行時にどこからでもアクセス可能 -
define CHAR = Character(...)… キャラ定義。ジャンプには通常不要 -
label/jump/call/return… シーン遷移の基本
参考リンク
- Ren'Py 公式ドキュメント(英語)
- Ren'Py 公式ドキュメント(日本語)
- 開発者コンソール:
renpy/common/00console.rpy(jumpコマンドの実装あり) - ローダー:
renpy/loader.py(ディスク優先の記述あり)