どうも、土鍋です。
この記事はIwakenLab Advent Calendar 2022の19日目の記事です。(遅刻してすいません)
今回はIVRCでも使用したXR Interaction Toolkit & OpenXR Plugin & Ouest2 & VIVEトラッカーで現実で足踏みをするとVR空間を歩けるようにするシステムを制作したので解説します。
IVRCについてはこちらに書きました。よろしければ御覧ください。
Quest2とVIVEトラッカーの併用(Oculus Link使用)
まずはQuest2とVIVEトラッカーを併用する必要があります。
こちらの記事を参考にさせていただきました。
XR Interaction Toolkit & OpenXR PluginでVIVEトラッカーを使用する
XR Interaction Toolkit & OpenXR PluginでVIVEトラッカーを使用するまではこちらの記事を参考にさせていただきました。
最近対応したばかりなので、記事も少なく苦労しました...(こりんさんの記事には毎回お世話になっています)
VR空間を歩けるようにする
それでは歩けるようにしましょう。
仕様
- 足をその場で上下させることで移動
- 歩いているかの判断はy座標の差が一定以上になったとき
- 一定時間足を揃えると停止
- 一定時間片足立ちでも停止
UniTaskとUniRxを使用しています。
ContinuousMoveと挙動を一致させたいのでContinuousMoveProviderBaseを継承しています。 またReadInputの部分やInputActionの取得部分は基本的にContinuousMoveと同じような作りにしました。
コードの解説
void Start() { _isGround.Value = true; _isGround .Subscribe(isGround => { if (isGround) { _cancellationTokenSourceOneLeg.Cancel(); _cancellationTokenSourceStanding = new CancellationTokenSource(); Standing(_cancellationTokenSourceStanding.Token).Forget(); } else { _cancellationTokenSourceStanding.Cancel(); _cancellationTokenSourceOneLeg = new CancellationTokenSource(); Walking(_cancellationTokenSourceOneLeg.Token).Forget(); } }).AddTo(this); }
足が地面についているか否かのReactiveProperty
StandingメソッドとWalkingメソッド内で時間計測し、一定時間片足立ちor両足が揃っていると歩く処理をしないようにしています。
cancellationTokenSource.Cancel()メソッドで中断処理を行っています。
protected override Vector2 ReadInput() { var leftFootValue = _LeftFootMoveAction.action?.ReadValue<Vector3>() ?? Vector3.zero; var rightFootValue = _RightFootMoveAction.action?.ReadValue<Vector3>() ?? Vector3.zero; Vector2 footValueVector2 = new Vector2(); _isGround.Value = (Math.Abs(leftFootValue.y - rightFootValue.y) < threshold) ? true : false; walking = (!_isStanding && !_isOneLeg); footValueVector2 = walking ? Vector2.up : Vector2.zero; return footValueVector2; }
ContinuousMoveProviderBaseのReadInput()をoverrideしています。
_isGroundは両足のy座標の差が閾値を超えているか否かで足が地面から離れているかを判断しています。
歩いているか否かは立ち止まっておらず片足立ちでない場合、歩いていると判断しています。
歩いていた場合、Vector2.upをreturnしています。
現状は歩いてる、歩いてないのみなので、歩く速度の変化とかはないですね。
以下全体のソースコード(一部抜粋)です。 正直冗長な部分多いです。お恥ずかしい。
[SerializeField] private float threshold = 0.1f; [SerializeField] InputActionProperty _LeftFootMoveAction; public InputActionProperty leftFootMoveAction { get => _LeftFootMoveAction; set => SetInputActionProperty(ref _LeftFootMoveAction, value); } [SerializeField] InputActionProperty _RightFootMoveAction; public InputActionProperty rightFootMoveAction { get => _RightFootMoveAction; set => SetInputActionProperty(ref _RightFootMoveAction, value); } [SerializeField] public UnityEvent OnGround; [SerializeField] public UnityEvent OnIdentifyTracker; [SerializeField] private float stopJadgeTime = 1.5f; [SerializeField] private ReactiveProperty<bool> _isGround; [SerializeField] private bool _isOneLeg = false; [SerializeField] private bool _isStanding = false; [SerializeField] private bool walking = false; private CancellationTokenSource _cancellationTokenSourceStanding = new CancellationTokenSource(); private CancellationTokenSource _cancellationTokenSourceOneLeg = new CancellationTokenSource(); void Start() { _isGround.Value = true; _isGround .Subscribe(isGround => { if (isGround) { _cancellationTokenSourceOneLeg.Cancel(); _cancellationTokenSourceStanding = new CancellationTokenSource(); Standing(_cancellationTokenSourceStanding.Token).Forget(); } else { _cancellationTokenSourceStanding.Cancel(); _cancellationTokenSourceOneLeg = new CancellationTokenSource(); Walking(_cancellationTokenSourceOneLeg.Token).Forget(); } }).AddTo(this); } protected override Vector2 ReadInput() { var leftFootValue = _LeftFootMoveAction.action?.ReadValue<Vector3>() ?? Vector3.zero; var rightFootValue = _RightFootMoveAction.action?.ReadValue<Vector3>() ?? Vector3.zero; Vector2 footValueVector2 = new Vector2(); _isGround.Value = (Math.Abs(leftFootValue.y - rightFootValue.y) < threshold) ? true : false; walking = (!_isStanding && !_isOneLeg); footValueVector2 = walking ? Vector2.up : Vector2.zero; return footValueVector2; } async UniTask Standing(CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); _isOneLeg = false; OnGround.Invoke(); Debug.Log("ground"); await UniTask.Delay(TimeSpan.FromSeconds(stopJadgeTime),cancellationToken: cancellationToken); if (!cancellationToken.IsCancellationRequested) { _isStanding = true; Debug.Log("standing"); } } async UniTask Walking(CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); _isStanding = false; Debug.Log("walking"); await UniTask.Delay(TimeSpan.FromSeconds(stopJadgeTime),cancellationToken: cancellationToken); if (!cancellationToken.IsCancellationRequested) { _isOneLeg = true; Debug.Log("one leg"); } }
インスペクターはこんな感じ