関連ページ
Unityで非同期処理を実装するには、コルーチン、Task、UniTaskの3つの選択肢があります。
このうち、現在の環境でわざわざTaskを選択する必要はなくて、必然的にCoroutineかUniTaskを使うことになると思います。
今現在の自分の考えとしては、UniTaskとCoroutineどちらか一択ではなく、場面場面で併用でも良い気がしています。
UniTaskは返り値を返すことができるのがとても便利です。
最近はダイアログをUniTaskで非同期処理で生成して、その返り値でダイアログのインスタンスを取得することなどを業務で行っています。
一方で中断処理が面倒という欠点もあります。
反対にCoroutineは中断処理が直感的で分かり易く、GameObjectが非アクティブになると自動で停止してくれるも利点。
これもこれもで、Unity開発では未だに便利だと思っています。

シンプルなコードで実装の比較
最初に、全く同じシンプルな実装をCoroutineとUniTask、Taskで行い、3者の比較をしていきます。
その次に中断処理について解説します。
すでに使い方をある程度知っている人は飛ばして大丈夫です。
実装する内容は、右のボタンを押すと星が5秒かけて右端に移動し、右端に到達すると星が点滅します。
左のボタンを押すと星が3秒かけて左端に移動し、左端に到達すると星が点滅します。
星の移動中は、上のテキストに経過時間が表示されます。
補足として今回は、星の移動処理に関してのみCoroutineでもUniTaskでもTaskでもなく
Tween
を使っていきます。(便利なので)
CoroutineはUnityEngineのnamespaceの中に入っているので、特に事前準備は必要ありません。

using
DG.Tweening;
using
UnityEngine;
using
UnityEngine.UI;
using
System.Collections;
/// <summary>
/// Coroutineを使ったアニメーション実装
/// </summary>
public
class
TestCoroutine
:
MonoBehaviour
{
[SerializeField]
private
Button
rightButton;
[SerializeField]
private
Button
leftButton;
[SerializeField]
private
Text
infoText;
[SerializeField]
private
Transform
star;
private
float
rightSlideSec
=
5f;
private
float
leftSlideSec
=
3f;
private
float
rightEndPosX
=
500;
private
float
leftEndPosX
=
-500;
private
int
flashCount
=
3;
private
float
flashInterval
=
0.2f;
private
void
Start()
{
//ボタンイベントをスクリプトから登録
rightButton.onClick.AddListener(OnTapRightButton);
leftButton.onClick.AddListener(OnTapLeftButton);
}
/// <summary>
/// 右のボタンを押した時の処理
/// </summary>
private
void
OnTapRightButton()
{
StartCoroutine(CommonSlideAction(rightEndPosX,
rightSlideSec));
}
/// <summary>
/// 左のボタンを押した時の処理
/// </summary>
private
void
OnTapLeftButton()
{
StartCoroutine(CommonSlideAction(leftEndPosX,
leftSlideSec));
}
/// <summary>
/// アニメーションの共通処理
/// </summary>
private
IEnumerator
CommonSlideAction(float
endPosX,
float
duration)
{
infoText.text
=
"経過時間0秒";
//星をTweenを使って移動させ、1秒置きにテキストを更新する
star.DOLocalMoveX(endPosX,
duration).SetEase(Ease.Linear);
var
remainTime
=
duration;
while
(remainTime
>
0)
{
//1秒待機
yield
return
new
WaitForSeconds(1);
remainTime--;
infoText.text
=
"経過時間"
+
(duration
-
remainTime)
+
"秒";
}
//星が目的地に着いたら点滅させる
for(int
i
=
0;
i
<
flashCount
*
2;
i++)
{
//偶数かどうか判定
var
isEven
=
i
%
2
==
0;
//偶数なら星を非表示、奇数なら表示
star.gameObject.SetActive(isEven
?
false
:
true);
//0.2秒秒待機
yield
return
new
WaitForSeconds(flashInterval);
}
infoText.text
=
"やりましたね!!";
}
}

