そういうのがいいブログ

アプリ個人開発 まるブログ

アプリ開発覚え書き

【Unity】mocopiとMetaQuest2を併用したモーションをUnity内に記録する方法

要件

・mocopi アプリバージョン 2.0.0
・Unity 2021.3.18f1
・VirtualMotionCapture(通称ばもきゃ) 0.57r1
・EasyVirtualMotionCaptureForUnity(略してEVMC4U) v4_0a
・EasyMotionRecorder v1.1.6

前提

Windows PCであること
・UnityでHumanoid形式の3Dモデルを扱ったことがあること

はじめに

3Dモデルに適用するモーションはmixamoやアセットストアで入手できますが
理想とするモーションがなかったりします。

モーションがない場合自作を考えるわけですが、
専用のアセットを使用しても作成はなかなか大変な状況です。
そこで今回、mocopiとMetaQuest2を使用し
自分の動きをモーション化していきました。

mocopi単体でもモーション取得は可能ですが
mocopiに下半身集中モードというモードがあったため
MetaQuest2と併用してのモーション取得に挑戦してみた記事になります。

なかなかつまづきポイントが多かったため備忘録として残しておきます。

使用したもの

PC以外で物理的に使用したものは次の3つです。
・mocopi(ソニーが提供するモバイルモーションキャプチャー)
・MetaQuest2
・PCとMetaQuest2をつなぐケーブル
 純正品は高いためこちらを購入(今のところ問題なく使えています。)
 USB-A 3.0 & Type C ケーブル【PR】

モーションを記録する概要

複数のガジェットとアプリケーションを使用するため
先に全体の流れを書きます。

1.mocopiとMetaQuest2のモーション情報を
 VirtualMotionCapture(以下、ばもきゃ)で合成する
2.ばもきゃからモーション情報をUnityへ送る
3.Unityでモーション情報を受け取り記録する

前準備

モーション記録を実現するために下記において前準備が必要です。
・mocopi
・MetaQuest2
・ばもきゃ
・Unity

mocopiの前準備

VR向け下半身優先モードを使用するための設定をします。

詳細はこちらです。
www.sony.net

「高度な機能の有効化」の項目は
mocopiアプリを起動した直後の右上のボタンから設定できます。
センサーのキャリブレーション後には項目が出てこないため注意です。

まだ設定が残っていますがこのタイミングでは一旦終了です。

MetaQuest2の前準備

MetaQuest2はPCと連携し
MetaQuest2からSteamVRを起動することで
ラッキング情報をばもきゃに送ることができます。

これらを実現するために次の内容が必要です。
1.Oculusアプリの取得
2.MetaQuest2とPCをUSBケーブルで接続しQuest Linkする
3.Oculusアプリの設定変更
4.SteamVRをインストールする

1.Oculusアプリの取得

公式サイトで「ソフトウェアをダウンロード」からアプリケーションを取得します。
https://www.meta.com/jp/ja/quest/setup/

2.MetaQuest2とPCをUSBケーブルで接続しQuest Linkする

ダウンロードしたアプリケーションを起動し
MetaQuest2とPCをUSBケーブルで接続します。

3.Oculusアプリの設定変更

MetaQuest2からSteamVRを起動するための設定を行います。
Settings→一般→提供元不明にチェックをいれます。

4.SteamVRをインストールする

SteamVRをインストールします。(Steamのアカウントが必要です。)
store.steampowered.com

ばもきゃの前準備

VirtualMotionCapture(通称ばもきゃ)のモーション送信機能は
PIXIV FANBOXでの支援者がダウンロードできるバージョンになります。

支援者となりモーション送信機能が付いたバージョンを入手しましょう。
(最低300円です。開発者様に感謝) akira.fanbox.cc

Unityの前準備

Untiyでの前準備は次の3つです。
1.unitypackageのダウンロード/Unityへのインポート
2.VRMファイルのインポート
3.Untiyでの設定

1.unitypackageのダウンロード/Unityへのインポート

Unityで3Dプロジェクトを新規作成し次の2つのunitypackageをインポートします。

・EasyMotionRecorder
受信したモーションを保存するもの

github.com


・EasyVirtualMotionCaptureForUnity
ばもきゃとUnityをつなぐもの

