はじめに
FirstVRのSDKはジェスチャーのキャリブレーションをコルーチンで実行する仕組みになっています。
これを書き換え、async/awaitで使えるようにします。
変更前のコード
次のコードは、FirstVRのサンプルコードの一部抜粋です。
public void SetTargetPress()
{
StartCoroutine(Calibrate(true));
}
public void SetNonTargetPress()
{
StartCoroutine(Calibrate(false));
}
/// <summary>
/// Calibrate the gesture with target or non-target values.
/// Calibration requires time, and it's best to let the user know what's going on, so this process is best done in a coroutine.
/// </summary>
IEnumerator Calibrate(bool target)
{
if (target)
{
// Setting target values
fvr.gestureManager.SetTargetData(gesture);
tCalibRounds++;
}
else
{
// Setting non-target values
fvr.gestureManager.SetNonTargetData(gesture);
/// The first time we set a target or non-target value, the round length and samples per second are ignored and the SVM takes only one value with dummy data then
/// the dummy data is replaced with real data.
/// After the first round the FVRGesture.calibrated flag is set to true and you are ready to start calibrating with real data
if (gesture.calibrated)
{
ntCalibRounds++;
}
else
{
nonTargetBtn.GetComponentInChildren<Text>().text = "Set\nNonTarget";
foreach (Button b in varBtns)
{
b.interactable = false;
}
}
}
// We dont wan't multiple coroutines taking the same data so it's good to block the user from starting a new one before this round is done
targetBtn.interactable = false;
nonTargetBtn.interactable = false;
resetBtn.interactable = false;
float t = 0;
while (gesture.registering)
{
/// While the target or non-target data is being set, the FVRGesture.registering flag will be set to true.
/// A count down or a image fill loading bar is a good way to let the user know your app is doing something.
/// Once the porcess is done, the FVRGesture.registering flag will be set to false, and we will exit this while loop.
t += Time.deltaTime;
if (target)
targetImg.fillAmount = t / (float) roundLength;
else
nonTargetImg.fillAmount = t / (float) roundLength;
yield return null;
}
UpdateTexts();
targetImg.fillAmount = 0;
nonTargetImg.fillAmount = 0;
// After the process is done you can enable whatever buttons you need to proceed with the calibration or move on with your app.
targetBtn.interactable = true;
nonTargetBtn.interactable = true;
resetBtn.interactable = true;
}
コルーチンでベタッと書かれているのを書き換えてみます。
拡張メソッド
キャリブレーションする部分を拡張メソッドに切り出します。
using UniRx.Async;
using UnityEngine;
namespace FVRSamples
{
public static class FVRGestureManagerExt
{
public static async UniTask CalibrateTargetGestureAsync(this FVRGestureManager manager, FVRGesture gesture,
IProgress<float> progress = null, CancellationToken token = default)
{
manager.SetTargetData(gesture);
var startTime = Time.time;
var span = manager.calibrationRoundLength;
while (gesture.registering)
{
await UniTask.Yield();
progress?.Report((Time.time - startTime) / span);
}
}
public static async UniTask CalibrateNonTargetGestureAsync(this FVRGestureManager manager, FVRGesture gesture,
IProgress<float> progress = null, CancellationToken token = default)
{
manager.SetNonTargetData(gesture);
var startTime = Time.time;
var span = manager.calibrationRoundLength;
while (gesture.registering)
{
await UniTask.Yield();
progress?.Report((Time.time - startTime) / span);
}
}
}
}
UniTask.WaitWhile
を用いることで特定のフラグがfalseになるまでawaitすることがきます。
IProgress<float>
を外から渡し、現在のキャリブレーションの進行状況を通知できるようにしています。
全体を書き換えたあとのコード
public void SetTargetPress()
{
_ = CalibrateAsync(true);
}
public void SetNonTargetPress()
{
_ = CalibrateAsync(false);
}
private async UniTaskVoid CalibrateAsync(bool target)
{
if (target)
{
tCalibRounds++;
}
else
{
if (gesture.calibrated)
{
ntCalibRounds++;
}
else
{
nonTargetBtn.GetComponentInChildren<Text>().text = "Set\nNonTarget";
foreach (Button b in varBtns)
{
b.interactable = false;
}
}
}
targetBtn.interactable = false;
nonTargetBtn.interactable = false;
resetBtn.interactable = false;
if (target)
{
await fvr.gestureManager.CalibrateTargetGestureAsync(gesture,
Progress.Create<float>(f => targetImg.fillAmount = f));
}
else
{
await fvr.gestureManager.CalibrateNonTargetGestureAsync(gesture,
Progress.Create<float>(f => nonTargetImg.fillAmount = f));
}
UpdateTexts();
targetImg.fillAmount = 0;
nonTargetImg.fillAmount = 0;
targetBtn.interactable = true;
nonTargetBtn.interactable = true;
resetBtn.interactable = true;
}
コルーチンで書くよりかは制御しやすいかなと思います。たぶん。