返り値
IEnumerator
で定義された関数を、
StartCoroutine
関数で読んで非同期処理を行っています。
実際の待機処理のコードは
yield return new WaitForSeconds
の部分。
まずCysharpのGitHubから、対象のリポジトリをUnityPackageに追加する必要があります。
追加方法はこちら
スクリプト上でUniTaskを使うには
using Cysharp.Threading.Tasks;
を追記します。

using
System;
using
DG.Tweening;
using
UnityEngine;
using
UnityEngine.UI;
using
Cysharp.Threading.Tasks;
/// <summary>
/// UniTaskを使ったアニメーション実装
/// </summary>
public
class
TestUniTask
:
MonoBehaviour
{
[SerializeField]
private
Button
rightButton;
[SerializeField]
private
Button
leftButton;
[SerializeField]
private
Text
infoText;
[SerializeField]
private
Transform
star;
private
float
rightSlideSec
=
5f;
private
float
leftSlideSec
=
3f;
private
float
rightEndPosX
=
500;
private
float
leftEndPosX
=
-500;
private
int
flashCount
=
3;
private
float
flashInterval
=
0.2f;
private
void
Start()
{
//ボタンイベントをスクリプトから登録
rightButton.onClick.AddListener(OnTapRightButton);
leftButton.onClick.AddListener(OnTapLeftButton);
}
/// <summary>
/// 右のボタンを押した時の処理
/// </summary>
private
void
OnTapRightButton()
{
//CommonSlideActionの非同期処理を実行後、このOnTapRightButton関数は何もすることが無いのでForgetで待機を破棄する。
CommonSlideAction(rightEndPosX,
rightSlideSec).Forget();
}
/// <summary>
/// 左のボタンを押した時の処理
/// </summary>
private
void
OnTapLeftButton()
{
//CommonSlideActionの非同期処理を実行後、このOnTapLeftButton関数は何もすることが無いのでForgetで待機を破棄する。
CommonSlideAction(leftEndPosX,
leftSlideSec).Forget();
}
/// <summary>
/// アニメーションの共通処理
/// </summary>
private
async
UniTask
CommonSlideAction(float
endPosX,
float
duration)
{
infoText.text
=
"経過時間0秒";
//星をTweenを使って移動させ、1秒置きにテキストを更新する
star.DOLocalMoveX(endPosX,
duration).SetEase(Ease.Linear);
var
remainTime
=
duration;
while
(remainTime
>
0)
{
//UniTask.Delayは基本はミリ秒を渡す設計だが(1000ミリ秒=1秒)、TimeSpan.FromSecondを渡すことで秒数指定できる
await
UniTask.Delay(TimeSpan.FromSeconds(1));
remainTime--;
infoText.text
=
"経過時間"
+
(duration
-
remainTime)
+
"秒";
}
//星が目的地に着いたら点滅させる
for(int
i
=
0;
i
<
flashCount
*
2;
i++)
{
//偶数かどうか判定
var
isEven
=
i
%
2
==
0;
//偶数なら星を非表示、奇数なら表示
star.gameObject.SetActive(isEven
?
false
:
true);
//TimeSpan.FromSecondに0.2を渡しているので0.2秒待機
await
UniTask.Delay(TimeSpan.FromSeconds(flashInterval));
}
infoText.text
=
"やりましたね!!";
}
}

Coroutineの
yeild return new WaitForSeconds
に相当するのが
await UniTask.Delay
。
UniTask.Delayはミリ秒指定が基本なので、1秒待ちたい場合はTimeSpan.FromSecondsを間に挟みます。
関数を宣言するとき、Coroutineは返り値に
IEnumerator
を指定しますが、UniTaskは
async UniTask
と入力します。
Coroutineと違いその関数を呼ぶ時にStartCoroutineと入力する必要がなく、通常の関数と呼び出し方法が変わりません。
IEnumeratorの関数内ではyield return ~の待機処理コードが無いとエラーが出ますが、aync Taskでは待機処理コードが無くても別に問題ないです。
またUniTaskで特徴的なのがOnTapRightButton()とOnTapLeftButton()内で使っているForget()。
これは非同期処理を呼び出す側が、その非同期処理が終わるのを待機する必要がないことを表します。
仮にこのForgetを記入しない場合、C#は必要のないWarningを表示する。
Warningが表示されるだけで実装上は何も問題がないですが、紛らわしいので待つ必要がないならForgetを付けて警告を消すべきです。
以上の細かい違いがありますが、このレベルの実装では正直CorouineでもUniTaskでもどっちでもいいです。
UniTaskはTaskの上位互換なので、Unityを使ってるならわざわざTaskを使う必要はないです。
でもUnity以外ではもしかして使う可能性もあるので自分用にメモ書き。
Taskを使用するにはusing System.Threading.Tasks;のコードを追加する必要があります。