入手先1
github.com 入手先2
booth.pm

EasyVirtualMotionCaptureForUnityをインポートすることで
UnityでVRMファイルを扱えるUniVRMも一緒にインポートされます。

2.VRMファイルのインポート

ニコニ立体やVRoid Studio、VRoid Hub等で入手した
動かしたいVRMファイルをUnityに入れます。

VRMのモデルデータにはVRM0とVRM1.0のバージョンがありますが
今回インポートに対応しているのはVRM0のみになります。

3.Untiyでの設定

以下をヒエラルキー上にセットしてください
・インポートしたVRMモデル

・EVMC4U>ExternalRecieverのプレハブ

・EasyMotionRecorder>Prefabs>EasyMotionRecorderのプレハブ

ヒエラルキー上にセットした2つのオブジェクトの設定を行います。

ExternalRecieverのインスペクター
VRMモデルのGameObject」欄に
VRMオブジェクトを割り当てます。

EasyMotionRecorderのインスペクター
「アニメーター」欄にVRMオブジェクトを割り当てます。

以上で前準備完了です。

モーション記録方法

モーション記録の一連の流れは以下となります。
1.前準備で設定したUnityのプロジェクトを開いておく
2.mocopiを下半身モードでキャリブレーションする
3.MetaQuest2を起動しQuest Linkを有効化する
4.Quest上でSteamVRを起動する
5.ばもきゃを起動する
6.mocopiとばもきゃの設定をする
7.Unityで実行する
8.モーションを録画する

1.前準備で設定したUnityのプロジェクトを開いておく

2.mocopiを下半身優先モードでキャリブレーションする

www.sony.net

3.MetaQuest2を起動しQuest Linkを有効化する

設定→システム→Quest Link→「Quest Linkを起動」のスイッチをON

参考 yuushablog.info

4.Quest上でSteamVRを起動する

Questメニューの「ディスプレイ」をでPCの画面を表示
→SteamVRを起動

SteamVRを起動できれば良いので起動方法は他の方法でも問題ありません。

5.ばもきゃを起動する

Quest上もしくはPCからばもきゃを起動する

6.mocopiとばもきゃの設定をする

下記の内容で設定します。

vmc.info

また、ばもきゃ側でOSCでモーション送信を有効にするにチェックをいれます。

7.Unityで実行する

Unityのヒエラルキー上に設置されている
ExternalReceiverのインスペクターの「ポート」が
ばもきゃ側と同じ数字になっているか確認する

ExternalReceiverのインスペクター

ばもきゃ

この状態で実行することで自分の動きがUnityのモデルに伝わります。

8.モーションを録画する

あとは録画するだけです。
ゲームビューが選択されている状態で
「R」キーを押すことで録画開始、
「X」キーを押すことで録画終了となります。

Xキーを押してもUnityの実行状態が終了するわけではないため注意してください。
また、Xキーを押して録画を終了しないと録画が完了しない点も注意です。

モーションのアニメーションデータ化

録画したモーションデータは
Unity上のAssets→Resourcesフォルダ内に入っています。
モーションデータのインスペクター右上のボタンを押し
「Export as Humanoid animation clips」を押してアニメーションのデータにします。

あとは編集が必要がであれば適宜アニメーション編集を実施してください。

mocopiとMetaQuest2を併用する メリットデメリット

mocopiとMetaQuest2を併用して感じたメリットデメリットは以下です。

メリット
・腕が上に真っ直ぐ上がるようになる
 mocopi単体では腕をまっすぐ上に上げても
 肘は曲がったままで手を上に上げた形になってしまいましたが
 腕が上に真っ直ぐ上がるようになりました。

・膝の曲げ伸ばしがより自然になる
 腕に付けていたmocopiのセンサーを膝上に移動するため精度が上がります。

デメリット
・遅延が発生した場合、動きを連動させるのが難しい
 mocopiはスマホorタブレットから送信するためか1秒程度遅れて反映されます。
 一方、MetaQuest2が担う頭、腕の動きは遅延は感じません。
 そのため、腕と下半身が連動する動きをすると下半身だけ遅れてしまいます。
 (接続の環境によると思います。)

 

おわりに

SteamVRを起動せずともトラッカーが反応してほしいところですね。
よくわからず進めたところもあるのでもっと簡単な方法がありましたら教えてください。

