Here is the last part of the MVC project.
I've tried to make a schema to illustrate the workflow of MVC in unity.
In a unity app, MonoBehaviour objects live in a scene, so you could see what objects in the hierarchy. But these objects can't communicate with each other directly. MVC pattern in unity is a useful solution to that problem.
Let's try to write it simply:
User input --> Controller -----createView(ModelData)-----> View -----display(ModelData)
First, we wait for input from the user like a button click. Next, the Controller provides the view(as prefab in project) and what model needed to pass to be able to display in this view. Then, Controller instantiates the View and creates it in the scene with the data provided from the Controller. Now, View is ready and holds references for UI objects, passes data to those references to be displayed.
Let's make it in unity from where we left.
I'll work on the view first. I'll create a panel that contains uGUI objects.
We have a panel and an Image for item icon, three text objects for name, type and attack power, respectively. To hold a reference for these objects, create a class in the project, InfoView
, add to InfoView object in the scene.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class InfoView : MonoBehaviour
{
public Image icon;
public Text nameText;
public Text typeText;
public Text attackText;
}
Next, let's add an init method to pass info to those references.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class InfoView : MonoBehaviour
{
public Image icon;
public Text nameText;
public Text typeText;
public Text attackText;
public void Init(ItemData data)
{
icon.sprite = data.icon;
nameText.text = data.name;
attackText.text = "Attack Power: " + data.attack;
switch (data.type)
{
case ItemType.Axe: typeText.text = "Type: Axe"; break;
case ItemType.Dagger: typeText.text = "Type: Dagger"; break;
case ItemType.Hammer: typeText.text = "Type: Hammer"; break;
case ItemType.Potion: typeText.text = "Type: Potion"; break;
}
}
}
Then, assign UI objects in the editor.
It's ready to make a prefab for this view. I'll make a folder as Resources
. This is a special folder that unity has an access API to this specific folder. Place this prefab in Resources.
I've organized my project folders a bit. Now all prefabs in my Resources
folder and I can access via API.
Since I will Instantiate
view prefabs, delete InfoView from scene.--
Time to open Controller.
I'll make a public Transform
to point who is going to be the parent of this view, and a private variable to store a reference to ViewInfo
at start.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemViewController : MonoBehaviour
{
public Inventory inventoryHolder;
public Transform inventoryViewParent;
public Transform infoViewParent;
private GameObject infoViewPrefab;
private GameObject itemViewPrefab;
private void Start()
{
itemViewPrefab = (GameObject)Resources.Load("Item");
infoViewPrefab = (GameObject)Resources.Load("InfoView");
}
}
Create a method to instantiate view into the scene.
private void CreateInfoView(ItemData data)
{
var infoGO = GameObject.Instantiate(infoViewPrefab, infoViewParent);
infoGO.GetComponent<InfoView>().Init(data);
}
I'll give this method to ItemView
in InitItem
, you guessed right thanks to Action<>
in c#. Let's modify a bit ItemView
to achieve that.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ItemView : MonoBehaviour
{
public Button button;
public Image itemIcon;
private ItemData itemData;
public void InitItem(ItemData item, Action<ItemData> callback)
{
this.itemData = item;
itemIcon.sprite = itemData.icon;
button.onClick.AddListener(() => callback(itemData) );
}
}
So here, I've added a parameter more to pass a method.
Now I can wire up in the controller.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemViewController : MonoBehaviour
{
public Inventory inventoryHolder;
public Transform inventoryViewParent;
public Transform infoViewParent;
private GameObject infoViewPrefab;
private GameObject itemViewPrefab;
private void Start()
{
itemViewPrefab = (GameObject)Resources.Load("Item");
infoViewPrefab = (GameObject)Resources.Load("InfoView");
foreach (var item in inventoryHolder.inventory)
{
var itemGO = GameObject.Instantiate(itemViewPrefab, inventoryViewParent);
itemGO.GetComponent<ItemView>().InitItem(item, CreateInfoView);
}
}
private void CreateInfoView(ItemData data)
{
var infoGO = GameObject.Instantiate(infoViewPrefab, infoViewParent);
infoGO.GetComponent<InfoView>().Init(data);
}
}
At Start
, I populate inventory with items, and when you click items, CreateInfoView
fires up. But before testing it out, I saw a problem here. Controller doesn't know if there is an InfoView
created before we click. Let's check before Instatiate
.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemViewController : MonoBehaviour
{
public Inventory inventoryHolder;
public Transform inventoryViewParent;
public Transform infoViewParent;
private GameObject infoViewPrefab;
private GameObject itemViewPrefab;
private GameObject infoView;
private void Start()
{
itemViewPrefab = (GameObject)Resources.Load("Item");
infoViewPrefab = (GameObject)Resources.Load("InfoView");
foreach (var item in inventoryHolder.inventory)
{
var itemGO = GameObject.Instantiate(itemViewPrefab, inventoryViewParent);
itemGO.GetComponent<ItemView>().InitItem(item, CreateInfoView);
}
}
private void CreateInfoView(ItemData data)
{
if (infoView != null)
{
Destroy(infoView);
}
infoView = GameObject.Instantiate(infoViewPrefab, infoViewParent);
infoView.GetComponent<InfoView>().Init(data);
}
}
Here, I've made a variable infoView
, and when I try to make a new InfoView
in the scene, first, I check if there is any.
Now time to give it a go.
Hit to play.
It seems we made it!
Project on github.
This was a gentle intro to MVC in unity with Scriptable Objects. But I believe it could be implemented like this way to any project. Especially, when receiving REST calls in unity, this pattern could be a life-saving pattern to keep your code organized and scaleable. In unity, it could be a challenge to maintain your objects and communicating them via code. Some may argue with the best solution could be Singletons. They're right, this is not the only way, but a good one.
I think it could be possible to live without design patterns as well, but I'm sure that would be no different from medieval :)
Anyway, since this series completed, I suggest you check my other series using MVC and Scriptable Objects as well: Making a REST service using Node and Express to use with Unity.
Happy coding unity ninjas!
Top comments (0)