Manuals
Manuals




This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this manual in English

アプリケーションのライフサイクル

Defold のアプリケーションやゲームのライフサイクルは、大きく見るとシンプルです。エンジンは、初期化、更新ループ(アプリやゲームが大半の時間を費やす段階)、終了処理という3つの実行段階を経ます。

このマニュアルは Defold 1.12.0 以降のバージョンを対象としています。バージョン 1.12.0 では、ライフサイクルに関する変更と、新しい late_update() 関数が導入されました。

ライフサイクルの概要

多くの場合、Defold の内部動作については基礎的な理解だけで十分です。ただし、Defold が処理を実行する正確な順序が重要になる特殊なケースに遭遇することもあります。このドキュメントでは、エンジンがアプリケーションを起動から終了までどのように実行するかを説明します。

アプリケーションは、エンジンの実行に必要なすべてのものを初期化して起動します。メインコレクション(main collection)を読み込み、読み込まれたコンポーネント(component)のうち、Lua の init() 関数を持つすべてのコンポーネント(スクリプトコンポーネントと、GUI スクリプトを持つ GUI コンポーネント)で init() を呼び出します。これにより、独自の初期化を実行できます。

続いてアプリケーションは更新ループに入り、生存期間の大半をここで過ごします。各フレームで、ゲームオブジェクト(game object)と、それに含まれるコンポーネントが更新されます。スクリプトと GUI スクリプトに定義された update() 関数が呼び出されます。更新ループの間に、メッセージが受信先へ配送され、サウンドが再生され、すべてのグラフィックスが描画されます。

いずれ、アプリケーションのライフサイクルは終わりを迎えます。アプリケーションが終了する前に、エンジンは更新ループを抜け、終了処理の段階に入ります。読み込まれたすべてのゲームオブジェクトを削除する準備をします。オブジェクトのすべてのコンポーネントの final() 関数が呼び出され、独自の後片付けを実行できます。その後、オブジェクトが削除され、メインコレクションがアンロードされます。

「メッセージの配送」の処理に含まれる手順は、分かりやすくするためにこのマニュアルの末尾にある別の図で示しています。各図では、小さな「矢印付きの封筒」アイコン 📩 で示しています。

初期化

ここがゲームの開始地点であり、ゲーム実行の最初のステップです。次の3つのフェーズに分けられます。

初期化

事前初期化

事前初期化に当たる Preinitialization フェーズでは、メイン(ブートストラップ)コレクション、つまり起動時に読み込まれるコレクションを読み込む前に、エンジンが多くの処理を実行します。メモリプロファイラー、ソケット、グラフィックス、HID(入力デバイス)、サウンド、物理シミュレーションなどをセットアップします。アプリケーションの設定(game.project)も読み込み、セットアップします。

事前初期化

ユーザーが制御できる最初の入口は、エンジンの初期化の最後に行われる、現在のレンダースクリプト(render script)の init() 関数の呼び出しです。

その後、メインコレクションが読み込まれ、初期化されます。

コレクションの初期化

コレクションの初期化に当たる Collection Init フェーズでは、コレクション内のすべてのゲームオブジェクトが、自身のトランスフォーム(transform)、つまり平行移動(位置の変更)、回転、拡大縮小を子に適用します。その後、すべてのコンポーネントに定義されている init() 関数が呼び出されます。

コレクションの初期化

ゲームオブジェクトのコンポーネントの init() 関数が呼び出される順序は未定義です。同じコレクションに属するオブジェクトをエンジンが特定の順序で初期化すると想定しないでください。

初期化中の更新後の処理

次にエンジンは、更新後の処理に当たる Post Update を一通り実行します。これは、後で各 Update Loop のステップ後に実行されるものと同じ処理です。初期化の最後に実行するのは、init() のコードが、新しいメッセージの送信、ファクトリー(factory)への新しいオブジェクトの生成指示、オブジェクトへの削除予定の印付けなどの操作を実行できるためです。

更新後の処理

この処理では、メッセージの配送、ファクトリーによるゲームオブジェクトの実際の生成、オブジェクトの削除が行われます。Post Update の処理には「メッセージの配送」という一連の処理が含まれ、キュー内のメッセージの配送だけでなく、コレクションプロキシ(collection proxy)へ送信されたメッセージの処理も行います。それに伴うプロキシの更新(有効化、無効化、初期化、終了処理、読み込み、アンロード予定の印付け)は、これらのステップで実行されます。