using
System;
using
DG.Tweening;
using
UnityEngine;
using
UnityEngine.UI;
using
System.Threading.Tasks;
/// <summary>
/// Taskを使ったアニメーション実装
/// </summary>
public
class
TestTask
:
MonoBehaviour
{
[SerializeField]
private
Button
rightButton;
[SerializeField]
private
Button
leftButton;
[SerializeField]
private
Text
infoText;
[SerializeField]
private
Transform
star;
private
float
rightSlideSec
=
5f;
private
float
leftSlideSec
=
3f;
private
float
rightEndPosX
=
500;
private
float
leftEndPosX
=
-500;
private
int
flashCount
=
3;
private
float
flashInterval
=
0.2f;
private
void
Start()
{
//ボタンイベントをスクリプトから登録
rightButton.onClick.AddListener(OnTapRightButton);
leftButton.onClick.AddListener(OnTapLeftButton);
}
/// <summary>
/// 右のボタンを押した時の処理
/// </summary>
private
void
OnTapRightButton()
{
CommonSlideAction(rightEndPosX,
rightSlideSec);
}
/// <summary>
/// 左のボタンを押した時の処理
/// </summary>
private
void
OnTapLeftButton()
{
CommonSlideAction(leftEndPosX,
leftSlideSec);
}
/// <summary>
/// アニメーションの共通処理
/// </summary>
private
async
Task
CommonSlideAction(float
endPosX,
float
duration)
{
infoText.text
=
"経過時間0秒";
//星をTweenを使って移動させ、1秒置きにテキストを更新する
star.DOLocalMoveX(endPosX,
duration).SetEase(Ease.Linear);
var
remainTime
=
duration;
while
(remainTime
>
0)
{
//Task.Delayは基本はミリ秒を渡す設計だが(1000ミリ秒=1秒)、TimeSpan.FromSecondを渡すことで秒数指定できる
await
Task.Delay(TimeSpan.FromSeconds(1));
remainTime--;
infoText.text
=
"経過時間"
+
(duration
-
remainTime)
+
"秒";
}
//星が目的地に着いたら点滅させる
for(int
i
=
0;
i
<
flashCount
*
2;
i++)
{
//偶数かどうか判定
var
isEven
=
i
%
2
==
0;
//偶数なら星を非表示、奇数なら表示
star.gameObject.SetActive(isEven
?
false
:
true);
//TimeSpan.FromSecondに0.2を渡しているので0.2秒待機
await
Task.Delay(TimeSpan.FromSeconds(flashInterval));
}
infoText.text
=
"やりましたね!!";
}
}

TaskはUniTaskと違いForget()が用意されていない事です。
なので警告を消すことができません。地味にやっかい。

中断処理で実装の比較
コードに重大な違いが出てくるのが中断処理を実行する時。
さて、もし右のボタンを押した後、その処理が完了する前に左のボタンを押すと下の動画のようになります。
右にスライドする処理と左にスライドする処理が同時に走っています。
しかも右スライドの方がアニメーション時間が長いので、左のボタンを右ボタンの後に押しても最終的な星の座標は右端になっています。
こうした不自然で予期できない挙動を阻止するため、ゲーム実装では頻繁に中断処理を差し込むことになります。
修正した結果は次の通り。

