【Unity】重い処理はメインスレッド以外の別スレッドで動かそう!!【非同期処理】

重い処理はメインスレッド以外の別スレッドで動かそう!!Tips

この記事はITRC Advent Calendar 2021 3日目の記事です。

こんにちは!saba🐟です!

みなさんはfor文やwhile文で計算をぶん回したい時、どのように計算をさせていますか?
その計算中画面全体がガクッと止まったフリーズしてしまった経験はありませんか?

今回はそんな悩みを解決する記事を書いていこうと思います!

大前提

まず、大前提として「本当にその繰り返し処理をしなれけばならないか」を考えます。

他の処理で置き換えることで、そもそも繰り返し処理でぶん回す必要がなくなることもありますのでそこをしっかり確認した上でどうしてもやらなければならない場合にこの記事の内容を参考にしていただければと思います。

また、この記事ではUniTaskを使用します。UniTaskを知らなくても理解することはできますが、UniTaskを知っている方がより理解が深まると思いますのでUniTaskの基本を知りたい方は以下の記事をご覧ください!

ポーカーゲームでの勝率計算

ここではポーカーゲームでの勝率計算を考えます。

どのくらいの確率で勝てるかを計算する時に「両者ともストレートで相手がA,自分がQだったら相手の勝ち」のようにポーカーの勝敗は複雑で勝率を出すのに、どうしても大量の計算が必要になります。

その処理を何も考えずにプログラムで書くと下のGIFのように、結果が出るまでに3秒ほど止まってしまいました。。。

このように画面が止まってしまうのはゲームとしてユーザーのストレスの元になってしまいますよね。。ただ、勝率が出ることはユーザーにとって嬉しい機能なのは事実

そこで、この処理を並列処理で動かすことで、解決していきます。

原因

なぜこのように画面が止まってしまうのでしょうか。

それはUnityの描画処理メインスレッドで行われていることが原因です。

この描画処理と確率計算を同時にメインスレッドで動かすことで描画処理が行えず、画面が止まってしまいます。

画面に表示する役割(View)以外の確率計算のような処理はメインスレッドを使わないのが得策でしょう。

ただ、Unityの標準メソッドはメインスレッド以外ではほぼ動かないので注意を…!
基本MonoBehaviourを継承していないクラスを別スレッドで動かしましょう!

対処法

メインスレッドで確率計算をしなければいいことはわかったけど別スレッドで動かすやり方がわからん!という声が聞こえた気がしたので簡単に別スレッドで動かす方法を紹介しようと思います。

UniTaskを使って別スレッドで動かす

UniTaskを使えば別スレッドでの処理が簡単に行うことができます。

具体的には、UniTask.SwitchToThreadPool()を使うことで処理するスレッドの変更を行うことができます。

// 処理するスレッドの変更
await UniTask.SwitchToThreadPool();

また、以下のようにawait UniTask.Run()でも別スレッドに移動します!

//メソッドの実行
await UniTask.Run(メソッド名,CancellationToken)

めちゃ簡単ですね!

ただ、UniTaskを使用しているのでCancellationTokenの扱いは忘れないようにしましょう。

キャンセル処理を書かないと常に動き続けてしまったり、逆に重くなる原因にもなります。

もっと具体的に処理を載せるとこのようになります。

public class GameController : MonoBehaviour
{
    private void Start()
    {
        //勝率計算
        //CancellationTokenの発行
        probCts = new CancellationTokenSource();
        //非同期処理呼び出し。
        WinProbabilityText(_deck, p1Hands, p2Hands, _communityCard,4,probCts.Token).Forget();
    }
 
    private async UniTaskVoid WinProbabilityText(Deck allDeck,Card[] p1Cards,Card[]p2Cards,Card[]communityCards,int communityCardNum,CancellationToken ct)
    {
        //ここでloadingのぐるぐるだったり、テキストで「計算中・・・」などを表示させる。
        p1WinProbText.text = $"確率:計算中...";
        p2WinProbText.text = $"確率:計算中...";

        try
        {
            //計算の呼び出し。値が入ってくるかCancelさせるまで待つ
            _winProb = await UniTask.Run(()=> WinProbability.GetWinProbability(allDeck, p1Cards, p2Cards, communityCards,communityCardNum,ct), cancellationToken: ct);
            p1WinProbText.text = $"確率:{_winProb[0]:f2}%";
            p2WinProbText.text = $"確率:{_winProb[1]:f2}%";
        }
        catch (OperationCanceledException e)
        {
            Debug.Log($"キャンセルされた{e}");
            p1WinProbText.text = $"確率:----";
            p2WinProbText.text = $"確率:----";
        }
    }
}

public static class WinProbability
{
    public static float[] GetWinProbability(Deck deck,Card[] p1Cards,Card[]p2Cards,Card[]communityCards,int communityCardNum,CancellationToken cts)
    {
        //確率計算の処理。長いので省略
    }
}

このようにして呼び出すことで以下のGIFのようにカクつきのない動作を実現できます!!

まとめ

このようにUnityではメインスレッドで描画処理を行なっているので重い処理は別のスレッドで動かすことで対処することができます。

また、UniTaskを使用することで、別スレッドでの動作も簡単に行うことができますので是非、試してみてください!

それではまた!!!

コメント

タイトルとURLをコピーしました