init() 中にコレクションプロキシを読み込み、その内部のすべてのオブジェクトが初期化されるようにしてから、プロキシを通じてコレクションをアンロードすることもできます。これらはすべて、最初のコンポーネントの update() が呼び出される前、つまりエンジンが初期化の段階を終えて更新ループに入る前に実行できます。

function init(self)
    print("init()")
    msg.post("#collectionproxy", "load")
end

function update(self, dt)
    -- The proxy collection is unloaded before this code is reached.
    print("update()")
end

function on_message(self, message_id, message, sender)
    if message_id == hash("proxy_loaded") then
        print("proxy_loaded. Init, enable and then unload.")
        msg.post("#collectionproxy", "init")
        msg.post("#collectionproxy", "enable")
        msg.post("#collectionproxy", "unload")
        -- The proxy collection objects’ init() and final() functions
        -- are called before we reach this object’s update()
    end
end

更新ループ

Update Loop は、1フレームにつき1回、決まった順序で実行されます。この一連の処理は、次の5つの主要なフェーズに分けられます。

更新ループ

  1. 入力(処理と対応)
  2. 更新(固定時間間隔での更新、通常の更新、後段の更新、エンジンのコンポーネントの更新を含みます)
  3. レンダー更新
  4. 更新後の処理(コレクションプロキシのアンロード、ゲームオブジェクトの生成と削除)
  5. フレームの描画(最終的なグラフィックスを描画します)

入力フェーズ

利用可能なデバイスから入力を読み取り、入力バインディング(input binding)に照らして対応付けた後、配送します。入力フォーカス(input focus)を取得した各ゲームオブジェクトには、そのすべてのコンポーネントの on_input() 関数に入力が送られます。スクリプトコンポーネントと、GUI スクリプトを持つ GUI コンポーネントを備えたゲームオブジェクトでは、両方のコンポーネントの on_input() 関数に入力が送られます。ただし、それらの関数が定義され、入力フォーカスを取得していることが条件です。

入力フェーズ

入力フォーカスを取得していて、コレクションプロキシコンポーネントを含む各ゲームオブジェクトは、プロキシのコレクション内のコンポーネントへ入力を配送します。この処理は、有効なコレクションプロキシ内にある有効なコレクションプロキシへと、再帰的に続きます。

更新フェーズ

更新を行う Update フェーズは、Update Loop の一部です。ルートコレクションに対して1回開始され、その後、有効な各コレクションプロキシに対して再帰的に実行されます。

コレクション内では、Defold はコンポーネントの種類ごとにコールバックを処理します。該当する段階を実装するコンポーネントの種類について、そのすべてのインスタンスを順に処理し、各インスタンスの Lua コールバックを呼び出します。その後、メッセージを配送してから、次のコンポーネントの種類へ進みます。

スクリプト コンポーネントの Lua コールバックが実行される段階の大まかな順序は、次のとおりです。

  1. fixed_update() - 1フレームあたり0..N回呼び出されます(固定タイムステップを使用する場合)
  2. update() - 1フレームあたり1回呼び出されます
  3. late_update() - 1フレームあたり1回呼び出されます

更新フェーズ

メインコレクション内の各ゲームオブジェクトのコンポーネントを順に処理します。これらのコンポーネントのいずれかに、fixed_update()/update()/late_update() 関数を持つスクリプトがある場合、その関数を呼び出します。コンポーネントがコレクションプロキシの場合は、プロキシのコレクション内の各コンポーネントを、Update フェーズのすべてのステップで再帰的に更新します。

ゲームオブジェクトのコンポーネントの update() 関数が呼び出される順序は未定義です。同じコレクションに属するオブジェクトをエンジンが特定の順序で更新すると想定しないでください。fixed_update()late_update()(1.12.0 以降)も同様です。

物理シミュレーション

衝突判定や物理特性を持つコリジョンオブジェクト(collision object)コンポーネントでは、物理メッセージ(衝突、トリガー、レイキャストの応答など)が、そのコンポーネントを含むゲームオブジェクト全体に配送されます。配送先は、on_message() 関数を持つスクリプトを含むすべてのコンポーネントです。

物理シミュレーションに固定タイムステップ、つまり固定されたシミュレーションの時間刻みを使用する場合は、すべてのスクリプトコンポーネントで fixed_update() 関数が呼び出されることもあります。この関数は、物理シミュレーションを使ったゲームで、物理オブジェクトを一定間隔で操作し、安定した物理シミュレーションを実現したい場合に役立ちます。

トランスフォーム

