Страницы

вторник, 24 декабря 2019 г.

Процедурное создание уровней в Unity. Часть 5-2. Создание виртуальной Солнечной системы на основе XML-файла. Работа с файлом

Сейчас у нас есть планета, которая вращается вокруг Солнца. Нужно сделать ее префабом, а затем создать другие планеты из XML-файла и создать экземпляр соответствующего игрового объекта в сцене с помощью этого префаба. В этой заключительной части мы создадим XML-файл и функции загрузки информации из него.

Создайте XML-файл и заполните его в вашем редакторе кода:
<?xml version="1.0" encoding="UTF-8"?>
<planets>
    <planet name = "Меркурий" diameter = "0.4" distancetoSun = "0.4"
    rotationPeriod= "58.6" orbitalVelocity = "1.6"> </planet>
    <planet name = "Венера" diameter = "0.95" distancetoSun = "0.72"
    rotationPeriod = "-243.0" orbitalVelocity = "1.17">
    </planet>
    <planet name = "Земля" diameter = "1" distancetoSun = "1"
    rotationPeriod = "1" orbitalVelocity = "1">
    </planet>
    <planet name = "Марс" diameter = "0.5" distancetoSun = "1.52"
    rotationPeriod = "1" orbitalVelocity = "0.81">
    </planet>
    <planet name = "Юпитер" diameter = "11" distancetoSun = "5.2"
    rotationPeriod = "0.42" orbitalVelocity = "0.44">
    </planet>
    <planet name = "Сатурн" diameter = "9.5" distancetoSun = "9.54"
    rotationPeriod = "0.43" orbitalVelocity = "0.3">
    </planet>
    <planet name = "Уран" diameter = "4" distancetoSun = "19.2"
    rotationPeriod = "-0.72" orbitalVelocity = "0.23">
    </planet>
    <planet name = "Нептун"  "3.9" distancetoSun = "30.1"
    rotationPeriod = "0.67" orbitalVelocity = "0.18">
    </planet>   
</planets>

Что в этом файле:

  • Первая строка объявляет версию XML и кодировку, как мы делали раньше.
  • Затем идет корневой узел под названием planets, который будет включать в себя все планеты.
  • В этом узле мы имеем описание всех планет. Каждая планета определяется с помощью тега <planet>.
  • Для каждой из них мы определяем такие атрибуты, как название, диаметр, расстояние до Солнца и т.д. Все эти параметры, за исключением названия, будут относиться к настройкам для планеты Земля (т. е. настройки для планеты Земля уже были определены ранее в скрипте Planet.cs).

Обратите внимание. В файле даны не абсолютные величины, а значения, высчитанные относительно Земли. То есть это множители, например, диаметр Марс примерно равен половине земного диаметра, а расстояние от Солнца примерно в полтора раза больше.
Сохраните созданный XML-файл под названием Planets.xml в папке с ресурсами.
Создайте префаб из объекта planet: перетащите объект в окно проекта. Теперь мы можем отключить объект planet, расположенный в окне иерархии.

Создайте пустой объект под названием _loadPlanets (GameObject | Create Empty). Этот объект будет использоваться для добавления (и выполнения) скрипта, который будет загружать все планеты.

Создайте новый скрипт под названием LoadPlanets и добавьте следующий код в начало файла (перед классом):
using System.Xml;
В начало класса добавьте объявление переменной:
 public GameObject planetTemplate;
В методе Start пропишите вызов функции:
LoadAllPlanets();
Создадим саму функцию. Добавьте ее в конец класса
void LoadAllPlanets()
{
   TextAsset textAsset = (TextAsset)Resources.Load("planets");
   XmlDocument doc = new XmlDocument();
   doc.LoadXml(textAsset.text);
   foreach (XmlNode planet in doc.SelectNodes("planets/planet"))
   { }
}

  • Мы объявляем функцию LoadAllPlanets.
  • Затем создаем переменную TextAsset, которая будет использоваться для загрузки содержимого XML-файла.
  • Затем мы объявляем XML-документ с именем doc. 
  • Содержимое XML-файла затем используется для загрузки XML-контента (текста) внутри XML-файла doc.
  • Затем выбираем все узлы, называемые planet, которые являются потомками (т. е. находятся внутри) узла, называемого planets (т.е. все планеты, включенные в файл).