using
DG.Tweening;
using
UnityEngine;
using
UnityEngine.UI;
using
System.Collections;
/// <summary>
/// Coroutineを使ったアニメーション実装
/// </summary>
public
class
TestCoroutine
:
MonoBehaviour
{
[SerializeField]
private
Button
rightButton;
[SerializeField]
private
Button
leftButton;
[SerializeField]
private
Text
infoText;
[SerializeField]
private
Transform
star;
private
float
rightSlideSec
=
5f;
private
float
leftSlideSec
=
3f;
private
float
rightEndPosX
=
500;
private
float
leftEndPosX
=
-500;
private
int
flashCount
=
3;
private
float
flashInterval
=
0.2f;
//中断処理のための変数を追加
private
Coroutine
slideCoroutine;
private
Tweener
slideTweener;
private
void
Start()
{
//ボタンイベントをスクリプトから登録
rightButton.onClick.AddListener(OnTapRightButton);
leftButton.onClick.AddListener(OnTapLeftButton);
}
/// <summary>
/// 右のボタンを押した時の処理
/// </summary>
private
void
OnTapRightButton()
{
//実行中のコルーチンがある場合中断
if
(slideCoroutine
!=
null)
{
StopCoroutine(slideCoroutine);
slideTweener.Kill();
}
slideCoroutine
=
StartCoroutine(CommonSlideAction(rightEndPosX,
rightSlideSec));
}
/// <summary>
/// 左のボタンを押した時の処理
/// </summary>
private
void
OnTapLeftButton()
{
//実行中のコルーチンがある場合中断
if
(slideCoroutine
!=
null)
{
StopCoroutine(slideCoroutine);
slideTweener.Kill();
}
slideCoroutine
=
StartCoroutine(CommonSlideAction(leftEndPosX,
leftSlideSec));
}
/// <summary>
/// アニメーションの共通処理
/// </summary>
private
IEnumerator
CommonSlideAction(float
endPosX,
float
duration)
{
star.gameObject.SetActive(true);
infoText.text
=
"経過時間0秒";
//星をTweenを使って移動させ、1秒置きにテキストを更新する
//中断処理のため実行したTweenは変数に保存
slideTweener
=
star.DOLocalMoveX(endPosX,
duration).SetEase(Ease.Linear);
var
remainTime
=
duration;
while
(remainTime
>
0)
{
//1秒待機
yield
return
new
WaitForSeconds(1);
remainTime--;
infoText.text
=
"経過時間"
+
(duration
-
remainTime)
+
"秒";
}
//星が目的地に着いたら点滅させる
for
(int
i
=
0;
i
<
flashCount
*
2;
i++)
{
//偶数かどうか判定
var
isEven
=
i
%
2
==
0;
//偶数なら星を非表示、奇数なら表示
star.gameObject.SetActive(isEven
?
false
:
true);
//0.2秒秒待機
yield
return
new
WaitForSeconds(flashInterval);
}
infoText.text
=
"やりましたね!!";
}
}

private Coroutine slideCoroutine;
この変数に実行するCoroutineをあらかじめ保存します。
次にボタンを押す際にはまず実行中のコルーチンを停止させます。
また星の移動にはTweenを使っているますが、TweenはCoroutineとは関係ない独立したスレッドで動いています。
なので
private Tweener slideTweener;
で実行するTweenを保存させています。Coroutineの停止と同じタイミングで停止を呼びます。
UniTaskで停止処理を行うには、
using System.Threading;
を新たに追記します。