参考

note.com

【Unity】All In 1 Sprite Shader でAndroidとiOSで動的に設定を切り替えられない時の対処法

要件

Unity2022.3.16f1
All In 1 Sprite Shader:3.7

はじめに

スプライトの見栄えを簡単に良くすることができるアセット
All In 1 Sprite Shader

こちらの設定を動的に変更した設定で実機確認をしたところ
AndroidiOSでは設定した内容が無視されました。(Unityエディター上では動く)

本記事は対処法のメモになります。

スクリプト例はUniTaskを使用しています。)

うまくいかなかった方法

シェーダーの設定をスクリプトから直接操作
→Unityエディター上では動くが、Andorid,iOSでは動かない
 エラーも何もなく無視されている様子

    private void Start()
    {
        spriteRenderer = gameObject.GetComponent<SpriteRenderer>();
    }

    public async void PlayDamageEffect()
    {
        // 発光処理
        spriteRenderer.material.EnableKeyword("GLOW_ON");
        spriteRenderer.material.SetFloat("_Glow", 0f);
        spriteRenderer.material.SetFloat("_GlowGlobal", 3.0f);

        // エフェクトを一定時間後に元に戻す
        await UniTask.Delay(TimeSpan.FromSeconds(0.04f));
        spriteRenderer.material.DisableKeyword("GLOW_ON");
    }

うまくいった方法

次の2ステップで実現できました。
1.Unity上で再現したいアニメーションを作成
2.作成したアニメーションをスクリプトから操作

1.Unity上で再現したいアニメーションを作成

良質な記事がネット上にたくさんありますので作成方法は省略します。

アセット製作者様の動画が参考になります。
youtu.be

2.作成したアニメーションをスクリプトから操作

以下のようなスクリプトでアニメーションを再生します。
攻撃を受けたときに1回だけ光らせたい場合のスクリプトになります。

    private Animator animator; // Animatorコンポーネント
    [SerializeField] private AnimationClip glowAnimationClip;//操作するアニメーションクリップの名前取得用
    private string glowAnimationClipName;  //操作するアニメーションクリップの名前

    private void Start()
    {        
        animator = gameObject.GetComponent<Animator>();
        animator.speed = 0;
        glowAnimationClipName = glowAnimationClip.name;
    }

    public async void PlayDamageEffect()
    {
        //アニメーションクリップの名前,対象のレイヤー -1でアクティブなレイヤーを指定,開始時間
        animator.Play(glowAnimationClipName, -1, 0f);
        animator.speed = 1; // 通常速度で再生

        // アニメーションの長さを取得
        float clipLength = GetAnimationClipLength(glowAnimationClipName);

        // アニメーション時間分待機
        await UniTask.Delay(TimeSpan.FromSeconds(clipLength));

        // アニメーションを再生することで初期状態に戻しアニメーション停止
        animator.Play(glowAnimationClipName, -1, 0f);
        animator.speed = 0; 
    }

    //アニメーションの時間を取得
    float GetAnimationClipLength(string clipName)
    {
        AnimationClip[] clips = animator.runtimeAnimatorController.animationClips;
        foreach (AnimationClip clip in clips)
        {
            if (clip.name == clipName)
                return clip.length;
        }
        return 0f;
    }

アニメーションのスクリプト操作についてはもっと良い方法があるはずです。

おわりに

All In 1 Sprite Shaderで動的に変更させたい場合は
アニメーションを使用する必要がある!

お伝えしたいのはこれだけです。
収穫がありましたら幸いです。

参考

All In 1 Sprite Shadeの使い方
kan-kikuchi.hatenablog.com


Unityフォーラム forum.unity.com

【Unity】iOSビルド時エラー xcrun: error: SDK "iphoneos" cannot be located に対応した方法

要件

Unity2022.3.16f1

はじめに

UnityにてプラットフォームをiOSにしてビルドしたところ
xcrun: error: SDK "iphoneos" cannot be located
というエラーが発生しました。

対応した方法をメモしておきます。

対応方法

対応は簡単でXcodeの設定を変更するだけでした。
Xcodeのアプリケーションを開き

左上のメニュー
Xcode
→Settings


