画像表示用ノードの選択
前回の記事までで、Inkシナリオを読み込んでシナリオを制御する部分までできました。次は背景画像(静止画像)を追加したいのですが、その機能はシナリオ制御とは切り離した別のノードにすべきだと考えています。そうすることで”その機能”を実現できるノードとして、再利用が非常に楽になったりするはず。そこは他の言語を色々やってきた私の経験からも多少はわかっていたりします。
まず現状のおさらいをしますが、現在のシーン構成は以下のようになっています。
+VboxContainer(ルートノード)
以上。これがシーンのルートノードで、これだけです。このノードの中で、Inkシナリオテキストの読み込みや、選択肢をボタンにして表示、ユーザ入力をもとに次のシナリオを表示したりしています。それがこのノードの機能ですね。
このノードに対し、森にいるような背景画像をいくつか表示できるようにしたいので、表示するためのノードが必要になるということが肌感でわかると思います。画像を表示するためのノードを追加するのですが、さて何のノードを選ぼうかという選択の必要が出てきました。
調べた結果、候補としてあがったのが、TextureRectノードと、Spriteノードです。2つの違いを表にしてまとめてみましょう。
特徴 | TextureRect | Sprite |
---|---|---|
用途 | UI要素や背景画像 | 2DゲームのSpriteやキャラ |
サイズ調整 | 自動でサイズ調整可能 | 手動でスケールを設定 |
原点の操作 | 原点は固定 | 原点を任意の位置に設定可能 |
アニメーション | 非対応 | スプライトシートのアニメ対応 |
レイアウト対応 | ストレッチやレイアウトに柔軟 | 固定サイズで使用 |
座標系 | UIレイアウト(Controlの子) | 2Dゲーム座標(Node2Dの子) |
決め手になったのは用途の項目で、もろに背景画像だからですね。また今回は森の画像を表示できればよく、何かを動かしたいという要求もありません。結論、TextureRect で実装することにします。あと重要なのは、レイアウト対応がある点が◎です。今はなんだそれと思う人がいるかもしれませんが、いろんなデバイス、異なる画面の大きさ、解像度で表示された場合でも、左上の原点はどれも同じなので、そこに画像の左上を合わせてから、勝手に拡大/縮小をしてくれる超便利機能です。
というわけで、いずれSprite はやることになるでしょうが、いまは TextureRect を選択します。
新ノード追加&既存ノードとの配置調整
さきほどの表の、「座標系」に注目です。UIレイアウトとありますね。これは、TextureRect という部品は、GUI(グラフィカルユーザーインターフェース)の一部ということです。こういった部品は、それぞれどういうレイアウトで配置したいか、後で柔軟に変更したりできるよう、何かの土台の上に配置するのが一般的です。先ほどの表をよく見ると、「Controlの子」とあります。ですので、シーンの構成としては下記が良さそうです。
+Control(ルートノード)
+TextureRect
さてあとは VBoxContainer と足せばいいのですが、どう足すのがベストかですね。あらため VBoxContainer は何者か調べましょう。
VBoxContainer はどういうものか?
GodotのVBoxContainerは、複数の子要素を垂直方向に自動的に配置するためのコンテナノードです。このコンテナに追加された子ノード(一般的にはUI要素)は、縦方向に整列し、各要素の大きさに応じてスペースが自動で割り当てられます。 例えば、VBoxContainerを使うことで、テキストラベルやボタンを縦に並べて配置するUIレイアウトを簡単に作成することができます。個々の子ノード間の間隔や、要素の均等配置、揃え方などもカスタマイズが可能です。
どうやらこれも何かを入れる箱の1つのようです。よく考えてみれば、選択肢のボタンもUIだからこの Container の上に乗っかっていたんでしょうね。さて、ズバリ親ノードには何が相応しいか聞いてみましょう。
VBoxContainer の親になるノードはどういうものですか?
VBoxContainerの親ノードとしては、他のGUIノード(Controlノードのサブクラス)が一般的に使われます。VBoxContainer自体がControlクラスの一部であるため、親ノードもControlクラスのサブクラスである必要があります。
なるほどつまり、以下のシーン構成が良さそうです。
+Control(ルートノード)
+TextureRect(ノード1:背景画像)
+VBoxContainer(ノード2:テキストと選択肢表示)
シーン構成が決まったので、既存プロジェクトの構成を変更していくのですが、現在のプロジェクトフォルダ「firstchat」には手を入れないでおいておきましょう。
代わりに「firstchat」フォルダと同じ場所に、新しいフォルダ「firstbackground」を新規作成して、「firstchat」フォルダの中身をまるごと「firstbackground」の中にコピーしてしまいましょう。
いったんGodotエンジンを終了させて再起動し、新しい「firstbackground」フォルダを開くと有効なプロジェクトが見つかるので、そのままインポートします。これで準備OKです。
まず、現在のシーン構成は以下のようになっていると思います。
下記の画面内の「+」を押します。
一覧から「Control」ノードを探し、「作成」ボタンを押して追加します。
Controlノードが追加されました。
あれ?ノードの順序が違うじゃない、と思ったそこのあなた。あわてないあわてない。
あとでまとめて直します。
さらにもう1つ、「TextureRect」ノードを追加します。同じように「+」を押します。
その後、一覧から「TextureRect」を探して「作成」ボタンを押します。
検索ボックスを使うと超らくちんにみつけられますね。
いまこんな感じになりました。
ルートノードの再設定
じゃあまずはルートノードからいきましょう。「Control」をルートにしたいのでした。
「Control」ノードのところで右クリックして、「シーンのルートにする」を選択します。
すると、あっという間にルートノードになりましたね。
簡単ですね。こんなに簡単に王座(ルート)を奪われていいのでしょうか。
VBoxContainerの胸中は内心穏やかではないはずです。
プロジェクト設定でシーン情報の更新
さて、これでシーンは計画どおり下記の構成になったと思われます。
+Control(ルートノード)
+TextureRect(ノード1:背景画像)
+VBoxContainer(ノード2:テキストと選択肢表示)
このままだとプロジェクト名が「FirstChat」のままなので、[プロジェクト]→[プロジェクト設定]→アプリケーションの「構成」を選択し、「名前」のところを「FirstBackground」に変えておきましょう。
念のため[プロジェクト]→[現在のプロジェクトをリロード]で、設定を再読み込みします。
さていま下記のような状態になりました。が、気になる点が1つあります。TextureRectノードは追加したものの、それに対して何もしていません。いま現在中央には、「v_box_container.tscn」スクリプトが表示されています。これは、VBoxContainerノードにアタッチされたスクリプトでした。
ルートノードにした TextureRectにも同じようにスクリプトをアタッチして、画像を描画する処理を書いたりしないといけないはずです。
早速スクリプトをアタッチしてみましょう。
下記の画面が表示されます。
さて、前回、VBoxContainerでは言語を「C#」にしていました。なぜかというと、Inkの処理を行う場合、GDScriptでの実装はC#の50倍遅くなるよと公式のドキュメントで脅された(?)からですね。でもここで行う処理は、画像の表示だけです。これならGDScriptで記述できるはずだと思いこのようにしました。
気になるのは、VBoxContainerノードの中で選択肢ボタンをユーザに押してもらいながら選択肢を進め、いざ画像を変更するときに、C#のVBoxContainerスクリプトから、TextureRectのGDスクリプトに何らかの情報を渡す必要がありますが、それがうまくいくのかどうか、ですね。
まあそれはおいておいて、「作成」ボタンを押してスクリプトを作ってみます。
マーカーをつけた箇所が変わりました。中央で(.gd)スクリプトと(.cs)スクリプトを選択すれば、ソース表示内容が変わるようになりました。
ここまでできたら、メニューから[シーン]→[シーンを保存]しましょう。
ここでメニューの[プロジェクト]→[プロジェクト設定]を開いてみます。メインシーンの名前が、「v_box_container.tscn」のままですね。この名前にしていたのは、シーンにVBoxContainerというノードが1つしかないからそのままその名前を付けていました。ノードを追加してルートノードも変更した今、このシーン名って適切ではないですね。
上記の画面では、拡張子が「.tscn」のファイルを選択することができますが、今はこのファイル名を別な名前に変更したいですね。いったんこれは右上の「X」ボタンで閉じて前の画面に戻ります。
前の画面の、左下にシーンファイル名が表示されています。そこを右クリックで、名前の変更ができまので、このシーンに「choices_in_woods」とつけましょう。
名前の変更が終わったら、念のためメニューから[シーン]→[シーンの保存]で保存しておきます。
ノードの基本処理の実行タイミング確認
新規に追加したGDスクリプトの話に戻ります。現時点では、_ready() も _process() も空っぽなので何も処理が行われません。ここでいま私は2点、疑問をもっています。
①メインシーンのスクリプト(.gd)には何も処理を入れていないが、このままプロジェクトを実行しても、その子ノードのスクリプト(.tscn)は実行されるのかどうか。
②もし実行されるなら、いま処理は次の4つがあることになるが、どういう順序で実行されるか。
そのまま実行すれば①は確認できそうですが、②はprint(文字列を表示するだけ)処理を入れないと確認できないので下記のprintを追加します(青色マーカー部分)。
■texture_rect_background.gd スクリプトにprint追加
func ready() -> void:
print("TextureRect Script _ready() Starts.");
pass
func process(delta: float) -> void:
print("TextureRect Script _process() Starts.");
pass
■v_boxcontainer_firstchat.tcsnスクリプトにprint追加
public override void _Ready()
{
GD.Print("VBoxContainer Script _Ready() Starts.");
ContinueStory();
}
public override void _Process(double delta)
{
GD.Print("VBoxContainer Script _Process() Starts.");
}
上がpythonライクなGDスクリプト言語、下がC#言語なので、同じprintでも書き方が少し異なるので注意が必要です。
準備が整ったので、実行してみました。
あああああああああああああああああああああああああああああ。
ログ画面での出力が止まりません。すぐ手動でプログラムを停止しました。それもそのはずというか、_process/_Process処理は、画面1秒あたり決まった回数だけ、画面を更新するたびに実行されるのですよね。たとえばフレームレートが60fpsの場合だと、1秒間にこの_process/_Process関数は60回呼び出されるのですね。
いまはフレームレートの話は置いておいて、一番最初のログを見てみましょう。
まず下記①の確認からです。
①メインシーンのスクリプト(.gd)には何も処理を入れていないが、このままプロジェクトを実行しても、その子ノードのスクリプト(.tscn)は実行されるのかどうか。
これは文句なしにできてますね。もし実行されないと、このログには “TextureRect Script …” の出力のみで、”VBoxContainer Script …” の出力は無かったはずですが、実際はどちらも表示されている、つまりどちらのスクリプトも実行されています。また、スクリプトの実行順序も、ルートの並び順ですね。今はControlノードのスクリプトを作ってないので出ないですが、Controlにスクリプトをアタッチしたら、おそらくそのスクリプトが真っ先に呼ばれるのでしょう。
次に②の実行順序の確認です。
②もし実行されるなら、いま処理は次の4つがあることになるが、どういう順序で実行されるか。
結果は上のログから下記であることがわかりました。
ノード1のREADY
↓
ノード2のREADY
↓
ノード1のPROCESS
↓
ノード2のPROCESS
↓
ノード1のPROCESS
↓
ノード2のPROCESS
↓
(以降ずっとノード1と2のProcessのループ)
つまりPROCESSよりREADYが先に実行されます。READYの実行順序はノードの並び順ですね。その後、PROCESSがノードの並び順で実行されるようです。これはたぶんめちゃくちゃ重要なので、ずっと覚えておくべき案件だと思います。
画像の読み込みというのは、基本的に重い処理です。ですが、一度メモリに読み込んでしまえば、あとは使いまわせるという性質があります。これを踏まえると、1度しかやる必要のない重たい処理は、すべからくREADYの中で書くべきだとわかります。
一方、画像を滑らかに動かしたい場合は、表示するSprite画像の表示座標を少しずつPROCESS処理の中でずらしたりしていく(思い出してください。60fpsだと1秒間に60回PROCESSは実行されますね)ことで、60fpsの滑らかな動きを実現していくわけです。
GDスクリプトとC#スクリプト内のコメントアウト
このブログを見ている人には説明不要な気が100%していますが(もしプログラミング初心者でこのブログで初めてこの言葉を知ったみたいな人がいたら嬉しいですね)、コメントアウトというのは、一度書いた処理(行)を、一時的に無効(無効にするとエディタの機能でグレーアウト表示されることが多いです)にするための記述方法です。
やり方は簡単で、GDスクリプトの場合は、文の冒頭に「#」を追加します。
# print("TextureRect Script _process() Starts.");
C#スクリプトの場合は、文の冒頭に「//」を追加します。
// GD.Print("VBoxContainer Script _Process() Starts.");
まとめ
さて、ノードを追加してシーンの構成を変える方法などはこれでだいたい(?)つかめたような気がします。次回はいよいよ画像を追加していきましょう。
それでは今日はこのへんで。
コメント