using
System;
using
DG.Tweening;
using
UnityEngine;
using
UnityEngine.UI;
using
Cysharp.Threading.Tasks;
using
System.Threading;
/// <summary>
/// UniTaskを使ったアニメーション実装
/// </summary>
public
class
TestUniTask
:
MonoBehaviour
{
[SerializeField]
private
Button
rightButton;
[SerializeField]
private
Button
leftButton;
[SerializeField]
private
Text
infoText;
[SerializeField]
private
Transform
star;
private
float
rightSlideSec
=
5f;
private
float
leftSlideSec
=
3f;
private
float
rightEndPosX
=
500;
private
float
leftEndPosX
=
-500;
private
int
flashCount
=
3;
private
float
flashInterval
=
0.2f;
//中断処理のための変数を追加
private
CancellationTokenSource
slideTokenSource;
private
Tweener
slideTweener;
private
void
Start()
{
//ボタンイベントをスクリプトから登録
rightButton.onClick.AddListener(OnTapRightButton);
leftButton.onClick.AddListener(OnTapLeftButton);
}
/// <summary>
/// 右のボタンを押した時の処理
/// </summary>
private
void
OnTapRightButton()
{
//実行中のタスクがある場合中断
if
(slideTokenSource
!=
null)
{
slideTokenSource.Cancel();
slideTweener.Kill();
}
slideTokenSource
=
new
CancellationTokenSource();
CommonSlideAction(rightEndPosX,
rightSlideSec,
slideTokenSource.Token).Forget();
}
/// <summary>
/// 左のボタンを押した時の処理
/// </summary>
private
void
OnTapLeftButton()
{
//実行中のタスクがある場合中断
if
(slideTokenSource
!=
null)
{
slideTokenSource.Cancel();
slideTweener.Kill();
}
slideTokenSource
=
new
CancellationTokenSource();
CommonSlideAction(leftEndPosX,
leftSlideSec,
slideTokenSource.Token).Forget();
}
/// <summary>
/// アニメーションの共通処理
/// </summary>
private
async
UniTask
CommonSlideAction(float
endPosX,
float
duration,
CancellationToken
token)
{
//中断を実行したタイミングによっては後半の点滅で
//star.gameOjbectが消えてる可能性があるのでtrueにする
star.gameObject.SetActive(true);
infoText.text
=
"経過時間0秒";
//星をTweenを使って移動させ、1秒置きにテキストを更新する
//中断処理のため実行したTweenは変数に保存
slideTweener
=
star.DOLocalMoveX(endPosX,
duration).SetEase(Ease.Linear);
var
remainTime
=
duration;
while
(remainTime
>
0)
{
//UniTask.Delayは基本はミリ秒を渡す設計だが(1000ミリ秒=1秒)、TimeSpan.FromSecondを渡すことで秒数指定できる
await
UniTask.Delay(TimeSpan.FromSeconds(1));
//タスクの中断の命令が出ていればreturn
if(token.IsCancellationRequested)
{
return;
}
remainTime--;
infoText.text
=
"経過時間"
+
(duration
-
remainTime)
+
"秒";
}
//星が目的地に着いたら点滅させる
for(int
i
=
0;
i
<
flashCount
*
2;
i++)
{
//偶数かどうか判定
var
isEven
=
i
%
2
==
0;
//偶数なら星を非表示、奇数なら表示
star.gameObject.SetActive(isEven
?
false
:
true);
//TimeSpan.FromSecondに0.2を渡しているので0.2秒待機
await
UniTask.Delay(TimeSpan.FromSeconds(flashInterval));
//タスクの中断の命令が出ていればreturn
if(token.IsCancellationRequested)
{
return;
}
}
infoText.text
=
"やりましたね!!";
}
}

UniTaskの中断処理では
StopCoroutine
ではなく
CancellationTokenSource
と
CancellationToken
を使います。
これが直感的でなく非常に分かりずらいです。
中断命令はCancellationTokenSourceで行いますが、中断命令が出されたかどうかのフラグはCancellationTokenを見る必要があります。
UniTaskの中断処理は、StopCoroutineのようにCoroutine関数の外部で行うのではなく、Task関数の内部で行う必要があります。
下が実際の中断処理のコード。
if(token.IsCancellationRequested)
{
return;
}
しかもUniTask関数の内部の全てのawait UniTask.Delayの後ろに中断処理を書き込む必要があります。
これが厄介で、長いアニメーションでは頻繁にUniTask.Delayを行うので、無意味に冗長で汚いコードになります。抜けも起きやすいです。
UniTaskに関する不満はこれ一点で、ここさえ解決してくれれば良いのにとずっと思ってる。
Taskの中断処理もUniTaskと同じ仕様。