→Locations
→Command Line Toolsで使用するXcodeバージョンを選択します。

以上を設定することでエラーは発生しなくなりました。

この記事がお役にたてましたら幸いです。

本書きました

marumaro7.hatenablog.com

【Unity】iOSビルド時エラー Cannot read BuildLayout header, BuildLayout has not open for a file に対応した方法

要件

Unity2022.3.16f1 (Silicon)
Addressables 1.21.19

はじめに

iOSでのビルド時に
「Cannot read BuildLayout header, BuildLayout has not open for a file」
というエラーが出ました。

根本原因はわかりませんがエラーが出なくなる方法がわかったのでメモしておきます。

方法

方法は次の2ステップです。
1.一時ファイルを削除する
2.クリーンビルドする


1.一時ファイルを削除する

Addressables Reportのウインドウが表示されていたら閉じておきます。
そして、Unityを閉じます。


該当するプロジェクトフォルダのLibraryフォルダから
次の2つを削除します。
・AddressablesConfig.datファイル
・com.unity.addresaablesフォルダ


2.クリーンビルドする

該当プロジェクトを開き、
ビルドボタンの横の▼ボタンを押した時に出てくる
「Clean Build...」を押してビルドします。


クリーンビルドをすることでエラーは出なくなるはずです。
しかし、もう一度続けてクリーンビルドしてもエラーは再発してしまいます。

(もう一度一時ファイルの削除から実施するとエラーは消えます。)

現場からは以上です。

【Unity】MVPパターン・インターフェース・VContainer・UniRxを用いてスクリプトを書いてみる

環境

Unity2022.3.15f1
VContainer 1.13.2
UniRx 7.1.0

はじめに

ボタンをクリックするとテキストの数字が増えるという内容で
以下の順番でスクリプトを変更していきました。

1.MVPパターンで構成
2.インターフェースを用いてロジックを差し替え可能にする
3.VContainerを用いた書き方
4.UniRxを用いた書き方

本記事は書き方がどのように変化するのか、
疑問点はどこかを自分の中で整理するために書いています。

※拙著を読まれた前提で書いています。
読まれた方は、インターフェースの差し替えまでは理解できると思いますが
VContainerとUniRxは別途勉強が必要です。(参考の記事を参照下さい) marumaro7.hatenablog.com

普通に書く

まずは一般的な書き方でスクリプトを書いてみます。
以下のスクリプトを用意します。

using UnityEngine;
using UnityEngine.UI;

public class CountManager: MonoBehaviour
{
    private int count = 0;
    public Text textX;

    public void PushCountButton()
    {
        count++;
        textX.text = count.ToString();
    }
}

空のオブジェクトにスクリプトを割り付けて
テキストを割り当て、ボタンに関数を設定します。
こちらは特に問題ないと思います。


1.MVPパターン

役割を分ける書き方です。
・Model(計算などのロジック部分)
・View(表示部分の処理)
・Presenter(ViewとModelを繋げる)

3つのスクリプトを用意した後、
初期化処理を書いてスクリプトを動かします。



View

using System;
using UnityEngine;
using UnityEngine.UI;

public class CountView : MonoBehaviour
{
    public Text counterText;
    public Button countButton;

    public event Action OnIncrementButtonClicked;

    void Start()
    {
        countButton.onClick.AddListener(IncrementButtonClicked);
    }

    private void IncrementButtonClicked()
    {
        OnIncrementButtonClicked?.Invoke();
    }

    public void SetText(int value)
    {
        counterText.text = value.ToString();
    }

    private void OnDestroy()
    {
        countButton.onClick.RemoveListener(IncrementButtonClicked);
    }

}


Model

using System;

public class CountModel
{
    private int _count;

    public int Count
    {
        get
        {
            return _count;
        }
        private set
        {
            _count = value;
            OnCountChange?.Invoke(_count);
        }
    }

    public event Action<int> OnCountChange;

    public void IncrementCount()
    {
        Count++;
    }
}


Presenter

public class CountPresenter
{
    public CountPresenter(CountModel countModel, CountView countView)
    {
        //ビューのイベントにモデルの関数を登録
        countView.OnIncrementButtonClicked += countModel.IncrementCount;

        //モデルのイベントにビューの関数を登録
        countModel.OnCountChange += countView.SetText;
    }
}


