【Unity】VContainerを使ったHPバー
Zenjectより軽量かつ高パフォーマンスと謳われているVContainerを使ってみました。
視覚的にわかりやすいように、ゲームでよく使われる HPバーを作ってみます。
環境
- Unity 2021.2.7f1
- VContainer 1.9.1
- UniRx 7.1.0
VContainer を使った HP バーサンプル

それでは具体的な実装方法を解説していきます。
実装するクラス一覧
Model
- IHealthPoint
- HealthPoint
Presenter
- HealthPointPresenter
View
- HealthPointView
DIContainer
- TestLifeTimeScope
Model の実装
Model は Unity に依存させたくないため、アセンブリを切ります。
アセンブリは、Project ビュー内で左上の+ボタンから Assembly Definition
を選択すると作成できます。

UniRx は使いたいため参照させておきます。 こうすることで強制的に Unity
から分離できます。 注意点としては、 Unity の Mathf が使えなくなります。
そのため、Mathfを自作するか、そもそもアセンブリを切らないというのもありだと思います。

Model の interface は以下のようにしておきます。
public interface IHealthPoint
{
void Damage(int damageNum);
void Heal(int healNum);
IReadOnlyReactiveProperty<int> HpRP { get; }
}
実体化させた HealthPoint の解説に関しては省略いたします。
ソースコード
View の実装
ボタンのイベント登録には Unity の AddListener は使わず、UniRx
の OnClickAsObservable を使用します。 拡張性に優れていて、UnityAction
型を登録しなくてもいいのがメリットです。
public class HealthPointView : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI _hpText;
[SerializeField] private Slider _hpSlider;
[SerializeField] private Button _damageButton;
[SerializeField] private Button _healButton;
public IObservable<Unit> OnClickDamageButton() => _damageButton.OnClickAsObservable();
public IObservable<Unit> OnClickHealButton() => _healButton.OnClickAsObservable();
public void SetHP(int hp)
{
_hpText.text = hp.ToString();
_hpSlider.value = hp;
}
}
適当なゲームオブジェクトにアタッチして、Slider やボタンの参照を
inspector から設定しておきます。
Presenter の実装
IInitializable を実装することで、VContainerのライフサイクルイベントを与えることができます。
どのタイミングで初期化したいのかは、継承するインターフェースで決めることができます。
タイミング一覧に関してはこちら
public class HealthPointPresenter : IInitializable
{
private readonly IHealthPoint _healthPoint;
private readonly HealthPointView _healthPointView;
public HealthPointPresenter(IHealthPoint healthPoint, HealthPointView healthPointView)
{
_healthPoint = healthPoint;
_healthPointView = healthPointView;
}
/// <summary>
/// 初期化時に呼ばれる
/// </summary>
public void Initialize()
{
_healthPoint.HpRP.Subscribe(hp => _healthPointView.SetHP(hp));
// ボタンに処理を登録する
_healthPointView.OnClickDamageButton().Subscribe(_ => _healthPoint.Damage(10));
_healthPointView.OnClickHealButton().Subscribe(_ => _healthPoint.Heal(10));
}
}
LifetimeScope の実装
LifetimeScope をアタッチしたゲームオブジェクトをコンテナと称します。
複数のコンテナが欲しい場合は、複数配置すればいいのですが、Lifetimeについての理解が曖昧なままだと思わぬ不具合につながるので注意しましょう。
LifetimeScope では、参照関係の整理とエントリーポイントの設定を行います。
interface の登録には、builder.Register Monobehavior
を継承したクラスの登録には builder.RegisterComponentInHierarchy を使用します。
登録に関しての詳細はこちら
このままだと、Presenter
はどこからもインスタンスを生成できないため、builder.RegisterEntryPoint
を使用して実体化させます。
public class TestLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
// interfaceを実体化
builder.Register<IHealthPoint, HealthPoint>(Lifetime.Scoped).WithParameter<int>(100);
// hierarchyから取得してくる
builder.RegisterComponentInHierarchy<HealthPointView>();
// DIから実体化する
builder.RegisterEntryPoint<HealthPointPresenter>(Lifetime.Scoped);
}
}
GameLifetimeScope というゲームオブジェクトを作成し、TestLifetimeScope
をアタッチします。
これで HP バーが完成しました。
DIContainer を使うメリット・デメリット
メリット、デメリットは以下のとおりです。
メリット
- 単一責任の原則を守りやすくなる
- 依存関係の整理をしやすくなる
- Model の差し替えが容易になる
デメリット
- 依存パッケージが増える
- Presenter の実装方法が一部変わる
- シーンに配置するオブジェクトが増える
すべての UI 実装を MVP パターン+DIContainerにすると変更に強くなりますが、小さいプロジェクトや簡易的な UIでは必要がないこともあります。
適材適所で使いましょう。
Table of contents