На этом этапе мы должны иметь доступ к XML-файлу, и следующие строки кода помогут нам проверить, можем ли мы прочитать из него какой-то контент.
Добавьте следующий код внутрь цикла foreach:
string name, diameter, distancetoSun, rotationPeriod, orbitalVelocity;
name = planet.Attributes.GetNamedItem("name").Value;
diameter = planet.Attributes.GetNamedItem("diameter").Value;
distancetoSun = planet.Attributes.GetNamedItem("distancetoSun").Value;
rotationPeriod = planet.Attributes.GetNamedItem("rotationPeriod").Value;
orbitalVelocity = planet.Attributes.GetNamedItem("orbitalVelocity").Value;
Debug.Log("Planet name: " + name);
Поскольку атрибуты каждого узла для чтения в XML-файлах хранятся в виде строк, мы объявляем строку для каждого из них, чтобы извлечь содержимое.

Затем мы считываем соответствующее значение каждого атрибута, используя функцию Attributes.GetNamedItem.

После этого напишем сообщение в окне консоли с именем планеты, к которой мы получили доступ из XML-файла, чтобы проверить, что мы можем правильно прочитать файл и атрибуты планет.

Сохраните ваш код.

  • Перетащите скрипт LoadPlanets на объект _loadPlanets.
  • Откройте в окне проекта папку с префабами.
  • Выберите объект _loadPlanets в окне иерархии. 
  • Убедившись, что окно Инспектора активно, перетащите префаб планеты на место переменной planetTemplate в скрипте.

Рис. 30. Устанавливаем переменную в скрипте
Запустите сцену и откройте консоль. Вы должны увидеть список названий планет из нашего файла.
Рис. 31.  Список планет в консоли
Теперь мы можем загрузить XML-файл и прочитать его содержимое; следующим этапом будет чтение атрибутов всех планет и создание экземпляров соответствующих объектов; однако прежде чем это сделать, мы изменим наш исходный скрипт, называемый Planet, чтобы можно было изменить свойства каждого объекта, созданного из префаба. Мы создадим публичные методы, которые будут доступны извне этого класса и которые могут быть использованы для установки атрибутов созданных планет.
Создаем эти функции.
Добавьте следующий код в класс Planet:
public void SetRotationalSpeed(float s)
{
   rotationalSpeed = s * rotationalSpeed;
}
public void SetOrbitSpeed(float os)
{
   orbitalSpeed = os * orbitalSpeed;
}
public void SetDistanceToSun(float d)
{
   distanceToSun = distanceToSun * d;
}
public void SetName(string name)
{
   this.name = name;
   transform.Find("label").GetComponent<TextMesh>().text = name;
}
public void SetRadius(float radius)
{
   transform.localScale = new Vector3(radius, radius, radius);
}

Мы создаем пять методов SetRotationalSpeed, SetOrbitSpeed, SetDistanceToSun, SetName и SetRadius.

Для большинства из этих методов мы используем значения параметров, чтобы изменить свойства создаваемой планеты. Еще раз напоминаю, что значения, включенные в XML-файл, определяются по отношению к планете Земля (т. е. это множители).