初期化処理

using UnityEngine;

public class CountInitializer : MonoBehaviour
{
    [SerializeField] private CountView countView;

    void Start()
    {
        CountModel countModel = new CountModel();
        new CountPresenter(countModel, countView);
    }
}


設定方法

2.インターフェースを用いてロジックを差し替え可能にする

次にインターフェースを用いてロジック部分を差し替え可能にします。

(下図はModelとViewの位置が先程の図と逆になってしまっています。)


View

using System;
using UnityEngine;
using UnityEngine.UI;

public class CountView2 : MonoBehaviour
{
    public Text counterText;
    public Button countButton;

    public event Action OnIncrementButtonClicked;

    void Start()
    {
        countButton.onClick.AddListener(IncrementButtonClicked);
    }

    private void IncrementButtonClicked()
    {
        OnIncrementButtonClicked?.Invoke();
    }

    public void SetText(int value)
    {
        counterText.text = value.ToString();
    }

    private void OnDestroy()
    {
        countButton.onClick.RemoveListener(IncrementButtonClicked);
    }

}


Model

using System;

public class CountModel2 : ICountModel
{
    private int _count;

    public int Count
    {
        get
        {
            return _count;
        }
        private set
        {
            _count = value;
            OnCountChange?.Invoke(_count);
        }
    }

    public event Action<int> OnCountChange;

    public void IncrementCount()
    {
        Count++;
    }
}


インターフェース

using System;

public interface ICountModel
{
    int Count { get; }
    event Action<int> OnCountChange;
    void IncrementCount();
}


Presenter

public class CountPresenter2
{
    public CountPresenter2(ICountModel countModel, CountView2 countView)
    {
        //ビューのイベントにモデルの関数を登録
        countView.OnIncrementButtonClicked += countModel.IncrementCount;

        //モデルのイベントにビューの関数を登録
        countModel.OnCountChange += countView.SetText;
    }
}


初期化処理

using UnityEngine;

public class CountInitializer2 : MonoBehaviour
{
    [SerializeField] private CountView2 countView;

    void Start()
    {
        ICountModel countModel = new CountModel2();
        new CountPresenter2(countModel, countView);
    }
}



新たにCountModelXを作成し、
CountModel2をCountModelXに差し替えたい場合


Model

using System;

public class CountModelX : ICountModel
{
    private int _count;

    public int Count
    {
        get
        {
            return _count;
        }
        private set
        {
            _count = value;
            OnCountChange?.Invoke(_count);
        }
    }

    public event Action<int> OnCountChange;

    public void IncrementCount()
    {
        Count++;
        Count++;
    }
}


下記のように変更するだけでロジックの差し替えが可能となります。
初期化処理

using UnityEngine;

public class CountInitializer2 : MonoBehaviour
{
    [SerializeField] private CountView2 countView;

    void Start()
    {
      //ICountModel countModel = new CountModel2(); 変更前
        ICountModel countModel = new CountModelX();//変更後
        new CountPresenter2(countModel, countView);
    }
}

3.VContainerを用いた書き方

VContainerを使用して書いてみます。

marumaro7.hatenablog.com

これまで初期化処理を書いていましたが。初期化内容の予約を書く形になります。

DIコンテナについていくつもの記事を見て理解しようとしましたが
理解しきれていません。
理解しきれていないながらも書いています。

「参考」にわかりやすかった記事を載せておきます。


View

using System;
using UnityEngine;
using UnityEngine.UI;

public class CountView3 : MonoBehaviour
{
    public Text counterText;
    public Button countButton;

    public event Action OnIncrementButtonClicked;

    void Start()
    {
        countButton.onClick.AddListener(IncrementButtonClicked);
    }

    private void IncrementButtonClicked()
    {
        OnIncrementButtonClicked?.Invoke();
    }

    public void SetText(int value)
    {
        counterText.text = value.ToString();
    }

    private void OnDestroy()
    {
        countButton.onClick.RemoveListener(IncrementButtonClicked);
    }

}


Model

using System;

public class CountModel3 : ICountModel
{
    private int _count;

    public int Count
    {
        get
        {
            return _count;
        }
        private set
        {
            _count = value;
            OnCountChange?.Invoke(_count);
        }
    }

