本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。

今回はホロモンの頭を撫でるとホロモンが反応するメモです。
頭に触られたことを判定する
頭に触られたことを判定する方法には、今回は視界システムと同じロジックを用いました。
UniRx.TriggersのOnTriggerStayAsObservable()を用いて、コライダーのイベントをフレーム毎にまとめて処理することができます。
using UniRx;
using UniRx.Triggers;
<summary>
</summary>
void Start()
{
this.OnTriggerStayAsObservable()
.BatchFrame()
.ObserveOnMainThread()
.Subscribe(colliderlist =>
{
foreach (Collider collider in colliderlist)
{
}
})
.AddTo(this);

今回は1フレーム毎に処理を行っていますが、UniRxのOperatorを活用することで様々な条件でコライダーを処理できます。
bluebirdofoz.hatenablog.com
更に頭部中心座標から一定距離の位置にオブジェクトが近づくと、オブジェクトが頭に触れたと判定して対象オブジェクトの情報を Dictionary 型で収集します。
bluebirdofoz.hatenablog.com
頭を撫でる手がパーの形かどうかは以下のハンドトラッキングによる判定を用います。
bluebirdofoz.hatenablog.com
以下が一定距離内のオブジェクトを検出する実装コードです。
・TactileBodyRegister.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using HoloMonApp.ConditionSpace;
using UniRx;
using UniRx.Triggers;
using UnityEditor;
using Microsoft.MixedReality.Toolkit;
using Microsoft.MixedReality.Toolkit.SpatialAwareness;
namespace HoloMonApp.TactileBodySpace
{
[RequireComponent(typeof(SphereCollider))]
public class TactileBodyRegister : MonoBehaviour
{
<summary>
</summary>
[SerializeField, Tooltip("範囲内の触覚オブジェクトコレクション")]
private TactileBodyCollection p_TactileBodyCollection;
[SerializeField, Tooltip("触覚原点トランスフォーム")]
private Transform p_TactileRoot;
[SerializeField, Tooltip("空間認識レイヤー")]
int p_SpatialAwarenessLayer;
[SerializeField, Tooltip("触覚発見距離(ホロモン1m時のメートル単位)")]
private float p_ForcedDiscoveryDistance = 0.5f;
<summary>
</summary>
void Start()
{
IMixedRealitySpatialAwarenessMeshObserver SpatialAwarenessMeshObserver =
CoreServices.GetSpatialAwarenessSystemDataProvider<IMixedRealitySpatialAwarenessMeshObserver>();
p_SpatialAwarenessLayer = SpatialAwarenessMeshObserver.MeshPhysicsLayer;
this.OnTriggerStayAsObservable()
.BatchFrame()
.ObserveOnMainThread()
.Subscribe(colliderlist =>
{
bool isUpdate = false;
List<GameObject> objectList = new List<GameObject>();
foreach (Collider collider in colliderlist)
{
if (collider == null) continue;
GameObject stayObject = collider.gameObject;
if (stayObject)
{
bool isSeen = TouchTactileObject(stayObject);
if (isSeen)
{
bool isRegisted = RegistTactileObject(stayObject);
if (isRegisted)
{
objectList.Add(stayObject);
}
isUpdate = isUpdate || isRegisted;
}
}
}
bool isRemove = RemoveTactileObject(objectList);
isUpdate = isUpdate || isRemove;
})
.AddTo(this);
}
<summary>
</summary>
void Update()
{
p_TactileBodyCollection.CheckStatusUpdate();
}
<summary>
</summary>
private bool TouchTactileObject(GameObject a_TactileObject)
{
int layernumber = a_TactileObject.layer;
if (layernumber == p_SpatialAwarenessLayer)
{
return false;
}
Vector3 betweenVector = a_TactileObject.transform.position - p_TactileRoot.position;
float betweenDistance = betweenVector.magnitude;
Vector3 betweenDirection = betweenVector.normalized;
Vector3 headDirection = p_TactileRoot.rotation * Vector3.forward;
float diffAngle = Vector3.Angle(headDirection, betweenDirection);
float forcedDiscoveryDistance = p_ForcedDiscoveryDistance * CurrentHeightScale();
if (betweenDistance > forcedDiscoveryDistance)
{
return false;
}
return true;
}
<summary>
</summary>
private bool RegistTactileObject(GameObject a_TactileObject)
{
bool isResisted = false;
TactileObjectWrap tactileObjectWrap = new TactileObjectWrap(a_TactileObject);
if (tactileObjectWrap.ExistObjectFeatures())
{
bool isContain = p_TactileBodyCollection.ContainsKey(a_TactileObject);
if (!isContain)
{
p_TactileBodyCollection.Add(tactileObjectWrap);
}
isResisted = true;
}
return isResisted;
}
<summary>
</summary>
private bool RemoveTactileObject(List<GameObject> a_CheckObjectList)
{
bool isRemoved = p_TactileBodyCollection.RemoveWithoutList(a_CheckObjectList);
return isRemoved;
}
<summary>
</summary>
<returns></returns>
private float CurrentHeightScale()
{
float currentHeight = HoloMonConditionBodySingleton.Instance.
IReadOnlyReactivePropertyHoloMonBodyStatus.Value.BodyHeight;
return currentHeight;
}
}
}
実装コード
待機アクションのロジックに頭を撫でられたときのアクションを追加しました。
体に触れられた割込みが発生した際、その情報をチェックします。
頭部をパーの形の手で触れられていた場合、モーションを再生しています。
<summary>
</summary>
<param name="a_TaouchObject"></param>
private bool TouchHeadReaction(TactileObjectWrap a_TactileObjectWrap)
{
bool isProcessed = false;
if ((a_TactileObjectWrap.CurrentFeatures().ObjectUnderstandType == ObjectUnderstandType.FriendRightHand)
|| (a_TactileObjectWrap.CurrentFeatures().ObjectUnderstandType == ObjectUnderstandType.FriendLeftHand))
{
int HandStatusHash = a_TactileObjectWrap.CurrentFeatures().ObjectUnderstandDataInterface.StatusHash();
#if UNITY_EDITOR
if( HandStatusHash == (int)ObjectStatusHand.Hand_Pistol) HandStatusHash = (int)ObjectStatusHand.Hand_Par;
#endif
if (HandStatusHash == (int)ObjectStatusHand.Hand_Par)
{
p_ModeLogicCommon.ReferenceAnimation.SetReactionPose(ReactionPose.StrokingHead);
p_TempActionTrigger?.Dispose();
p_TempActionTrigger = p_ModeLogicCommon.ReferenceAnimation.AnimationTrigger
.OnStateUpdateAsObservable()
.Where(onStateInfo => !onStateInfo.Animator.IsInTransition(onStateInfo.LayerIndex))
.Where(onStateInfo => onStateInfo.StateInfo.normalizedTime > 0.95)
.Take(1)
.Subscribe(_ =>
{
p_ModeLogicCommon.ReferenceAnimation.SetReactionPose(ReactionPose.Nothing);
})
.AddTo(this);
isProcessed = true;
}
}
return isProcessed;
}
ObservableStateMachineTriggerを使ってUniRxでアニメーションの終了を検知しています。
bluebirdofoz.hatenablog.com
動作確認
シーンを再生して動作を確認します。

手で頭を撫でてあげるとホロモンが両手を挙げて反応しました。
