10 December 2019

MVC в Unity со Scriptable Objects. Часть 3

Plarium corporate blogGame developmentC#Unity3D
Translation
Original author: Cem Ugur Karacam
Завершение цикла статей от Cem Ugur Karacam о реализации MVC в Unity с помощью Scriptable Objects. Прочитать предыдущие части вы можете здесь и здесь.



Это последний этап нашего проекта.

Я попытался сделать схему, чтобы проиллюстрировать рабочий процесс MVC в Unity.



В приложении Unity объекты MonoBehaviour находятся в сцене, поэтому вы можете видеть их иерархию. Но эти объекты не могут общаться друг с другом напрямую. Шаблон MVC в Unity — решение этой проблемы.

Если говорить проще: пользовательский ввод приходит в контроллер, который создает представление для модели, а представление отображает данные модели на экране.

Сначала мы ждем ввода от пользователя, например нажатия кнопки. Затем контроллер создает представление и выбирает модель, необходимую для отображения в этом представлении. Теперь представление готово, оно содержит ссылки на объекты пользовательского интерфейса и передает данные в эти ссылки для отображения.

Давайте продолжим проект с того, на чем мы остановились в прошлой статье. Поработаем над представлением. Я создам панель, которая будет содержать UI-объекты.



У нас есть панель, объект Image для иконки предмета и три текстовых объекта для отображения имени, типа и силы атаки. Чтобы управлять этими объектами, создадим класс в проекте с именем InfoView и добавим его в сцену.



Добавим ссылки на элементы интерфейса.

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;
}

Затем создадим метод Init, чтобы настроить эти элементы согласно входным данным.

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;
        }
    }
}

Теперь назначим поля в редакторе.



Мы готовы сделать префаб из этого представления. Я создал папку с именем Resources. Это особая папка, которая позволяет Unity загружать файлы из нее через специальный API. Поместим наши префабы в папку Resources.



Поскольку я буду использовать префабы, то удалю InfoView из сцены.



Время открыть контроллер.

Я добавлю открытую переменную Transform, чтобы знать, какой объект будет родительским для этого представления, и закрытую переменную, чтобы сохранить ссылку на InfoView при старте.

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");
    }
}

Напишем метод для создания экземпляров представления в сцене.

private void CreateInfoView(ItemData data)
{
var infoGO = GameObject.Instantiate(infoViewPrefab, infoViewParent);
infoGO.GetComponent<InfoView>().Init(data);
}

Я передам этот метод ItemView в InitItem, используя события в C#. Чтобы этого добиться, изменим немного ItemView.

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) );
    }
}

Я добавил параметр, чтобы передать метод. И теперь можно подключить контроллер.

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);
    }
}

В методе Start я заполняю инвентарь предметами, и когда вы кликнете по одному из них, будет вызван метод CreateInfoView. Но перед тем как мы начнем тестировать это, я укажу вам на одну проблему. Контроллер не знает, создавали ли мы InfoView ранее. Давайте исправим это.

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);
    }
}

Мы сделали переменную infoView и проверяем ее перед созданием нового экземпляра InfoView в сцене.

Давайте протестируем.



Кажется, мы это сделали!

Проект на GitHub.

Это только основы реализации MVC в Unity с использованием Scriptable Objects. Но я верю, что подобный подход можно реализовать в любом проекте. Например, при работе с REST-вызовами этот шаблон может сэкономить вам много времени и сохранить код расширяемым. В целом в Unity достаточно сложно передать объекты сцены в код и работать с ними. Кто-то может возразить и сказать, что для этих целей можно использовать шаблон Singleton. Да, метод, который я описал, не единственный, но он весьма неплох.

Думаю, мы можем вообще не использовать шаблоны, но тогда мы ничем не будем отличаться от средневековья. :)

В любом случае, поскольку эта серия статей завершена, я предлагаю вам почитать другие мои тексты, в которых также рассказывается о шаблоне MVC и Scriptable Objects: Making a REST service using Node and Express to use with Unity.

На этом все. Удачного кодинга!
Tags: unity c# scriptableobject mvc model view controller разработка разработка игр программирование геймдев геймдевелопмент новичкам программирование игр шаблон шаблоны
Hubs: Plarium corporate blog Game development C# Unity3D
+7
2.1k 47
Comments 3
Ads