    public event Action<int> OnCountChange;

    public void IncrementCount()
    {
        Count++;
    }
}


インターフェース

using System;

public interface ICountModel
{
    int Count { get; }
    event Action<int> OnCountChange;
    void IncrementCount();
}


Presenter

using UnityEngine;
using VContainer;
using VContainer.Unity;

public class CountPresenter3 :IInitializable
{
    private ICountModel _countModel;
    private CountView3 _countView;

    [Inject]
    public CountPresenter3(ICountModel countModel, CountView3 countView)
    {
        _countModel = countModel;
        _countView = countView;
    }

    public void Initialize()
    {
        Debug.Log("Presenter.Initialize");

        //ビューのイベントにモデルの関数を登録
        _countView.OnIncrementButtonClicked += _countModel.IncrementCount;

        //モデルのイベントにビューの関数を登録
        _countModel.OnCountChange += _countView.SetText;
    }


}


初期化内容の予約

using VContainer;
using VContainer.Unity;

public class CountLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        // CountPresenter3をエントリーポイントとして登録。
        // これにより、VContainerはアプリケーションの起動時にCountPresenter3を初期化します。
        builder.RegisterEntryPoint<CountPresenter3>();

        // ICountModelのインターフェースにCountModel3の実装をシングルトンとして関連付ける。
        // これはアプリケーション全体で一つのCountModel3インスタンスが共有されることを意味します。
        builder.Register<ICountModel,CountModel3>(Lifetime.Singleton);

        // CountView3コンポーネントをUnityの階層から検索し、VContainerに登録します。
        // これにより、VContainerはUnityの階層にあるCountView3インスタンスを見つけ出し、依存関係を注入できます。
        builder.RegisterComponentInHierarchy<CountView3>();
    }
}

設定方法
空のオブジェクトを作成しCountLifetimeScopeを割り当てます。

Viewのスクリプト設定内容はこれまでと変化ありません。

ここまでの内容だけですとスクリプトが複雑になっただけで
VContainerを使うメリットは感じられません。

恩恵を受けるのは下記のように複数のPresenterで
ICountModelを使う場合と考えています。

CountPresenterXとCountPresenterYを追加した場合を考えます。
(どのPresenterもCountView3を使っており
 そんなシチュエーションある??という感じですが例としてご容赦を・・・)


追加のPresenter:CountPresenterX (内容はCountPresenter3と同じ)

using UnityEngine;
using VContainer;
using VContainer.Unity;

public class CountPresenterX : IInitializable
{
    private ICountModel _countModel;
    private CountView3 _countView;

    [Inject]
    public CountPresenterX(ICountModel countModel, CountView3 countView)
    {
        _countModel = countModel;
        _countView = countView;
    }

    public void Initialize()
    {
        Debug.Log("Presenter.Initialize");

        //ビューのイベントにモデルの関数を登録
        _countView.OnIncrementButtonClicked += _countModel.IncrementCount;

        //モデルのイベントにビューの関数を登録
        _countModel.OnCountChange += _countView.SetText;
    }

}


追加のPresenter:CountPresenterY (内容はCountPresenter3と同じ)

using UnityEngine;
using VContainer;
using VContainer.Unity;

public class CountPresenterY : IInitializable
{
    private ICountModel _countModel;
    private CountView3 _countView;

    [Inject]
    public CountPresenterY(ICountModel countModel, CountView3 countView)
    {
        _countModel = countModel;
        _countView = countView;
    }

    public void Initialize()
    {
        Debug.Log("Presenter.Initialize");

        //ビューのイベントにモデルの関数を登録
        _countView.OnIncrementButtonClicked += _countModel.IncrementCount;

        //モデルのイベントにビューの関数を登録
        _countModel.OnCountChange += _countView.SetText;
    }

}


初期化内容の予約:コード追加

using VContainer;
using VContainer.Unity;