using
System;
using
DG.Tweening;
using
UnityEngine;
using
UnityEngine.UI;
using
System.Threading.Tasks;
using
System.Threading;
/// <summary>
/// Taskを使ったアニメーション実装
/// </summary>
public
class
TestTask
:
MonoBehaviour
{
[SerializeField]
private
Button
rightButton;
[SerializeField]
private
Button
leftButton;
[SerializeField]
private
Text
infoText;
[SerializeField]
private
Transform
star;
private
float
rightSlideSec
=
5f;
private
float
leftSlideSec
=
3f;
private
float
rightEndPosX
=
500;
private
float
leftEndPosX
=
-500;
private
int
flashCount
=
3;
private
float
flashInterval
=
0.2f;
//中断処理のための変数を追加
private
CancellationTokenSource
slideTokenSource;
private
Tweener
slideTweener;
private
void
Start()
{
//ボタンイベントをスクリプトから登録
rightButton.onClick.AddListener(OnTapRightButton);
leftButton.onClick.AddListener(OnTapLeftButton);
}
/// <summary>
/// 右のボタンを押した時の処理
/// </summary>
private
void
OnTapRightButton()
{
//実行中のタスクがある場合中断
if
(slideTokenSource
!=
null)
{
slideTokenSource.Cancel();
slideTweener.Kill();
}
slideTokenSource
=
new
CancellationTokenSource();
CommonSlideAction(rightEndPosX,
rightSlideSec,
slideTokenSource.Token);
}
/// <summary>
/// 左のボタンを押した時の処理
/// </summary>
private
void
OnTapLeftButton()
{
//実行中のタスクがある場合中断
if
(slideTokenSource
!=
null)
{
slideTokenSource.Cancel();
slideTweener.Kill();
}
slideTokenSource
=
new
CancellationTokenSource();
CommonSlideAction(leftEndPosX,
leftSlideSec,
slideTokenSource.Token);
}
/// <summary>
/// アニメーションの共通処理
/// </summary>
private
async
Task
CommonSlideAction(float
endPosX,
float
duration,
CancellationToken
token)
{
//中断を実行したタイミングによっては後半の点滅で
//star.gameOjbectが消えてる可能性があるのでtrueにする
star.gameObject.SetActive(true);
infoText.text
=
"経過時間0秒";
//星をTweenを使って移動させ、1秒置きにテキストを更新する
//中断処理のため実行したTweenは変数に保存
slideTweener
=
star.DOLocalMoveX(endPosX,
duration).SetEase(Ease.Linear);
var
remainTime
=
duration;
while
(remainTime
>
0)
{
//Task.Delayは基本はミリ秒を渡す設計だが(1000ミリ秒=1秒)、TimeSpan.FromSecondを渡すことで秒数指定できる
await
Task.Delay(TimeSpan.FromSeconds(1));
//タスクの中断の命令が出ていればreturn
if(token.IsCancellationRequested)
{
return;
}
remainTime--;
infoText.text
=
"経過時間"
+
(duration
-
remainTime)
+
"秒";
}
//星が目的地に着いたら点滅させる
for(int
i
=
0;
i
<
flashCount
*
2;
i++)
{
//偶数かどうか判定
var
isEven
=
i
%
2
==
0;
//偶数なら星を非表示、奇数なら表示
star.gameObject.SetActive(isEven
?
false
:
true);
//TimeSpan.FromSecondに0.2を渡しているので0.2秒待機
await
Task.Delay(TimeSpan.FromSeconds(flashInterval));
//タスクの中断の命令が出ていればreturn
if(token.IsCancellationRequested)
{
return;
}
}
infoText.text
=
"やりましたね!!";
}
}


UniTaskの返り値について
UniTask(もしくはTask)で一番自分が便利だと思うのは、Genericで返り値を指定できることです。
これはCoroutineでは真似できないです。
試しに下の動画のような処理を作ってみます。
「計測スタート」のボタンをタップした後、右が左のキーを計5回タップすると、押した回数が多い方に星が移動します。
コードは下のような感じ。