Update Loop 中には複数回、コンポーネントの種類ごとの更新の前に、必要に応じてトランスフォームが更新されます。ゲームオブジェクトの移動、回転、拡大縮小が、各ゲームオブジェクトのコンポーネントと、すべての子ゲームオブジェクトのコンポーネントに適用されます。

Update Loop の最後には、必要に応じて、最後のトランスフォーム更新がもう1回行われます。

エンジンの更新フェーズ(固定時間間隔での更新なし)

以下の表は、エンジンレベル の更新処理を示しています。内部のコンポーネントの正確な優先順位はエンジンの実装の詳細なので、ここでは意図的に省略しています。ただし、スクリプトに関係する、順序に関する次の保証は反映しています。

  • fixed_update()update() より前に実行されます
  • late_update()update() より後に実行されます
  • 送信されたメッセージは、コンポーネントの種類ごとの更新の間と、スクリプトのコールバック段階の間に配送されます

Use Fixed Timestepfalse、または Fixed Update Frequency が 0、あるいはその両方の場合、フェーズの開始時に dt を準備し、その後は以下の表に示す流れで処理します。

:::sidenote コンポーネントの種類ごとの更新の後には、すべてのメッセージが配送されます。表を見やすくするため、以下の表ではこの処理を省略しています。 :::

ステップ エンジンのフェーズ Lua コールバック 説明
1 更新 update() 更新を実装するコンポーネントの種類ごとに、内部の優先順位に従って、1フレームにつき1回呼び出されます。また、go.animate() で開始したゲームオブジェクトのプロパティアニメーションも、独立したコンポーネントの種類としてここで更新されます。物理コンポーネントもここで更新されます。有効な各コレクションプロキシに対して、Update フェーズ全体がステップ1から再帰的に呼び出されます。
2 後段の更新 late_update() 後段の更新を実装するコンポーネントの種類ごとに、内部の優先順位に従って、1フレームにつき1回呼び出されます。
3 トランスフォーム   最後に、必要に応じて各コンポーネントのトランスフォーム更新がもう1回行われます。

固定タイムステップを使用するエンジンの更新フェーズ

Use Fixed Timesteptrue で、Fixed Update Frequency がゼロ以外の場合、フェーズの開始時に dt(経過時間)、fixed_dtnum_fixed_steps0..N)を準備します。最後の値は固定時間間隔での更新を呼び出す回数で、更新が一定の頻度で行われるよう、前回の更新からの経過時間に基づいて決まります。

:::sidenote コンポーネントの種類ごとの更新の後には、すべてのメッセージが配送されます。表を見やすくするため、以下の表ではこの処理を省略しています。 :::

その後、次のように繰り返し処理します。

ステップ エンジンのフェーズ Lua コールバック 説明
1 固定時間間隔での更新 fixed_update() 固定時間間隔での更新を実装するコンポーネントの種類ごとに、内部の優先順位に従って、時間に応じて1フレームあたり 0..N 回呼び出されます。これには、物理 コンポーネントの固定時間間隔での更新ステップも含まれます。
2 更新 update() 更新を実装するコンポーネントの種類ごとに、内部の優先順位に従って、1フレームにつき1回呼び出されます。また、go.animate() で開始したゲームオブジェクトのプロパティアニメーションも、独立したコンポーネントの種類としてここで更新されます。有効な各コレクションプロキシに対して、Update フェーズがステップ1から再帰的に呼び出されます。
3 後段の更新 late_update() 後段の更新を実装するコンポーネントの種類ごとに、内部の優先順位に従って、1フレームにつき1回呼び出されます。
4 トランスフォーム   最後に、必要に応じて各コンポーネントのトランスフォーム更新がもう1回行われます。

更新フェーズ中の Defold の内部動作をさらに詳しく知りたい場合は、gameobject.cpp のコード自体を読むと参考になります。

レンダー更新フェーズ

レンダー更新の処理では、まず @render ソケットへ送信されたすべてのメッセージ(カメラコンポーネントの set_view_projection メッセージ、set_clear_color メッセージなど)を配送します。その後、レンダースクリプトの update() が呼び出されます。

レンダー更新フェーズ

更新後の処理フェーズ

更新が終わると、更新後の一連の処理を実行します。アンロード予定の印が付いているコレクションプロキシをメモリからアンロードします(これは「メッセージの配送」の一連の処理中に行われます)。削除予定の印が付いている各ゲームオブジェクトでは、すべてのコンポーネントに定義されている final() 関数が呼び出されます。final() 関数内のコードは新しいメッセージをキューに送信することが多いため、その後に「メッセージの配送」の処理が実行されます。