public class CountLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        // CountPresenter3, CountPresenterX, CountPresenterYをエントリーポイントとして登録。
        // VContainerはこれらのクラスをアプリケーション起動時に初期化します。
        builder.RegisterEntryPoint<CountPresenter3>();
        builder.RegisterEntryPoint<CountPresenterX>();//追加
        builder.RegisterEntryPoint<CountPresenterY>();//追加

        // ICountModelインターフェースに対してCountModel3の実装をシングルトンとして関連付けます。
        // アプリケーション全体で1つのCountModel3インスタンスが共有されます。
        builder.Register<ICountModel, CountModel3>(Lifetime.Singleton);

        // Unityの階層からCountView3コンポーネントを検索し、VContainerに登録します。
        // VContainerはUnity階層内のCountView3インスタンスを見つけ出し、依存関係を注入します。
        builder.RegisterComponentInHierarchy<CountView3>();
    }
}


CountPresenter3, CountPresenterX, CountPresenterにて
ICountModelはCountModel3を参照するよう指定している状況です。

ここからCountModelXを参照するよう指定したい時に
CountModel3からCountModelXへ一行のコードを変更することで
各Presenterへ設定する内容を一挙に変更することができます。

using VContainer;
using VContainer.Unity;

public class CountLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        // CountPresenter3, CountPresenterX, CountPresenterYをエントリーポイントとして登録。
        // VContainerはこれらのクラスをアプリケーション起動時に初期化します。
        builder.RegisterEntryPoint<CountPresenter3>();
        builder.RegisterEntryPoint<CountPresenterX>();
        builder.RegisterEntryPoint<CountPresenterY>();

        // ICountModelインターフェースに対してCountModelXの実装をシングルトンとして関連付けます。
        // アプリケーション全体で1つのCountModelXインスタンスが共有されます。
      //builder.Register<ICountModel, CountModel3>(Lifetime.Singleton);//変更前
        builder.Register<ICountModel, CountModelX>(Lifetime.Singleton);//変更後

        // Unityの階層からCountView3コンポーネントを検索し、VContainerに登録します。
        // VContainerはUnity階層内のCountView3インスタンスを見つけ出し、依存関係を注入します。
        builder.RegisterComponentInHierarchy<CountView3>();
    }
}


私の開発規模で恩恵を受けるシチュエーションがイメージできないため
無理して使わなくてもよいかなと考えています。


4.UniRxを用いた書き方

UniRxを使用し数値の変更を検出、関数を実行するように書き換えます。
(導入手順は省略)

ViewとPresenterの内容が先程のコードと変更になります。

スクリプト間の構成としては変化ありません。


View

using UnityEngine;
using UnityEngine.UI;
using UniRx;

public class CountView4 : MonoBehaviour
{
    public Text counterText;
    public Button countButton;

    // UniRxのReactiveCommandを使用
    public ReactiveCommand OnIncrementButtonClicked = new ReactiveCommand();


    void Start()
    {
        // ButtonのonClickイベントをUniRxのObservableに変換
        countButton.onClick.AsObservable()
            .Subscribe(_ => OnIncrementButtonClicked.Execute())
            .AddTo(this); // このオブジェクトが破棄された時に購読を自動的に解除
    }

    public void SetText(int value)
    {
        counterText.text = value.ToString();
    }
}


Model

using System;

public class CountModel4 : ICountModel
{
    private int _count;

    public int Count
    {
        get
        {
            return _count;
        }
        private set
        {
            _count = value;
            OnCountChange?.Invoke(_count);
        }
    }

    public event Action<int> OnCountChange;

    public void IncrementCount()
    {
        Count++;
    }
}


インターフェース

using System;

public interface ICountModel
{
    int Count { get; }
    event Action<int> OnCountChange;
    void IncrementCount();
}


Presenter

using UnityEngine;
using VContainer;
using VContainer.Unity;
using UniRx;

public class CountPresenter4 :IInitializable
{
    private ICountModel _countModel;
    private CountView4 _countView;

    [Inject]
    public CountPresenter4(ICountModel countModel, CountView4 countView)
    {
        _countModel = countModel;
        _countView = countView;
    }

    public void Initialize()
    {
        Debug.Log("Presenter.Initialize");

        // UniRxを使用してイベントを購読
        _countView.OnIncrementButtonClicked.Subscribe(_ =>
        {
            _countModel.IncrementCount();
        }).AddTo(_countView);

        // モデルのイベントにビューの関数を登録
        _countModel.OnCountChange += _countView.SetText;
    }


}


初期化内容の予約

using VContainer;
using VContainer.Unity;