Для метода SetName мы ищем дочерний элемент игрового объекта label, а затем записываем в его текст имя, найденное в XML-файле.
Откройте скрипт LoadPlanet и добавьте код в функцию LoadAllPlanets  внутрь цикла foreach (до строчки с Debug.Log, которая нам сейчас уже будет не нужна):
float diameter2, distancetoSun2, rotationPeriod2, orbitalVelocity2;
diameter2 = float.Parse(diameter);
distancetoSun2 = float.Parse(distancetoSun);
rotationPeriod2 = float.Parse(rotationPeriod);
orbitalVelocity2 = float.Parse(orbitalVelocity);
print("Planet " + name + ": Diameter " + diameter2 + "; Distance " + distancetoSun2);
GameObject g = Instantiate(planetTemplate);
g.GetComponent<Planet>().SetDistanceToSun(distancetoSun2);
g.GetComponent<Planet>().SetOrbitSpeed(orbitalVelocity2);
g.GetComponent<Planet>().SetRotationalSpeed(1 / rotationPeriod2);
g.GetComponent<Planet>().SetName(name);
g.GetComponent<Planet>().SetRadius(diameter2);

Мы объявляем значения, необходимые для инициализации новых планет; они будут получены из строк, считанных из XML-файла.

Затем мы преобразуем данные, собранные в строковом формате, в формат float и устанавливаем переменные float diameter 2, distancetoSun2, rotationPeriod2 и orbialVelocity2 с этими новыми значениями. Для преобразования чисел используем функцию float.Parse.

Затем мы создаем экземпляр новой планеты с помощью префаба planet. После того, как новый объект создан, мы используем открытые методы класса Planet, чтобы установить орбитальный радиус этой планеты, скорость, диаметр, скорость вращения и имя.

Сохраните код и запустите сцену.

!!! Возможно, вместо нормально запущенной сцены с орбитами планет, вы увидите в консоли вот такую ошибку:
Рис. 32. Ошибка при запуске
Текст ошибки:
FormatException: Input string was not in a correct format.
System.Number.ParseSingle (System.String value, System.Globalization.NumberStyles options, System.Globalization.NumberFormatInfo numfmt) (at <567df3e0919241ba98db88bec4c6696f>:0)
… еще строки…
LoadPlanets.LoadAllPlanets () (at Assets/Scripts/LoadPlanets.cs:34)
LoadPlanets.Start () (at Assets/Scripts/LoadPlanets.cs:12)
Это значит, что программа не распознала формат float, потому что наши значения в XML-файле указаны с точкой, а региональные настройки предусматривают запятую. Кроме того, две планеты у нас вращаются вокруг себя в обратную сторону, на что указывает знак минус перед значением rotationPeriod. Минус тоже так просто не определится Мы не можем менять файл со значениями, поэтому придется добавить дополнительные параметры в метод float.parse. Установим стиль с десятичной точкой NumberStyles.AllowDecimalPoint, стиль со знаком минус перед числом NumberStyles.AllowLeadingSign  и провайдера IFormatProvider provider, который будет соответствовать языку “en-Us”.
Добавьте в раздел объявлений using строку
using System.Globalization;
В этом модуле содержатся необходимые нам значения стилей. В раздел объявлений функции LoadAllPlanets добавьте объявление нашего провайдера (перед циклом foreach):
CultureInfo provider = new CultureInfo("en-Us");
Затем перепишите строки парсинга в виде:
diameter2 = float.Parse(diameter, NumberStyles.AllowDecimalPoint, provider);
distancetoSun2 = float.Parse(distancetoSun, NumberStyles.AllowDecimalPoint, provider);
rotationPeriod2 = float.Parse(rotationPeriod, NumberStyles. AllowDecimalPoint|NumberStyles.AllowLeadingSign, provider);
orbitalVelocity2 = float.Parse(orbitalVelocity, NumberStyles.AllowDecimalPoint, provider);
В строку с rotationPeriod мы добавили дополнительный параметр для знака "минус".

Запустим сцену и увидим список планет в Иерархии и рисунок орбит в режиме игры. В режиме сцены можно сфокусироваться на планетах (Shift + F), приблизиться и посмотреть надпись.
Рис. 33. Вид запущенной сцены со списком планет
Рис. 34. Вид игры
Рис. 35. Планета на орбите
Ссылки:

Содержание
Часть 5-1. Создание виртуальной Солнечной системы на основе XML-файла. Скрипт движения планет