更新後の処理フェーズ

次に、ゲームオブジェクトを生成するよう指示されたファクトリーコンポーネントが、その生成を行います。最後に、削除予定の印が付いているゲームオブジェクトが実際に削除されます。

描画フェーズ

更新ループの最後のステップでは、@system メッセージ(exitreboot メッセージ、プロファイラーの切り替え、動画キャプチャーの開始と停止など)を配送します。

描画フェーズ

その後、グラフィックスを描画し、ビジュアルプロファイラーの描画も行います(デバッグのドキュメントを参照してください)。グラフィックスの描画後に、動画キャプチャーを行います。

フレームレートとコレクションのタイムステップ

1秒あたりのフレーム更新回数(1秒あたりの更新ループの実行回数と同じ)は、プロジェクト設定で設定するか、@system ソケットへ set_update_frequency メッセージを送信してプログラムから設定できます。また、プロキシへ set_time_step メッセージを送信すると、コレクションプロキシごとに タイムステップ を個別に設定できます。コレクションのタイムステップを変更しても、フレームレートには影響しません。物理更新のタイムステップと、update(). に渡される dt 変数には影響します。また、タイムステップを変更しても、各フレームで update() が呼び出される回数は変わりません。常にちょうど1回です。

(詳しくは、コレクションプロキシのマニュアルset_time_step を参照してください)

エンジンのスロットリング

Defold 1.12.0 では、入力の検出を続けながら、エンジンの更新と描画を完全にスキップできる、エンジンのスロットリング(engine throttling)API が導入されました。何らかの入力があるとエンジンは再び動作を再開し、クールダウン期間の後にスロットリングへ戻ることができます。

詳細と使用例については、sys.set_engine_throttle() API を参照してください。

終了処理

アプリケーションが終了するときは、まず最後の更新ループの一連の処理を完了します。これにより、すべてのコレクションプロキシがアンロードされ、各プロキシのコレクション内にあるすべてのゲームオブジェクトの終了処理と削除が行われます。

それが完了すると、エンジンはメインコレクションとそのオブジェクトを処理する、一連の終了処理に入ります。

終了処理

最初に、コンポーネントの final() 関数が呼び出されます。続いてメッセージが配送されます。最後に、すべてのゲームオブジェクトが削除され、メインコレクションがアンロードされます。

その後、エンジンは内部でサブシステムのシャットダウンを進めます。プロジェクト設定を削除し、メモリプロファイラーを停止するなどの処理を行います。

これで、アプリケーションは完全に終了します。

メッセージの配送

メッセージの配送は、コンポーネントの種類ごとの更新の後、たとえばスプライトの更新、スクリプトの更新の後や、メッセージを送信する可能性があるその他の操作の後に実行される特別な処理です。実行中は、送信されてキューに蓄積されたすべてのメッセージが配送されます。図では、小さな「矢印付きの封筒」アイコン 📩 で示しています。

メッセージの配送

各コンポーネントの on_message() を呼び出して、すべてのユーザー定義メッセージを配送した後、Defold 固有のメッセージを、各コレクションプロキシについて次の順序で処理します(図にも同じ順序を示しています)。

  1. load メッセージ - 読み込み予定の印が付いているコレクションプロキシを読み込み、proxy_loaded メッセージを返します。
  2. unload メッセージ - アンロード予定の印が付いているコレクションプロキシをアンロードし、proxy_unloaded メッセージを返します。
  3. init メッセージ - 初期化するすべてのコレクションプロキシに対して、Collection Init フェーズを開始します。
  4. final メッセージ - 終了処理予定の印が付いているプロキシのすべてのコンポーネントで、final() を呼び出します。
  5. enable メッセージ - コレクションプロキシを有効にして、次のフレームでそのプロキシの Update Loop が実行されるようにします。これにより、コレクションの各コンポーネントの init() が暗黙的に呼び出されます。
  6. disable メッセージ - コレクションプロキシを無効にして、次のフレームでそのプロキシの Update Loop実行されないようにします。そのプロキシの Update Loop の実行を完全に停止します。

受信する各コンポーネントの on_message() のコードは追加のメッセージを送信できるため、メッセージディスパッチャーは、メッセージキューが空になるまで、送信されたメッセージを再帰的に配送し続けます。ただし、メッセージディスパッチャーがメッセージキューを処理する回数には上限があります。詳しくは、メッセージチェーンを参照してください。