計算量の多いレベル生成アルゴリズムがあります。そのため、呼び出すと常にゲーム画面がフリーズします。ゲームがまだロード画面をレンダリングし続けている間に、ゲームがフリーズしていないことを示すために、関数を2番目のスレッドに配置するにはどうすればよいですか?
計算量の多いレベル生成アルゴリズムがあります。そのため、呼び出すと常にゲーム画面がフリーズします。ゲームがまだロード画面をレンダリングし続けている間に、ゲームがフリーズしていないことを示すために、関数を2番目のスレッドに配置するにはどうすればよいですか?
回答:
更新: 2018年、Unityは作業をオフロードし、複数のCPUコアを使用する方法としてC#ジョブシステムを展開しています。
以下の回答は、このシステムより前のものです。それでも動作しますが、ニーズに応じて、最新のUnityでより良いオプションが利用できる場合があります。特に、ジョブシステムは、以下で説明する、手動で作成されたスレッドが安全にアクセスできるものに関する制限の一部を解決するようです。たとえば、レイキャストを実行し、メッシュを並行して構築するプレビューレポートを試す開発者。
このジョブシステムを使用した経験のあるユーザーを招待して、エンジンの現在の状態を反映する独自の回答を追加します。
私は過去にUnityのヘビーウェイトタスク(通常は画像およびジオメトリ処理)にスレッドを使用しましたが、他のC#アプリケーションでスレッドを使用することと2つの注意点があります。
Unityは多少古い.NETのサブセットを使用しているため、すぐに使用できない新しいスレッド機能とライブラリがいくつかありますが、基本はすべて揃っています。
上記のコメントでAlmoが指摘しているように、多くのUnityタイプはスレッドセーフではないため、メインスレッドから構築、使用、または比較しようとすると例外がスローされます。留意すべきこと:
一般的なケースの1つは、GameObjectまたはMonobehaviour参照がメンバーにアクセスする前にnullであるかどうかを確認することです。myUnityObject == null
UnityEngine.Objectから派生したものに対してオーバーロードされた演算子を呼び出しますが、System.Object.ReferenceEquals()
これをある程度回避します-Destroy ()ed GameObjectはオーバーロードを使用してnullと等しいが、まだReferenceEqualがnullではないことに注意してください。
Unityタイプからのパラメーターの読み取りは通常、別のスレッドで安全です(上記のようにnullをチェックするように注意している限り、すぐに例外をスローしません)が、メインスレッドが状態を変更しているかもしれないというPhilippの警告に注意あなたがそれを読んでいる間。一貫性のない状態を読み取らないようにするために、誰が何をいつ変更できるかについて規律を守る必要があります。意のままに再現しないでください。
ランダムおよび時間の静的メンバーは使用できません。ランダム性が必要な場合はスレッドごとにSystem.Randomのインスタンスを作成し、タイミング情報が必要な場合はSystem.Diagnostics.Stopwatchを作成します。
Mathf関数、Vector、Matrix、Quaternion、Color構造体はすべてスレッド間で正常に機能するため、ほとんどの計算を個別に実行できます。
GameObjectsの作成、Monobehavioursのアタッチ、またはテクスチャ、メッシュ、マテリアルなどの作成/更新はすべてメインスレッドで行う必要があります。過去にこれらを使用する必要があったときに、プロデューサー-コンシューマキューを設定しました。ここで、ワーカースレッドは生データ(メッシュまたはテクスチャに適用するための大きなベクトル/色の配列など)を準備します。メインスレッドの更新またはコルーチンがデータをポーリングして適用します。
これらのメモが邪魔にならないように、スレッド化された作業によく使用するパターンを以下に示します。私はそれがベストプラクティスのスタイルであることを保証しませんが、仕事は完了します。(改善のためのコメントや編集は大歓迎です-スレッド化は非常に深いトピックであり、基本的なことしか知りません
using UnityEngine;
using System.Threading;
public class MyThreadedBehaviour : MonoBehaviour
{
bool _threadRunning;
Thread _thread;
void Start()
{
// Begin our heavy work on a new thread.
_thread = new Thread(ThreadedWork);
_thread.Start();
}
void ThreadedWork()
{
_threadRunning = true;
bool workDone = false;
// This pattern lets us interrupt the work at a safe point if neeeded.
while(_threadRunning && !workDone)
{
// Do Work...
}
_threadRunning = false;
}
void OnDisable()
{
// If the thread is still running, we should shut it down,
// otherwise it can prevent the game from exiting correctly.
if(_threadRunning)
{
// This forces the while loop in the ThreadedWork function to abort.
_threadRunning = false;
// This waits until the thread exits,
// ensuring any cleanup we do after this is safe.
_thread.Join();
}
// Thread is guaranteed no longer running. Do other cleanup tasks.
}
}
速度を上げるためにスレッド間で厳密に作業を分割する必要がなく、ゲームの残りの部分がカチカチ音をたて続けるようにブロックしないようにする方法を探している場合、Unityのより軽量なソリューションはCoroutinesです。これらは、いくつかの作業を実行し、エンジンに制御を戻し、実行を継続し、後でシームレスに再開できる機能です。
using UnityEngine;
using System.Collections;
public class MyYieldingBehaviour : MonoBehaviour
{
void Start()
{
// Begin our heavy work in a coroutine.
StartCoroutine(YieldingWork());
}
IEnumerator YieldingWork()
{
bool workDone = false;
while(!workDone)
{
// Let the engine run for a frame.
yield return null;
// Do Work...
}
}
}
エンジンは(私が知る限り)破壊されたオブジェクトからコルーチンを取り除くため、特別なクリーンアップの考慮事項は必要ありません。
メソッドのローカル状態はすべて、yieldおよび再開しても保持されます。そのため、多くの目的で、別のスレッドで中断せずに実行されているように見えます(ただし、メインスレッドで実行するのに便利です)。メインスレッドを不当に遅くしないように、各反復が十分に短いことを確認する必要があります。
重要な操作がyieldで分離されないようにすることで、シングルスレッドの動作の一貫性を得ることができます。メインスレッド上の他のスクリプトやシステムは、作業中のデータを変更できません。
yield return行にはいくつかのオプションがあります。あなたはできる...
yield return null
次のフレームのUpdate()後に再開するyield return new WaitForFixedUpdate()
次のFixedUpdate()の後に再開するyield return new WaitForSeconds(delay)
一定のゲーム時間が経過した後に再開するyield return new WaitForEndOfFrame()
GUIがレンダリングを終了した後に再開するyield return myRequest
どこmyRequest
でWWWのインスタンスは、要求されたデータは、ウェブまたはディスクからの読み込みが終了したら再開し、。yield return otherCoroutine
where otherCoroutine
は、完了後に再開するCoroutineインスタンスotherCoroutine
です。これは多くの場合、yield return StartCoroutine(OtherCoroutineMethod())
実行時に新しいコルーチンにチェーンするためにフォームで使用されます。このコルーチンは、必要に応じてそれ自体を生成できます。
実験的に、同じコンテキストで実行を連鎖させたい場合は、2番目StartCoroutine
をスキップして単純に記述yield return OtherCoroutineMethod()
するだけで同じ目標を達成できます。
内部のラッピングは、StartCoroutine
あなたが第二の目的に関連して、ネストされたコルーチンを実行したい場合は、まだのように、有用である可能性がありますyield return otherObject.StartCoroutine(OtherObjectsCoroutineMethod())
...コルーチンをいつ次のターンに入れるかによって異なります。
またはyield break;
、コルーチンが最後に到達する前に停止return;
するには、従来の方法を早期に使用する方法があります。
重い計算を別のスレッドに入れることはできますが、UnityのAPIはスレッドセーフではないため、メインスレッドで実行する必要があります。
Asset Storeでこのパッケージを試してみると、スレッドの使用が簡単になります。http://u3d.as/wQg 1行のコードのみを使用して、スレッドを開始し、Unity APIを安全に実行できます。
Svelto.Tasksを使用すると、マルチスレッドルーチンの結果をメインスレッド(したがってユニティ関数)に非常に簡単に返すことができます。
http://www.sebaslab.com/svelto-taskrunner-run-serial-and-parallel-asynchronous-tasks-in-unity3d/
@DMGregoryはそれを本当にうまく説明しました。
スレッドとコルーチンの両方を使用できます。以前はメインスレッドをオフロードし、後でメインのスレッドに制御を返しました。重いリフトを別のスレッドにプッシュする方が合理的です。したがって、ジョブキューはあなたが求めているものかもしれません。
Unity Wikiには、本当に良いJobQueueサンプルスクリプトがあります。