本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。
今回はホロモンの身長に合わせて食事の大きさを変えるメモです。
ホロモンの身長に合わせて食事の大きさを変える
前回のホロモンの状態を記録するスクリプトと同じ手法で身体状態を保持するスクリプトを作成しました。
・HoloMonConditionBodySingleton.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using UniRx; using HMProject.HoloMon; using HMProject.HoloMonUtilities; namespace HMProject.HoloMonCondition { /// <summary> /// ホロモンのボディコンディションの定義 /// </summary> [Serializable] public class HoloMonBodyStatus { /// <summary> /// ホロモンの誕生日時 /// </summary> public DateTime BirthTime; /// <summary> /// ホロモンの日齢 /// </summary> public int AgeOfDay; /// <summary> /// ホロモンの大きさ /// </summary> public float BodyHeight; /// <summary> /// ホロモンのデフォルトの大きさ /// </summary> public float DefaultHeight; } /// <summary> /// ホロモンのボディコンディションを表すReactiveProperty /// </summary> [Serializable] public class HoloMonBodyStatusReactiveProperty : ReactiveProperty<HoloMonBodyStatus> { public HoloMonBodyStatusReactiveProperty() { } public HoloMonBodyStatusReactiveProperty(HoloMonBodyStatus a_HoloMonBodyStatus) : base(a_HoloMonBodyStatus) { } } public class HoloMonConditionBodySingleton : MonoBehaviour { /// <summary> /// 単一インスタンス /// </summary> private static HoloMonConditionBodySingleton ps_instance; /// <summary> /// 参照用インスタンス /// </summary> public static HoloMonConditionBodySingleton Instance { get { if (ps_instance == null && ps_searchForInstance) { ps_searchForInstance = false; var ps_instances = FindObjectsOfType<HoloMonConditionBodySingleton>(); if (ps_instances.Length == 1) { ps_instance = ps_instances[0]; } else if (ps_instances.Length > 1) { Debug.LogErrorFormat("Expected exactly 1 {0} but found {1}.", ps_instance.GetType().ToString(), ps_instances.Length); } } return ps_instance; } } private static bool ps_searchForInstance = true; /// <summary> /// ホロモンのボディコンディション /// </summary> [SerializeField, Tooltip("ホロモンのボディコンディション")] private HoloMonBodyStatusReactiveProperty p_HoloMonBodyStatus = new HoloMonBodyStatusReactiveProperty(); /// <summary> /// ホロモンのホロモンのボディコンディションのReadOnlyReactivePropertyの保持変数 /// </summary> private IReadOnlyReactiveProperty<HoloMonBodyStatus> p_IReadOnlyReactivePropertyHoloMonBodyStatus; /// <summary> /// ホロモンのホロモンのボディコンディションのReadOnlyReactivePropertyの参照変数 /// </summary> public IReadOnlyReactiveProperty<HoloMonBodyStatus> IReadOnlyReactivePropertyHoloMonBodyStatus => p_IReadOnlyReactivePropertyHoloMonBodyStatus ?? (p_IReadOnlyReactivePropertyHoloMonBodyStatus = p_HoloMonBodyStatus.ToSequentialReadOnlyReactiveProperty()); /// <summary> /// 時刻判定(分刻み)のトリガー /// </summary> IDisposable p_MinuteTimeTrigger; /// <summary> /// 誕生日時の設定 /// </summary> /// <param name="a_BirthTime"></param> private void ReceptionBirthTime(DateTime a_BirthTime) { // 変数の登録を行う // 非変更時は通知が発生しない HoloMonBodyStatus status = p_HoloMonBodyStatus.Value; // 状態の変化が発生するか否か if (status.BirthTime != a_BirthTime) { // 状態を設定する status.BirthTime = a_BirthTime; // 状態を更新する p_HoloMonBodyStatus.SetValueAndForceNotify(status); } } /// <summary> /// 日齢の変化 /// </summary> /// <param name="a_AgeOfDay"></param> private void ReceptionAgeOfDay(int a_AgeOfDay) { // 変数の登録を行う // 非変更時は通知が発生しない HoloMonBodyStatus status = p_HoloMonBodyStatus.Value; // 状態の変化が発生するか否か if (status.AgeOfDay != a_AgeOfDay) { // 状態を設定する status.AgeOfDay = a_AgeOfDay; // 状態を更新する p_HoloMonBodyStatus.SetValueAndForceNotify(status); } } /// <summary> /// 大きさの変化 /// </summary> /// <param name="a_BodyHeight"></param> private void ReceptionBodyHeight(float a_BodyHeight) { // 変数の登録を行う // 非変更時は通知が発生しない HoloMonBodyStatus status = p_HoloMonBodyStatus.Value; // ルールに基づいて値を四捨五入する a_BodyHeight = RoundHeight(a_BodyHeight); // 状態の変化が発生するか否か if (status.BodyHeight != a_BodyHeight) { // 状態を設定する status.BodyHeight = a_BodyHeight; // 状態を更新する p_HoloMonBodyStatus.SetValueAndForceNotify(status); } } /// <summary> /// デフォルトの大きさの変化 /// </summary> /// <param name="a_DefaultHeight"></param> private void ReceptionDefaultHeight(float a_DefaultHeight) { // 変数の登録を行う // 非変更時は通知が発生しない HoloMonBodyStatus status = p_HoloMonBodyStatus.Value; // ルールに基づいて値を四捨五入する a_DefaultHeight = RoundHeight(a_DefaultHeight); // 状態の変化が発生するか否か if (status.DefaultHeight != a_DefaultHeight) { // 状態を設定する status.DefaultHeight = a_DefaultHeight; // 状態を更新する p_HoloMonBodyStatus.SetValueAndForceNotify(status); } } /// <summary> /// 身長を記録する /// </summary> /// <param name="a_Height"></param> public void SetHeight(float a_Height) { // 身長を設定する ReceptionBodyHeight(a_Height); } /// <summary> /// ホロモンデータの設定 /// </summary> public void SetHoloMonData(HoloMonSaveData a_SaveData) { p_HoloMonBodyStatus.Value = a_SaveData.BodyStatus; } /// <summary> /// 開始処理 /// </summary> void Start() { // ボディコンディションを初期化 InitializeBodyStatus(); // 1分毎にトリガーを実行する p_MinuteTimeTrigger = Observable .Timer(TimeSpan.FromSeconds(60.0f - DateTime.Now.Second), TimeSpan.FromMinutes(1.0f)) .SubscribeOnMainThread() .Subscribe(x => { ChangeOverTimeCondition(DateTime.Now); }) .AddTo(this); } /// <summary> /// 定期処理 /// </summary> void Update() { } /// <summary> /// ボディコンディションを初期化する /// </summary> private void InitializeBodyStatus() { // 誕生日時を現在とする ReceptionBirthTime(DateTime.Now); // 日齢を0とする ReceptionAgeOfDay(0); // デフォルトの大きさを身長値に設定する ReceptionBodyHeight(0.3f); ReceptionDefaultHeight(0.3f); } /// <summary> /// 時間経過によるコンディション変化を管理する /// </summary> /// <param name="a_DateTime"></param> private void ChangeOverTimeCondition(DateTime a_DateTime) { Debug.Log("ChangeOverTimeCondition : " + a_DateTime.ToString()); // 日齢をチェックする CheckAgeOfDay(a_DateTime); } /// <summary> /// 日齢の変化をチェックする /// </summary> /// <param name="a_DataTime"></param> private void CheckAgeOfDay(DateTime a_DataTime) { // 誕生日時を取得する DateTime BirthTime = p_HoloMonBodyStatus.Value.BirthTime; // 現在の日齢を取得する int CurrentAgeOfDay = ActivityElapsedDays(BirthTime, a_DataTime); // 現在設定中の日齢と異なるか否か if (CurrentAgeOfDay != p_HoloMonBodyStatus.Value.AgeOfDay) { // 異なる場合は日齢を設定する ReceptionAgeOfDay(CurrentAgeOfDay); } } /// <summary> /// 指定時刻から指定時刻までの経過日数を返す /// </summary> /// <param name="a_StartTime"></param> /// <param name="a_EndTime"></param> /// <returns></returns> private int ActivityElapsedDays(DateTime a_StartTime, DateTime a_EndTime) { // 経過時刻を計算する TimeSpan SubtractionTime = a_EndTime - a_StartTime; // 日数を取得する int elapsedTotalDays = SubtractionTime.Days; return elapsedTotalDays; } /// <summary> /// 身長の値をルールに基づいて四捨五入する /// </summary> /// <returns></returns> private float RoundHeight(float a_Height) { float roundHeight = 0.0f; // 1m 未満の場合は小数点第三位を四捨五入する if (a_Height < 1.0f) { roundHeight = (float)Math.Round(a_Height, 2, MidpointRounding.AwayFromZero); } else { // 1m ~ 10m の場合は小数点第二位を四捨五入する if(a_Height < 10.0f) { roundHeight = (float)Math.Round(a_Height, 1, MidpointRounding.AwayFromZero); } // 10m 以上は小数点第一位を四捨五入する else { roundHeight = (float)Math.Round(a_Height, MidpointRounding.AwayFromZero); } } return roundHeight; } } }
一方でお肉モデルを管理するスクリプトにReactivePropertyをSubscribeして身長の変化に合わせてモデルのサイズを変更する機能を追加します。
・ItemFoodController.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UniRx; using HMProject.HoloMonCondition; namespace HMProject.Item { public class ItemFoodController : MonoBehaviour { /// <summary> /// コントロール対象 /// </summary> [SerializeField, Tooltip("コントロール対象")] private GameObject p_ControlObject; /// <summary> /// 投擲基準位置 /// </summary> [SerializeField, Tooltip("投擲基準位置")] private Transform p_ThrowPosition; /// <summary> /// NavMesh追跡ポイント /// </summary> [SerializeField, Tooltip("NavMesh追跡ポイント")] private Transform p_NavMeshTrackingPoint; /// <summary> /// デフォルト座標 /// </summary> [SerializeField, Tooltip("デフォルト座標")] private Vector3 p_DefaultWorldPosition; /// <summary> /// デフォルトサイズ /// </summary> [SerializeField, Tooltip("デフォルトサイズ")] private Vector3 p_DefaultLocalScale; /// <summary> /// 投擲時イベント /// </summary> [SerializeField, Tooltip("投擲時イベント")] private UnityEvent p_ThrowKickEvent; /// <summary> /// 開始処理 /// </summary> void Start() { // デフォルト座標を保存 p_DefaultWorldPosition = p_ControlObject.transform.position; // デフォルトスケールを保存 p_DefaultLocalScale = p_ControlObject.transform.localScale; // 初期状態は無効とする p_ControlObject.SetActive(false); // 身長設定変更時の処理を設定する HoloMonConditionBodySingleton.Instance.IReadOnlyReactivePropertyHoloMonBodyStatus .ObserveOnMainThread() .Subscribe(status => { ApplySizeCondition(status.DefaultHeight, status.BodyHeight); }) .AddTo(this); } /// <summary> /// 定期処理 /// </summary> void Update() { } /// <summary> /// オブジェクトリセット /// </summary> public void ResetObject() { // 慣性をリセットする p_ControlObject.GetComponent<Rigidbody>().velocity = Vector3.zero; // デフォルト位置に戻す p_ControlObject.transform.position = p_DefaultWorldPosition; // オブジェクトを無効化する p_ControlObject.SetActive(false); } /// <summary> /// 投げ渡す /// </summary> public void ThrowObject() { // オブジェクトを有効化する p_ControlObject.SetActive(true); // 投擲基準位置にオブジェクトを移動する p_ControlObject.transform.position = p_ThrowPosition.position + (p_ThrowPosition.up * 0.2f); // 投擲トランスフォームの正面方向を取得する Vector3 forward = p_ThrowPosition.forward * 100.0f; // 一旦、慣性をリセットする p_ControlObject.GetComponent<Rigidbody>().velocity = Vector3.zero; // 投擲を行う p_ControlObject.GetComponent<Rigidbody>().AddForce(forward); // イベント実行 p_ThrowKickEvent.Invoke(); } /// <summary> /// 現在のホロモンの身長に合わせて大きさを反映する /// </summary> /// <param name="a_DefaultHoloMonHeight"></param> /// <param name="a_CurrentHoloMonHeight"></param> private void ApplySizeCondition(float a_DefaultHoloMonHeight, float a_CurrentHoloMonHeight) { // デフォルトサイズからの変化比率を取得する float ratioHeight = a_CurrentHoloMonHeight / a_DefaultHoloMonHeight; // 現在のホロモンサイズに合わせた大きさを算出して設定する p_ControlObject.transform.localScale = p_DefaultLocalScale * ratioHeight; } } }
シーンを再生して動作を確認します。
ホロモンの身長に合わせて食事が徐々に大きくなっていきます。