シーンツリー
導入
ここから少し話がややこしくなりますが、ついて来てくださいね。よく考えればそんなに難しく無いはずです。
以前のチュートリアルでは全てはノードを中心にできていて、シーンがそれらからできているという話で、それらは一度シーンツリーに入ったらアクティブになると言う話でした。
今回はもう少し深いところに言及したいと思います。実際にはシーンシステムはコアな構成要素では無く、シーンツリー無しで直接スクリプトやC++でサーバーとやり取りすることも出来ます。しかしそんな風にゲームを作るのは徒労ですし、そんな時間があるなら素材のクオリティを高めるためにつかったほうが有意義です。
メインループ
Godotの内部動作を以下で説明したいと思います。一番最初に実行するインスタンスはOSクラスというものです。その後サーバー、スクリプト言語、シーンシステムなどの全てのドライバーが読み込まれます。初期化が終了すると、OSメインループを実行させます。
ここまでが、Godotの内部動作の全貌です。もっと内部動作を知りたければソースコードのmain/main.cppを見てください。
ユーザーのプログラムやゲームはメインループから始まります。このクラスには初期化、アイドル(フレーム同期コールバック)、固定化(物理エンジン同期コールバック)、入力のためのいくつかのメソッドがあります。またこれはとても低レベルなAPIで、Godotでゲームを作る際にオリジナルのメインループを作るのはあまり意味がありません。
シーンツリー
Godotの動きかたについては、ローレベルなミドルウエアの上にハイレベルなゲームエンジンが乗っているものであると説明出来ます。
シーンシステムはゲームエンジンで、OSやサーバーはローレベルAPIです。とにかく、シーンシステムはOSのメインループやシーンツリーを提供します。
これは自動的にインスタンス化され、シーンが実行うされる時にセットされます。ゲーム開発者は何も触る必要はありません。
シーンツリークラスについて知っておくべきことは以下のような点です。
シーンが最初に実行される時にシーンツリーに子ノードとして追加されるルートビューポートが格納されます。ルートビューポートについては後に説明します。
グループに関する情報が含まれていて、グループ内の全てのノードを呼び出したり、そのリストを得ることが出来ます。
ポーズモードやプロセスの終了時に関する設定などグローバルステイトに関する関数もあります。
ノードがシーンツリーの一部である時、シーンツリーの単量子は単にNode.get_tree()を呼ぶことで得られます。
ルートビューポート
ルートビューポートは常にシーンの最上位にあります。 ノードから二種類の方法をつかって得ることが出来ます。
get_tree().get_root() # scenemainloopからのアクセス
get_node("/root") # 絶対パスでのアクセス
ノードはメインビューポートを格納していて、ビューポートの子供は全てその内部に書き込まれます。全てのノードのトップが常にこのノードである意味はここで、これが無ければ何も見れなくなります。
画面を分割したいなどの理由で、あえて他のビューポートが作られない限りは、これはユーザーによって作られることの無いものです。自動的にSceneTree内部に作られます。
シーンツリー
ノードがルートビューポートに繋がれた時、それが直接的でも間接的でも、ノードはシーンツリーの一部になります。これは、前回のチュートリアルで説明したように、_enter_tree()や_ready()コールバックによって得ることが出来ます。_exit_tree()も前回と動揺です。
ノードはシーンツリーに入ると、活発になります。また、ノードはインプットの取得、2D、3Dでの表示、通知、サウンドの再生、グループなどの処理が必要な全てのものにアクセス出来ます。そしてシーンツリーが削除された時にはノードは無くなります。
ツリーの順序
Godotでは2D描画、演算、通知の取得のような大部分のノードに関するオペレーションがツリーの順序に関連してなされます。より前の順序の親兄弟に向けて現在のノードからメッセージが送られるという意味です。
シーンツリーに入る時の「有効化」
シーンはディスクからロードされるか、スクリプトによって作成されます。
そのシーンのルートノード(ルートノードは一つだけであることは覚えていますか)はシーンツリーに由来する「ルートビューポイント」の子どもとして追加されます。孫であることもひ孫であることもあります。
シーンに新しくノードが追加される毎に、上から下の順で"enter_tree"という通知を受け取って行きます。(GDScriptの_enter_tree()コールバックを思い出して下さい)
特別な通知"ready"(GDScriptの_ready()コールバックを思い出して下さい)はノードとその全ての子がアクティブシーンに入る時に便利に使えます。
シーンやその一部が削除された時、下から上へ通知"exit scene"を受け取って行きます。(GDScriptの_exit_tree()コールバックを思い出して下さい)
シーンの変更
シーンがロードされた後、シーンを切り替える必要が出てくることがあります。そのためには、SceneTree.change_scene()関数を使うのがシンプルな方法です。
func _my_level_was_completed():
get_tree().change_scene("res://levels/level2.scn")
これはシーンを切り替える手っ取り早くて使える方法ですが、シーンがロードされ実行されるまでの時間分遅れが生じる欠点があります。あなたのゲームのポイントポイントで、プログレスバーや、アニメーションインジケーターを用いるなど、適切なロード時間用のスクリーンを用意するか、あるいはバックグラウンドでプレロードする必要があるかもしれません。オートロード(次の章のトピック)やバックグラウンドロードなどを処理するAPIも存在しています。