Чтение из json в Unity 5.6

Здравствуй, читатель!

В этой статье я хочу поделиться своим опытом решения проблемы передачи данных из файла в unity, но обо всем по порядку.

Задумал я написать прототип игры по собственному концепту, который разработал во время прохождения курса геймдизайнера, да добавить в нее редактор персонажа.

И хотелось мне, чтобы в редакторе были кнопки, соответствующие деталям персонажа (корпус, оружие), а дублировать кнопки руками и менять у каждой все переменные — не хотелось. Тогда то и задумался над вводом данных из файла.

Графическая составляющая

Первым делом набросал в Фотошопе разметку интерфейса:

GUI ангара

Далее разметил это в unity:

2017-10-01_21-40-20

После чего сделал префабы для кнопок категорий и кнопок конкретных частей.

Подготовка данных к чтению

Перед написанием кода необходимо определиться с параметрами, которые мы будем считывать из файла. Для категорий это всего два параметра: имя и код категории.

Код
{
	"name": "Ноги",
	"code": "legs"
}

Для частей игрока параметров больше и их структура сложнее: имя, категория, точка опоры, точки крепления.

На этом моменте уже можно набросать примерный json-файлик с описанием отдельных объектов.

Код
{
	"name": "leg1",
	"category": "legs",
	"anchorPoint": {
		"x": 0,
		"y": 0
	},
	"attachPoints": {
		"attachPointsArray": [
			{
				"x": 0,
				"y": 0
			}
		]
	}
}

Но создавать по файлику на каждую кнопку как-то неразумно. Нам нужен массив!

Код
{ "categories":
	[
		{
			"name": "Ноги",
			"code": "legs"
		},
		{
			"name": "Корпус",
			"code": "body"
		}
	]
}
Cам массив должен быть параметром некоего внешнего объекта.

Подготовка необходимых классов

И вот тут начинается веселье. C# строго типирован, и, как оказалось, вы не можете просто так разобрать json-строку в динамический объект, из которого потом будете забирать нужные данные. Нееет. Вам необходимо описать формат того, что будет считывать Unity из файла.

Здесь на помощь приходит вид/тип/семейство классов под тэгом Serializable. В unity 5 такие классы описываются следующим образом:

Код
using System.Collections.Generic;

[System.Serializable]
public class Category {
    public string name;
    public string code;
}

Класс! Этого достаточно, чтобы считать из json-строки один экземпляр. Как же быть с массивом? Нужно создать сериализуемый класс, содержащий один параметр — массив объектов нужного класса. (Ваш кэп)

Код
[System.Serializable]
public class CategoryList
{
    public List<Category> categories;
}

Теперь то всё? Теперь все — можем считать и разобрать наш json, содержащий массив описаний кнопок категорий.

Названия параметров, перечисленных в json, должны совпадать с параметрами сериализуемого класса.
Под спойлером описание с примером чуть более сложной структуры объекта.
using System.Collections.Generic;

[System.Serializable]
public class AnchorPoint
{
    public int x;
    public int y;
}
[System.Serializable]
public class AttachPoint
{
    public int x;
    public int y;
}
[System.Serializable]
public class AttachPointList
{
    public List<AttachPoint> attachPointsArray;
}
[System.Serializable]
public class PlayerPart
{
    public string name;
    public string category;
    public AnchorPoint anchorPoint;
    public AttachPointList attachPoints;
}
[System.Serializable]
public class PlayerPartsList
{
    public List<PlayerPart> parts;
}

Переходим к считыванию!

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

Код
using UnityEngine;
using System.IO; //нужно для чтения файлов

public class UIinitializer : MonoBehaviour {
    public GameObject categoryArea;
    public GameObject categoryPrefab;

    public GameObject selectPartArea;
    public GameObject partButtonPrefab;

    private string categoriesJson = "Assets/Configs/UI/categories.json";
    private string objectsJson = "Assets/Configs/PlayerParts.json";
    private CategoryList catList;
    private PlayerPartsList partsList;

    private Sprite[] objectSprites;
    // Use this for initialization
    void Start () {
       
    }
	
    // Update is called once per frame
    void Update () {
		
    }
}

Далее я написал два метода: чтение json из переданного файла и создание кнопок из префаба.

Код
private void readCategories()
{
    StreamReader strRead = new StreamReader(categoriesJson);
    string json = strRead.ReadToEnd();
    catList = JsonUtility.FromJson<CategoryList>(json);
}

private void createCategoriesButtons()
{
    int i = 0;
    foreach (Category cat in catList.categories)
    {
        GameObject button = Instantiate(categoryPrefab, categoryArea.transform);
        CategorySelectorScript buttonScript = button.GetComponent<CategorySelectorScript>();
        
        i++;
    }
}

Пишем скрипт, который будет управлять самой кнопкой, определяем в нем методы для установки параметров.

