UnityでのMV(R)Pモデルメモ
初めに
今回は、オブジェクト指向のデザインパターンを勉強したのでそれをunityで実践した事をメモした記事になります。
MV(R)P (Model -View - Presenter) モデルとは
ソフトウェアの設計パターンの一つで、アプリケーションの構造を整理し、保守性とテスト性を向上させるために使用されます。以下に、各コンポーネントの役割を説明します。
Model(LightModel): Modelはアプリケーションのビジネスロジックとデータ状態を管理。Modelはデータの取得、保存、更新などの操作を行い、その結果をPresenterに通知する。
View(LightView): ViewはUIに関する見た目の部分を管理。Viewはユーザーからの入力を受け取り、Presenterに通知する。また、Presenterからの指示に基づいてUIを更新します。UIの表示とユーザーからの入力に集中させる。
Presenter(LightPresenter): PresenterはModelとViewを結びつける役割を果たす。PresenterはViewからのユーザー入力を受け取り、それに基づいてModelの状態を更新。また、Modelの状態が変更されたときには、その変更をViewに反映。どのようにModelを変化させるかの処理を担当。
デモコード
今回はLightの挙動を表す処理をMV(R)Pモデルで実装した。
View (LightView)
光の強度や角度を表示するための部分を担当。
using UnityEngine;
public class LightView : MonoBehaviour
{
private Light _light;
private void Awake()
{
_light = GetComponent<Light>();
}
// 光の強さを設定する
public void SetIntensity(float intensity)
{
_light.intensity = intensity;
}
// 光の角度を設定する
public void SetAngle(float angle)
{
_light.transform.rotation = Quaternion.Euler(90 + angle, angle, 0);
}
}
Model (LightModel)
LightModelでは光の強度や角度などの状態を保持し、それらの状態を変更するメソッド。
using UniRx;
public class LightModel
{
public ReactiveProperty<float> Intensity { get; private set; }
public ReactiveProperty<float> Angle { get; private set; }
public LightModel()
{
Intensity = new ReactiveProperty<float>(1.0f);
Angle = new ReactiveProperty<float>(0.0f);
}
// 角度を変動させるメソッド
public void ChangeAngle(float deltaAngle)
{
Angle.Value += deltaAngle;
}
}
Presenter (LightPresenter)
LightViewからの操作に応じてLightModelの状態を変更し、その結果をLightViewに反映します。そのため、外側から処理を加える場合はPresenter内のメソッドを呼び出します。
using UnityEngine;
using UniRx;
public class LightPresenter : MonoBehaviour
{
private LightView _lightView;
private LightModel _lightModel;
private void Awake()
{
// LightViewのインスタンスを参照
_lightView = GetComponent<LightView>();
_lightModel = new LightModel();
// Modelの値が変更されたときにViewを更新する
_lightModel.Intensity.Subscribe(_lightView.SetIntensity).AddTo(this);
_lightModel.Angle.Subscribe(_lightView.SetAngle).AddTo(this);
}
// 光の強さを時間経過によって変化させるメソッド
public void ChangeIntensityByTime(float deltaTime, float changeSpeed)
{
_lightModel.Intensity.Value += deltaTime * changeSpeed;
}
// 角度を変動させるメソッド
public void ChangeAngleByTime(float deltaTime, float deltaAngle)
{
_lightModel.ChangeAngle(deltaTime * deltaAngle);
}
}
Manager (LightManager)
全体のフローを制御する役割。ライフサイクル(起動、終了など)や、複数のLightPresenterを管理する役割を果たす。例えば、特定のイベント(アプリケーションの起動)に応じて、適切なLightPresenterのメソッドを呼び出すなどの役割を果たす。
using UnityEngine;
using UniRx;
public class LightManager : MonoBehaviour
{
[SerializeField] private LightPresenter _lightPresenter;
[SerializeField] private float _changeLightIntensitySpeed = 1.0f; // 光の強さが変化する速度
[SerializeField] private float _changeLightAngleSpeed = 30.0f; // 光の角度が変化する速度
private void Start()
{
// 毎フレームの更新を購読
Observable.EveryUpdate()
.Subscribe(_ =>
{
_lightPresenter.ChangeIntensityByTime(Time.deltaTime, _changeLightIntensitySpeed);
_lightPresenter.ChangeAngleByTime(Time.deltaTime, _changeLightAngleSpeed);
})
.AddTo(this);
}
}
個人的疑問
勉強しながら発生した疑問に対して、調べた上で解決した事を書いていく。
・ViewからModelを直接弄れば良いのでは?
ViewはUIの表示とユーザーからの入力の受け取りに集中するべき。ロジック(どのようにModelを操作するべきか)はPresenterが担当。これにより、各コンポーネントの役割が明確になり、コードの読みやすさ、保守性、再利用性が向上する。
Presenterを介すことで、ロジックを独立してテストすることが可能となる。ViewはUIと密接に結びついており、テストが困難な場合がある。しかし、PresenterはViewから独立しているため、ユニットテスト(特定の関数やメソッドが正しく動作するか確認)を行いやすくなります。
同じロジックを複数のViewで使用する場合、そのロジックをPresenterに置くことで、コードの再利用が可能となる。
・角度を連続で変化させる処理はどこに書く?
Lightの角度を連続的に変化させる処理は、LightModelに記載。LightModelはデータ(この場合はLightの角度)とそのデータを操作するロジックを持つべき。
しかし、その変化をトリガーするタイミングや条件(例えば、特定のイベントが発生したときや一定時間が経過したときなど)は、"LightPresenter"が制御する。LightPresenterはLightModelとLightViewを繋げる役割を持ち、ユーザーの入力やゲームの状態に応じてLightModelの状態を変更する。
したがって、LightModelに角度を変化させるメソッドを作り、LightPresenterからそのメソッドを適切なタイミングで呼び出すという形にする。
・LightManagerの処理もLightPresenterに書くべき?
LightManagerクラスの役割は、光の強さを時間経過によって変化させるという特定のロジックを実装しています。このロジックは、LightPresenterよりも一段階上のレベルで制御するべき内容であるため、LightManagerクラスに残しておくのが適切。
LightPresenterは、LightModelとLightViewを繋げる役割を持ち、Modelの状態変化をViewに反映させる役割を果たします。一方、LightManagerはゲームの具体的なロジック(光の強さを時間経過で変化させる等)を制御します。
この記事が気に入ったらサポートをしてみませんか?