UniTask.WhenAny()で、思い通りの戻り値を取得する方法

この記事で伝えたいこと

var result = await UniTask.WhenAny(tasks)

上記のように渡されたtasksの中で、一番早く処理が終わったtaskを取得したい場合、以下のように書けます。

var result = await UniTask.WhenAny(buttonTasks)
.ContinueWith(t => t.result);

複数のボタンのうち、どれか1つがクリックされるまで待つ処理

RPGゲームの戦闘にて、プレイヤーが次に使う技を選択する場面を考えてみます。

考えられる実装方法としては、選択肢となる技を一つずつボタンにして並べるものがあります。

そして、非同期処理を直感的に実装できるライブラリUniTaskを使用することで、どれか一つのボタンがクリックされるまで待つという処理を実装することができます。

var buttonTasks = new List<UniTask<Skill>>();
buttonTasks.Add(skillButton1.OnClickTask);
buttonTasks.Add(skillButton2.OnClickTask);
var result = await UniTask.WhenAny(buttonTasks);

ここでのskillButtonOnClickTaskは、skillButton(技の名前が表示されているボタン)がクリックされたときに、そのskillButtonが格納しているSkillを返すUniTaskです。

ボタンにアタッチしたクラスの一部分は以下の通りです。

buttonのクリックを監視し、クリックされたらskillを返すObservableを生成、それをUniTaskに変換しています。

[SerializeField] 
Button button;
Skill skill;
public UniTask OnClickSkillButtonTask => button.OnClickAsObservable()
.Select(_ => skill)
.ToUniTask(true);

これで、UniTask.WhenAny()の戻り値resultには、OnClickSkillButtonTaskの戻り値skillが格納されるはず…と考えていました。

UniTask.WhenAny()の戻り値はタプル型

しかし、UniTask.WhenAny()の戻り値がタプル型という特殊な型だったため、想定していた値が返ってきませんでした。
具体的には、以下のような型の値が返ってきました。

UniTask<(int winArgumentIndex, T result)>

この、()でくくられているのがタプル型。複数の異なる値をまとめて扱いたいが、わざわざ構造体やクラスにまとめるほどではない場合に、簡易的に値をひとまとめにできる記述方法です。

今回は、この2つの値のうち、T resultのみを取得したかったので、何か方法がないかと調べたところ、ContinueWith()という関数を見つけました。

var result = await UniTask.WhenAny(buttonTasks)
              .ContinueWith(t => t.result);

このように記述すると、返ってきたタプル型のresultのみを取得し、var resultに格納することができます。