using
System.Collections.Generic;
using
UnityEngine;
using
UnityEngine.UI;
using
Cysharp.Threading.Tasks;
using
System.Linq;
public
class
TestUniTaskMeasure
:
MonoBehaviour
{
[SerializeField]
private
Button
startMeasureButton;
[SerializeField]
private
Text
keyDownStatusText;
[SerializeField]
private
Transform
star;
//keyDownRecordにAddする文字列を事前にreadonlyで定義
private
readonly
string
leftString
=
"Left";
private
readonly
string
rightString
=
"Right";
//計測終了までに必要なキーの押下回数
private
readonly
int
needKeyDownCount
=
5;
private
int
curKeyDownCount
=
0;
//計測結果に応じて移動させる星の最終座標
private
float
leftEndPosX
=
-500;
private
float
rightEndPosX
=
500;
//どちらのキーを押したかの記録を保存するためのList
private
List<string>
keyDownRecord
=
new
List<string>();
private
void
Start()
{
//ボタンイベントをスクリプトから登録
startMeasureButton.onClick.AddListener(()
=>
OnTapStartMeasure().Forget());
}
/// <summary>
/// 計測スタートのボタンを押した時の処理
/// </summary>
private
async
UniTask
OnTapStartMeasure()
{
//ボタン表示を一時的に消す
startMeasureButton.gameObject.SetActive(false);
keyDownStatusText.text
=
"計測中...";
//初期化処理
keyDownRecord.Clear();
curKeyDownCount
=
0;
Vector2
curPos
=
star.localPosition;
star.localPosition
=
new
Vector3(0,
curPos.y);
//Measure関数の終了をawaitで待機し、結果はresultに代入
string
resultString
=
await
Measure();
//結果が左ならleftEndPosXを、右ならrightEndPosをresultPosに代入
float
resultPos
=
resultString
==
leftString
?
leftEndPosX
:
rightEndPosX;
//星のx座標を移動させる
star.localPosition
=
new
Vector3(resultPos,
curPos.y);
//ボタン表示を復活させる
startMeasureButton.gameObject.SetActive(false);
keyDownStatusText.text
=
"計測終了!
結果
=
"
+
resultString;
}
/// <summary>
/// キーダウンの計測を開始。右矢印、もしくは左矢印を計5回押すまで計測を続ける
/// 計測が終わったら、どちらの矢印をより多く押したかの結果を返す
/// </summary>
private
async
UniTask<string>
Measure()
{
while(true)
{
//キーボードの左矢印を押下
if(Input.GetKeyDown(KeyCode.LeftArrow))
{
keyDownRecord.Add(leftString);
curKeyDownCount++;
keyDownStatusText.text
=
leftString
+
"のキーを押下!
押下回数"
+
curKeyDownCount;
}
//キーボードの右矢印を押下
if(Input.GetKeyDown(KeyCode.RightArrow))
{
keyDownRecord.Add(rightString);
curKeyDownCount++;
keyDownStatusText.text
=
rightString
+
"のキーを押下!
押下回数"
+
curKeyDownCount;
}
//キーの押下回数がneedKeyDownCountに達したら計測終了処理へ
if(curKeyDownCount
>=
needKeyDownCount)
{
//LINQでkeyDownRecord内の左矢印を押した回数と右矢印を押した回数を取得
int
leftCount
=
keyDownRecord.Count(x
=>
x
==
leftString);
int
rightCount
=
keyDownRecord.Count(x
=>
x
==
rightString);
//leftCountの方が大きければleftStringを、そうでなければrightStringを結果として返す
return
leftCount
>
rightCount?
leftString
:
rightString;
}
//1フレームだけ待機
await
UniTask.Yield();
}
}
}

OnTapStartMeasure()関数の中で、
//Measure関数の終了をawaitで待機し、結果はresultに代入
string
resultString
=
await
Measure();
といった具合にMeasure()関数の終了をawaitで待っています。
Measure()関数の定義を見てみると、返り値がUniTask<string>と指定してあります。
private async UniTask<string> Measure()
つまりこの関数は、返り値に必ずstringを返す必要があるUniTaskの非同期処理関数ということになります。
Measure()関数の後半に実際にその返り値を返す処理が打ち込んである。
//キーの押下回数がneedKeyDownCountに達したら計測終了処理へ
if(curKeyDownCount
>=
needKeyDownCount)
{
//LINQでkeyDownRecord内の左矢印を押した回数と右矢印を押した回数を取得
int
leftCount
=
keyDownRecord.Count(x
=>
x
==
leftString);
int
rightCount
=
keyDownRecord.Count(x
=>
x
==
rightString);
//leftCountの方が大きければleftStringを、そうでなければrightStringを結果として返す
return
leftCount
>
rightCount?
leftString
:
rightString;
}
今回たまたまGenericでstringを指定していますが、別にboolでも、intでも、自分が定義したクラスでも、なんでも返すことが出来ます。
0
0