public class CountUniRxLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        builder.RegisterEntryPoint<CountPresenter4>();
        builder.Register<ICountModel,CountModel4>(Lifetime.Singleton);
        builder.RegisterComponentInHierarchy<CountView4>();
    }
}


設定方法
VContainerを用いた書き方と変化ありません。

おわりに

今回、VContainerとUniRxについては調べながら書いてみました。
これらはプロジェクトの規模が大きいほど効果を発揮すると感じます。

私の場合ですと勉強コストと導入効果が釣り合わない感覚があったため、
基本は「MVPパターン+時々インターフェース」の内容で作り、
どうしても必要になったらVContainerやUniRxの導入を検討する
というスタンスにすることにしました。

時間をかけて調べた割に結局使わない決断をしたわけですが、
自分の中で設計スタンスが決定したので良しとします。

なにか得るものがありましたら幸いです。

参考

UniRxの情報はネットに豊富にあるため
VContainerやDI、DIコンテナについての記事が中心です。

note.com

shuhelohelo.hatenablog.com

xrdnk.hateblo.jp

backpaper0.github.io

qiita.com

【Unity】VContainerの導入手順

環境

Unity 2022.3.15f1
VContainer 1.13.2 (Unity 2018.4以上のバージョンが必要)

はじめに

VContainerの導入メモです。

導入手順

パッケージマネージャーの設定画面を開きます。
→編集
→プロジェクト設定
→パッケージマネージャー


必要事項を入力します。
名前: VContainer(名前はなんでもよい)
URL:https://package.openupm.com
Scope(s):jp.hadashikick.vcontainer
→保存


VContainerをインストールします。
→ウインドウ
→パッケージマネージャー
→ マイレジストリ
→VContainerを選択 →インストール


インストールが完了すると
プロジェクトウインドウのPackagesに
VContainerが追加されていることが確認できます。

参考

www.youtube.com

vcontainer.hadashikick.jp

VSCode(VisualStudioCode)でPlantUMLを使うための環境構築手順

環境

Apple M2 Ultra
macOS Ventura 13.4

はじめに

VSCode(VisualStudioCode)でPlantUMLを使うための環境構築 の実施メモになります。

コマンドはターミナルを開いて行います。

1.Homebrew をインストール

基本はこちら参照 zenn.dev


HomebrewのPATHを通す際はこちらも参照 qiita.com


メモ PATHを通すコマンドを打った後は特に何も起きない


2.Javaをインストール

ターミナルに下記を入力してインストールします。

brew install --cask adoptopenjdk


3.VSCode(VisualStudioCode)をインストール

公式ページからインストールします。

下記を参照

www602.math.ryukoku.ac.jp


4.Graphviz をインストール

ターミナルに下記を入力してインストールします。(数分かかります)

brew install graphviz


PlantUMLのプラグインをインストール

VSCode(VisualStudioCode)を起動し
→左側の「拡張機能
→検索窓に「PlantUML」と入力
→PlantUMLをクリック
→インストール

インストールが完了したらVSCodeを再起動します。


使い方

細かいことは抜きにしてとりあえずクラス図を表示する方法です。

ファイルを新規作成します。
 ファイル→新しいファイル

ファイル名を入力するウインドウが出てきますので
○○.plantumlと名前を付けます。

"○○"は好きな名前を入力します。(今回はTestTestとしました.。)

".plantuml"の部分は「.puml」でも良いようです。

エンターキーを押し、ファイルを保存する場所を決定したら
PlantUML の文法に従って、テキストベースでクラス図を記述します。


サンプルコード

@startuml
class Testクラス {
    +String 変数
    +void 関数()
}
@enduml

サンプルコードを入力した後

Option + dを押すことで右側にプレビュー画面が表示されます。

プレビュー画面 エラー対応

私の場合、プレビュー画面の左上に!マークが表示されていました。
内容はTimesのフォントがないとのことですので
素直にフォントをインストールします。

フォント追加手順
0.VSCodeを閉じる


1.フォントダウンロード
www.freebestfonts.com


2.ダウンロードしたフォントデータをクリック


3.Font Bookが勝手に開くのでインストール

VSCodeを開いてプレビューを確認すると
エラーが出なくなっています。


参考

guinpen98.github.io

toronavi.com

qiita.com