Adding MVP to Unity3D Games

imageGood day to all. In this article I would like to talk about how you can apply the MVP template in the process of developing games on the Unity3D platform. Using this template can help streamline code and improve project structure. It should be noted right away that the article does not give a detailed description of the template itself, but it assumes that the reader has basic knowledge about it.

As we all know, MVP is a template designed to separate presentation logic from application logic. In the case of Unity3D, a presentation can be a GameObject with a set of components attached to it necessary for implementing presentation logic (including a component of the presentation logic itself - MonoBehaviour implementing the corresponding presentation interface ( View )).

The Presenter can be any type of .NET that implements the logic of a certain part of the application and interacts with the rest of its parts, such as models, services, etc.

Let's move on to practice


So, suppose we are faced with the task of creating a very simple game in which the player is not presented with the widest arsenal of possible actions: all he can do is click on the image of the colored rectangle. In response to the player’s actions, the rectangle can jump, flip and change its color to an arbitrary one, depending on the logic of the game. This is a very simple example, but it is enough to understand how you can apply the MVP template when developing games on Unity3D.

Let's take a look at a possible project organization option shown in the following figure:

image

Pay attention to the FunnyRectView script in the list of components of the FunnyRect object . This script is an implementation of presentation functionality. A typeFunnyRectView implements the IFunnyRectView interface , and this interface is also actively used by the presenter to interact with the view.

Below is the FunnyRectView code .

public class FunnyRectView : MonoBehaviour, IFunnyRectView 
{     
	private IFunnyRectPresenter _presenter;
	public void Awake()
	{
        _presenter = new FunnyRectPresenter(this);
		_presenter.Initialize();
	}
}

A bunch of presentations with a presenter takes place in Awake. The presenter, as a constructor parameter, accepts a link of type IFunnyRectView . This interface also serves as the bridge that connects the presentation with the presenter. It allows you to call methods defined in FunnyRectView , subscribe and respond to its events. The view also has the ability to interact with the presenter via the link stored in _presenter .

Let's add the logic necessary for normal functioning to FunnyRectView .

public class FunnyRectView : MonoBehaviour, IFunnyRectView
{
	private IFunnyRectPresenter _presenter;
	private bool _isInputEnabled;
	private Animator _animator;
	private const string JumpAnimationTriggerName = "JumpTrigger";
	private const string RotateAnimationTriggerName = "RotateTrigger";
	public void Awake()
	{
        _presenter = new FunnyRectPresenter(this);
		_animator = gameObject.GetComponent();
		_presenter.Initialize();
	}
	public void OnDestroy()
	{
		_presenter.Uninitialize();
	}
	public event EventHandler RotationEnd;
	public event EventHandler JumpEnd;
	public event EventHandler Clicked;
	public void OnMouseDown()
	{
		if (!_isInputEnabled) return;
		Clicked(this, EventArgs.Empty);
	}
	public void NotifyRotationEnded()     
	{
		RotationEnd(this, EventArgs.Empty);
	}
	public void NotifyJumpEnded()     
	{
		JumpEnd(this, EventArgs.Empty);
	}
	public void DisableInput()     
	{
		_isInputEnabled = false;
	}
	public void EnableInput()     
	{
		_isInputEnabled = true;
	}
	public void Rotate()     
	{
		_animator.SetTrigger(RotateAnimationTriggerName);
	}
	public void Jump()
	{
		_animator.SetTrigger(JumpAnimationTriggerName);     
	}
	public void ChangeColor(Color color)     
	{         
		var spriteRenderer = gameObject.GetComponent();
		spriteRenderer.color = color;
		_isInputEnabled = true;
	}
}

In the Awake method , we get a link to the animator component. It is needed to play the animation of the jump and flip the rectangle. The Clicked event is necessary to notify the presenter that the player clicked on the rectangle. This happens in OnMousedDown (there is a collider on the rectangle). The NotifyRotationEnded and NotifyJumpEnded methods are called by the animator at the moment when the jump or flip animation ends and generate JumpEnd and RotationEnd events, respectively. All other methods are called from the presenter.

public class FunnyRectPresenter : IFunnyRectPresenter
{
	private readonly IFunnyRectView _view;
	private const int FunnyRectRotateActionCode = 0;
	private const int FunnyRectJumpActionCode = 1;
	private const int FunnyRectChangeColorActionCode = 2;
	public FunnyRectPresenter(IFunnyRectView view)
	{         
		_view = view;
	}
	private void OnRectClicked(object sender, EventArgs e)
	{
		_view.DisableInput();
		var action = GenerateRandomAction();
		switch (action)
		{
			case FunnyRectRotateActionCode:
				_view.Rotate();
				break;
			case FunnyRectJumpActionCode:
				_view.Jump();
				break;
			case FunnyRectChangeColorActionCode:
				var color = GenerateRandomColor();
				_view.ChangeColor(color);
				break;
		}
	}
	private void OnRotationEnd(object sender, EventArgs e)
	{
		_view.EnableInput();
	}
	private void OnJumpEnd(object sender, EventArgs e)
	{
		_view.EnableInput();
	}
	private Color GenerateRandomColor()
	{
		var random = new Random();
		var c = Color.white;
		c.r = (float)random.NextDouble();
		c.g = (float)random.NextDouble();
		c.b = (float)random.NextDouble();
		return c;
	}
	private int GenerateRandomAction()
	{
		var random = new Random();
		return random.Next(FunnyRectRotateActionCode, FunnyRectChangeColorActionCode+1);
	}
	public void Initialize()
	{
		_view.Clicked += OnRectClicked;
		_view.RotationEnd += OnRotationEnd;
		_view.JumpEnd += OnJumpEnd;
		_view.EnableInput();
	}
	public void Uninitialize()
	{
		_view.Clicked -= OnRectClicked;
		_view.RotationEnd -= OnRotationEnd;
		_view.JumpEnd -= OnJumpEnd;
	}
}

I think that this code is quite simple and understandable. The Initialize occurs subscribe to events generated in the representation in Uninitialize unsubscribing from them. In the constructor, we save the link to the view. The OnRectClicked method is called every time a player clicks on a rectangle. A random action is generated in it and the corresponding presentation method is called. It remains to take a look at the code IFunnyRectView and IFunnyRectPresenter .

IFunnyRectView:

public interface IFunnyRectView 
{     
	event EventHandler RotationEnd;
	event EventHandler JumpEnd;
	event EventHandler Clicked;
	void EnableInput();
	void DisableInput();
	void Rotate();
	void Jump();
	void ChangeColor(Color color);
}

IFunnyRectPresenter:

public interface IFunnyRectPresenter
{
	void Initialize();
	void Uninitialize();
} 

The use of MVP gave us the opportunity to subsequently change the presentation logic at our discretion without affecting the application logic. It should also be noted that, based on the concepts of MVP, it is necessary to avoid direct method calls and setting presenter properties from the view. Instead, it must be notified of the occurrence of certain events.

The full source code with comments can be found at github.com/rumyancevpavel/FunnyRect .

Also popular now: