如何在Unity中合理运用MVVM架构?
别纠结书本上的理论定义,直接看Unity场景下的职责拆分,绝对不违反单一职责:
View(UI层):只做两件事——把用户的点击/输入事件转发给ViewModel,以及根据ViewModel提供的数据更新UI显示。它就是个“传声筒+显示器”,半毛钱业务逻辑都不能有。
比如你提到的Button,绝对不能在按钮脚本里写初始化子弹的代码,正确的做法是写个极简的转发脚本:using UnityEngine; using UnityEngine.UI; public class ButtonForwarder : MonoBehaviour { [SerializeField] private Button _fireButton; // 可以在Inspector拖入ViewModel实例,或者用依赖注入框架注入 public WeaponViewModel WeaponVM; void Awake() { _fireButton.onClick.AddListener(WeaponVM.HandleFireRequest); } }这个脚本除了转发点击事件,啥也不干,完美符合SRP。
ViewModel(业务协调层):是纯C#类(别继承MonoBehaviour!),负责处理View传过来的用户请求,调用底层服务完成业务逻辑,同时暴露可被View绑定的属性。它完全不碰Unity的UI API,甚至不知道View长啥样。
比如处理开火请求的ViewModel:public class WeaponViewModel { private readonly IBulletSpawner _bulletSpawner; // 用可观察对象实现数据绑定,让View自动更新弹药数 public Observable<int> CurrentAmmo { get; private set; } = new Observable<int>(30); // 构造注入依赖,把真正的子弹初始化逻辑交给专门的服务 public WeaponViewModel(IBulletSpawner bulletSpawner) { _bulletSpawner = bulletSpawner; } public void HandleFireRequest() { if (CurrentAmmo.Value <= 0) return; // 调用服务生成子弹,ViewModel只负责触发逻辑,不具体实现 _bulletSpawner.SpawnBullet(); CurrentAmmo.Value--; } }这里真正初始化子弹的是
IBulletSpawner这个服务类,ViewModel只做业务协调,职责清晰。和MVC的核心区别:Unity里的MVC往往是Controller直接操作View(比如Controller直接找Text组件改文字),而MVVM里ViewModel只提供数据和命令,View通过数据绑定自动更新——ViewModel和View完全解耦,你换个UI布局,ViewModel不用改一行代码。
常见误区提醒:别把ViewModel做成挂在UI上的Mono脚本,那等于把ViewModel和View绑死了,完全失去了MVVM解耦的意义。ViewModel应该是独立的纯C#对象,通过依赖注入或者工厂创建后,交给View层的Mono脚本去绑定。
内容的提问来源于stack exchange,提问作者IndividualGames

