Чтение из json в Unity 5.6
Здравствуй, читатель!
В этой статье я хочу поделиться своим опытом решения проблемы передачи данных из файла в unity, но обо всем по порядку.
Задумал я написать прототип игры по собственному концепту, который разработал во время прохождения курса геймдизайнера, да добавить в нее редактор персонажа.
И хотелось мне, чтобы в редакторе были кнопки, соответствующие деталям персонажа (корпус, оружие), а дублировать кнопки руками и менять у каждой все переменные — не хотелось. Тогда то и задумался над вводом данных из файла.
Графическая составляющая
Первым делом набросал в Фотошопе разметку интерфейса:

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

После чего сделал префабы для кнопок категорий и кнопок конкретных частей.
Подготовка данных к чтению
Перед написанием кода необходимо определиться с параметрами, которые мы будем считывать из файла. Для категорий это всего два параметра: имя и код категории.
Код
{
"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 очков, кто больше?). Буду рад обратной связи, до новых статей!