Код
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class CategorySelectorScript : MonoBehaviour {
    public string categoryName;

    private Text textField;
    private string categoryCode;
    private List<GameObject> linkedItems; //массив связанных 

	// Use this for initialization
	void Awake () {
        textField = this.GetComponentInChildren<Text>();
	}
	
	// Update is called once per frame
	void Update () {
		
	}

    public void PickCategory()
    {
        
        foreach(GameObject item in linkedItems)
        {
            item.SetActive(true);
        }
    }

    public void AddObjectToList(GameObject obj)
    {
        linkedItems.Add(obj);
    }

    public void SetName(string name)
    {
        categoryName = name;
        textField.text = categoryName;
    }

    public void SetCode(string code)
    {
        categoryCode = code;
    }
}

Добавляем передачу параметров и установку координат в наш класс-создатель:

Код
//после строки CategorySelectorScript buttonScript = button.GetComponent<CategorySelectorScript>();
button.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, -i * offset + first_offset);
buttonScript.SetName(cat.name);
buttonScript.SetCode(cat.code);

Готово! Мы великолепны, теперь после загрузки сцены игра считает json, создает и размещает кнопки без нашего участия.

Под спойлером код для создания кнопок в нижней части.

Полный листинг инициализирующего скрипта

using UnityEngine;
using System;
using System.IO;

public class UIinitializer : MonoBehaviour {
    public GameObject categoryArea;
    public GameObject categoryPrefab;

    public GameObject selectPartArea;
    public GameObject partButtonPrefab;

    public int offset = 20;
    public int first_offset = -14;

    private string categoriesJson = "Assets/Configs/UI/categories.json";
    private string objectsJson = "Assets/Configs/PlayerParts.json";
    private CategoryList catList;
    private PlayerPartsList partsList;
    //для подгрузки спрайта из атласа
    private Sprite[] objectSprites;
    // Use this for initialization
    void Start () {
        objectSprites = Resources.LoadAll<Sprite>("Sprites/objectsAtlas");

        readCategories();
        //считываем объекты
        readObjects();

        createCategoriesButtons();

    }
	
	// Update is called once per frame
	void Update () {
		
	}

    private void createCategoriesButtons()
    {
        int i = 0;
        foreach (Category cat in catList.categories)
        {
            GameObject button = Instantiate(categoryPrefab, categoryArea.transform);
            CategorySelectorScript buttonScript = button.GetComponent<CategorySelectorScript>();
            button.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, -i * offset + first_offset);
            buttonScript.SetName(cat.name);
            buttonScript.SetCode(cat.code);
            //добавили этот цикл для создания кнопок в нижней части экрана
            foreach (PlayerPart part in partsList.parts)
            {
                if (cat.code == part.category)
                {
                    GameObject partButton = Instantiate(partButtonPrefab, selectPartArea.transform);
                    PlayerPartButton partButtonScript = partButton.GetComponent<PlayerPartButton>();
                    Sprite newSprite = getSpriteByName(part.name);
                    partButtonScript.SetConfig(part.name, part.anchorPoint, part.attachPoints, newSprite);
                }
            }

            i++;
        }
    }

    private void readCategories()
    {
        StreamReader strRead = new StreamReader(categoriesJson);
        string json = strRead.ReadToEnd();
        catList = JsonUtility.FromJson<CategoryList>(json);
    }

    private void readObjects()
    {
        StreamReader strRead = new StreamReader(objectsJson);
        string json = strRead.ReadToEnd();
        partsList = JsonUtility.FromJson<PlayerPartsList>(json);

    }
    //получаем спрайт из атласа по имени
    private Sprite getSpriteByName(string sprName)
    {
        foreach (Sprite spr in objectSprites)
        {
            if (spr.name == sprName) return spr;
        }

        return new Sprite();
    }
}

Описание контроллера кнопок в нижней части:

using UnityEngine;
using UnityEngine.UI;

public class PlayerPartButton: MonoBehaviour {

    private AnchorPoint anchor;
    private AttachPointList attachPointList;
    private string spriteName;
    private Image sprite;

	// Use this for initialization
	void Awake () {
        sprite = this.GetComponent<Image>();
	}
	
	// Update is called once per frame
	void Update () {
		
	}

    public void SetConfig(string newName, AnchorPoint newAnchor, AttachPointList newAtList, Sprite newSprite)
    {
        spriteName = newName;
        anchor = newAnchor;
        attachPointList = newAtList;
        sprite.sprite = newSprite;
    }
}

Резюмируя, алгоритм для работы с данными из конфигов следующий:

  • Продумать нужные параметры
  • Создать сериализуемые классы с этими параметрами, если планируете получать массивы — классы с параметром-массивом классов.
  • Получить json в коде
  • Присвоить считанные данные нужным переменным/выполнить какие-то действия

Надеюсь, данная статья была вам полезна. Когда я сам с этим разбирался — мне не хватило такого руководства.

А пока я пишу следующий билд, можете подписаться на цель в смартпрогрессе, поделиться опытом и мнением здесь или в комментариях к цели. 

Также предлагаю прямо в браузере поиграть в мою первую игру на unity и рассказать о своих результатах (мой рекорд — 120 очков, кто больше?). Буду рад обратной связи, до новых статей!

Полезные ссылки:

© 2022 CAsperovskii BLOG.RU // Дизайн и поддержка: GoodwinPress.ru