Ren'Pyを学ぶ
リンク
- Ren'Py公式ドキュメント
はじめに
Ren'Pyとはビジュアルノベル専用のゲームエンジンであり、20年以上にもわたる歴史を持つ由緒正しいソフトウェアである。そもそも、ビジュアルノベルというゲームジャンルはそれ自体でも歴史が長い。35年前から今日に至るまで、ビジュアルノベルは――そのいちばん基礎的で、魅力的なかの特有のゲームシステムを失うことなく、実に見事に――多様な進化を遂げてきた。このビジュアルノベルの緩やかながらも確実な進化を前に、Ren'Pyはまさに柔軟に変化に応えてきた。
ビジュアルノベルの深い歴史に比べれば私のプレイ歴は非常に浅く、高々5年に過ぎない。初めてプレイしたビジュアルノベルは07th Expansion作の『ひぐらしのなく頃に 奉』であるが、当時は結局途中で投げてしまった――今年の2月から5年越しに再開し、いよいよ次は祭囃し編である。そしてR18作品としては、2021年の1月に購入したオーガスト作の『穢翼のユースティア』が私の初めてプレイした作品である。
また、私はプログラミングの歴もまだまだ浅い。大学1年生の夏に初めてC言語に触れてからまだ2年も経っていない。触れたことのある言語もそう多くはない。C、C++、Java、Ruby、HTML/CSS、JavaScript、Vue.js、Python、(MIPS、)こんな程度である。
ゲーム開発の経験もほとんどないと言っていい。ただの素人である。
そんな私がイチからRen'Pyを勉強し、イッチョマエにゲーム開発をしてみよう。ということで立てたスレッドである。
注意
まず、最終的な目標とするレベルだが、ほとんど全ての機能を自由に弄れる程度まで仕上げたいと思う。一応私は、腐っても国立大学の情報科に通う身であるため、このくらいのことは当然にできていなければならないのである。どうやらRen'PyはグラフィクスにOpenGLを用いているらしいが、必要になればOpenGLもまとめて学習する気でいる。このために、このスレッドは手探りかつ長期的な展開になることが予想される。
実は少しだけRen'Pyはいじったことがあるため、このスレッドでは中途半端だが私が学習してきたことの続きからスタートすることになる。後述するが、記事にする際はきちんと最初からまとめる予定なので安心してほしい。
また、今のところ吉里吉里ZやLight.vn、ティラノスクリプトなどの他のビジュアルノベルのゲームエンジンを熱心に触れるつもりはない。触るエンジンはどれか一つに絞って、それを極めることが最善だと考えたからだ。
最後に、目標達成後このスレッドは閉じることにする。ここで積み上げたものを全てまとめて記事にするためである。最も基礎的なところからハイレベルなところまで、順番にまとめていきたい。
Displayable
DisplayableはどうやらRen'Pyを扱ううえで欠かせない要素であるらしい。Displayableとはユーザに表示されるあらゆるものの抽象的なオブジェクトであり、これが自由に操作できれば確かにビジュアルノベルのビジュアルの部分に大きく近づけることになるだろう。
Displayableというテーマは大きく、公式ドキュメントにもDisplayableのセクションがある。本投稿ではこのDisplayableについて理解を深めていく。
だが、いきなりDisplayableに入ろうとしてもとっつきづらそうに感じたので、まずはGUIカスタマイズガイドというセクションについて理解する。
GUIカスタマイズガイド
GUIとはグラフィカルユーザインタフェースの略で、ユーザ(ここではゲームプレイヤー)が操作するボタンやスライダーなどのことを指す。つまり、「ユーザに見える」Displayableオブジェクトの一部ということになる。
基本的には公式ドキュメント『GUIカスタマイズガイド』を読めば分かるのだが、中には(翻訳の粗さなどいくつかの理由によって)分かりづらい部分もある。私自身もすべて理解できる自信があるわけではないが、一緒に読み合わせ紐解いていくくらいのことはやりたいと思う。
オーバーレイ画像
Ren'Pyではゲーム画面で起動したメニューにオーバーレイ画像を設定することができる。オーバーレイとは何かの表面を覆うことである。
或いはこう想像するとわかりやすいかもしれない。あなたが綺麗な料理を作ったとする。あなたはみんなに自分が作った料理を見てもらいたくてSNSに写真を投稿したい。あなたはみんな褒めてくれるかな、美味しそうって言ってくれるかな、と期待しながら写真を撮ろうとする。そしたらどうだろうか。カメラを起動したスマートフォンの画面をよく見て見ると、キラキラした目の前の料理の下にはド派手なあなたのオタク机。好きなキャラ、好きなゲーム、好きなアニメのブロマイドやらステッカーやら、ぱしゃ〇れやらが挟まったカラフル過ぎる机である。七尾百〇子と望月〇奈の成長Chu→L〇VER!!のポーズに、「ジュエルを買うなら!AS〇BI STORE!」と書かれたチラシが挟まれている。こんなものの上で写真を撮ったら、料理よりも目立ってしまうではないか。可愛い百〇子と〇奈が一番目立ってしまうではないか。これじゃあ写真を撮れない、どうしよう。そうだこうしよう。綺麗な布で覆ってしまおう。この机をまっさらなシルクでできた布で全部覆ってしまえばいい。そうすればオタク机は見えないし、あなたの料理はより美味しそうに写るに違いない。まさに妙案である。
ゲームにおいてはオタク机がゲーム画面で布がオーバーレイ画像、料理が設定画面の文字やらボタンやらである。ゲーム画面にそのままボタンなどを表示させては見づらいからオーバーレイ画像で覆ってゲーム画面をいったん見えない状態にしてしまう。こういうわけである。
これらはディレクトリとしてはgui/overlay/
に入れればよいらしい。
Borders
これは分かりづらい。まず最初に、次のことを心得ておいて欲しい:「フレームの背景用の画像を用意し、それの枠線をBordersによって指定する」。Bordersは枠線のオブジェクトであって、これだけで何らかのウィンドウが生成されるわけではない。
例えばボタンは、ボタンの中の文字数によってその大きさが変わってくる。「設定」という文字のみ書かれたボタンと、「ギャラリー」という文字のみ書かれたボタンでは、一般的に言って後者の方が横長になると思われる。ここで、内部の文字は"子要素"であり、ボタンの四角い画像は"背景"である。この背景が全く無地のときもあるが、枠線が付いているときもある。枠線がある場合、テキトーに引き延ばしたら枠線はそのままで背景のみ引き延ばされてしまう可能性がある。例えば、
このボタンを引き延ばして
こうなる可能性もあるのである。人の顔で言うとこうである。
なので、明示的に枠線を指定してあげないと訳が分からないことになってしまうのである。
そこでBordersの出番だ。Ren'Pyではテキストウィンドウやボタンなどのフレームを作る際、暗示的に次が実行されているらしい。
Frame("gui/<フレーム名>.png", Borders( , , , ), title=<タイトル>)
つまり、何らかのフレームを作る際には背景画像とボーダーがセットで与えられなくてはならないのである。
或いは、こう想像すると理解しやすいかもしれない。あなたの両手を広げてみてほしい。そして両手とも小指、薬指、中指を握って、人差し指と親指を直角にしてみよう。次に、右手と左手の親指同士が平行に、人差し指同士が平行になるように指で長方形を作ってみる。よくカメラのポーズみたいに言われるやつである。トミタケフラッシュということだ(?)。こうして作った長方形だが、この長方形全体がフレーム、指が枠線、指で囲われた内側が子要素としよう。あなたはこのフレームで何らかの対象物を捉える。自分の机の、目の前に飾っているアクスタにこのフレームを向ける。小さめの、ぺコ〇ーヌのアクスタだ。なるほどこのアクスタは綺麗にフレームに収まるだろう。しかし、次に目に映ったのは巨大な等身大アクスタである。その価格は実に20万円。流石にフレームを向けただけでははみ出してしまう。ならばあなたはどうするか。もちろん、フレームを拡張するのである。右手と左手をそれぞれ対角の向きに離していくようにして額縁を広げるのである。十分に広がれば見事に等身大アクスタはすっぽりあなたのフレームに収まるのだ。
あなたが宇宙人でない限り、手の数は多くても2個だろう。指でフレームを作っても2つ分の角までしか表現できていないはずである。ただし、人間にとって想像力は最も強力な武器であり、想像の中ではなんでも叶えることができる。そんなあなたの内的ドラ〇もんに力を借りて、手を4つに増やして欲しい。そして先ほどと同様にして、今度は四隅に親指と人差し指の直角をあててみよう。次に手を、長方形の中心から遠ざけるようにして動かして欲しい。これがRen'Pyのフレームである。
附論
応用1
近頃はあまり時間がなくて勉強がはかどらないのだが、Displayableの応用として1つの機能を作ってみた。内容的には少しTransformの知識も必要だが、大したことはないだろう(多分)。
関数
init python:
# エフェクト位置取得
def character_effect_place(cname:str):
# キャラクターの位置・大きさ取得
bounds = renpy.get_image_bounds(cname)
if bounds is not None:
x, y, w, h = bounds
# 表示位置
# Transformのxposとyposは相対位置(0.0-1.0)で指定しなければならない
ef_x = (x + 100) / config.screen_width
ef_y = (y + 10) / config.screen_height
print("xpos: {}, ypos: {}".format(ef_x, ef_y))
return Transform(xpos=ef_x, ypos=ef_y)
else:
print("Image bounds not found.")
return Transform(xpos=0.5, ypos=0.5)
これは、引数に表示されている立ち絵のキャラクターオブジェクトを渡すと、そのキャラクターのエフェクト表示位置のTransformオブジェクトを返す関数である。例えば、表示されているキャラクターの頭の位置に「💢」を表示したいとする。このとき、一番重要になるのは表示位置だ。これを何とかするのがこの関数。
「💢」の画像や動画などは頑張って自分で作ってくれ。
使い方
例えばスクリプト中に以下のようにして挿入する。
label start:
show bg room
show eileen happy at center zorder 20
with dissolve
e "こんにちは、これはADVモードの表示です。"
$ ikari_pos = character_effect_place('eileen')
play sound "/audio/ikaria.mp3"
show ikarim at ikari_pos zorder 30
# show ikari at ikari_pos zorder 30
e "くぁwせdrftgyふじこlp"
e "とまあ、こんな感じで。お次は一発屋百姓さんのNVLモードです。よろしくお願いします。"
背景としては「💢」マークの動画ikari.mp3をキャラクターeileenの頭に表示したい、ということである。
結果はこんな感じ
この「💢」マークの動画は友人に頼んで作ってもらったやつであり、立ち絵や背景はフリー素材を使っている。
動画編集関連の話に疎いため、クロマキーがうまくできていない。よって、透過したい部分も黒塗りで表示されてしまっている。ごめんね。
素材ソース
注意点
xposやyposは相対位置のみ指定可能で、ピクセル単位での指定はできなかった。したがって、ef_xやef_yの計算ではウィンドウ全体の横幅と縦幅で割っている。
renpy.get_image_bounds(cname)
ではcnameで指定されたキャラクターオブジェクトの、現在表示されている立ち絵のDisplayableの矩形領域の左上端の座標、幅と高さを取得できる。
スクリプト中にあるikaria.mp3
はこの怒りエフェクトを表示したと同時に流す効果音(aはaudioの略)であって、いかりや長介のパチモン的なものではない。
TransformとATL
なんじゃそれ
Ren'PyにおいてTransformとはDisplayableの位置や動きの情報を持つオブジェクトの謂いであり、実際に生成されたTransformのインスタンスは各Displayableのインスタンスに対していくつかの方法を用いて付加(適用)することができる。そして、そのDisplayableに対してあるTransformを付加したオブジェクトもそれはそれでDisplayableらしい。自分でも何を言っているのかよく分からないが多分そうらしい。
一方で、ATLとはAnimation and Transform Languageのことで、Transformオブジェクトにおいて詳しい情報を与えたインスタンスを生成するために用いる高級言語のことである。
あるDisplayableに対するTransformの適用方法
例えばpinyaKorata(謎の緑色の怪物の画像)というDisplayableと、putX50Y50(「絶対位置x軸方向50px、y軸方向50pxの位置に配置する」という情報)というTransformがあったとする。このとき、putX50Y50をpinyaKorataに適用するには以下の3通りの方法があるらしい(まだすべて実験したわけではないので確かではない。改めて実験する)。
At()
# At(displayable, transform)
At(pinyaKorata, putX50Y50)
もっとも一般的に使われるらしい。Ren'Pyからもオヌヌメされてるらしい。
Displayableの引数
# displayable(child=transform)
# キーワード引数childにtransformを渡す。
pinyaKorata(child=putX50Y50)
transform枠はATLのtransformにしか通用しない。
ATLについては追記。
Transformの引数
# transform(displayable)
putX50Y50(pinyaKorata)
引数にdisplayableを渡す必要があるので、引数の必要ないtransformのみしか対応していない。
ATL
ATLの書き方を説明する。
transformステートメント
公式ドキュメントには
こう書いてあるが、全く意味が分からないのでかみ砕いて説明する。
まず::=は定義。数学でも
肝心なのは実際にプログラム上で書くべき構文だ。
# 引数があるとき
transform 名前 (引数):
色んなプロパティを書くブロック
# 引数がないとき
transform 名前:
色んなプロパティを書くブロック
transformの書き方はPythonの関数定義のそれとほとんど同じである。公式ドキュメントの()?はあればこの括弧の中身を書いてね、ということを意味する。つまり、引数がないときは括弧もいらない。