Эта заметка про программирование и будет полезна совсем начинающим разработчиками игр на движке Unity. В ней рассмотрим работу с файлом в формате .json в проекте для платформы Android. А также реализуем смену языка интерфейса.
Загрузка из файла (и соответственно сохранение в файл) будет полезной для хранения настроек игры, переключения языка, загрузки ассета (текста или картинки) и даже переноса каких-то внутриигровых данных между сценами.
Создадим скрипт контроллера параметров C# и прикрепим его к объекту, который будет с нами в течении всей сцены (например к камере). Назовём этот скрипт ConfController и реализуем паттерн Singleton:
public static ConfController _confControllerInstance = null;
Допустим у нас будет файл опций options.json в формате JSON следующего содержания: {"Music":0.5,"Effects":0.5,"Language":"English"} . Этот файл следует положить следуя рекомендации из документации Unity в проект в папку Assets\StreamingAssets. Опишем этот файл в нашем скрипте-контроллере:
public OptionsConfFile optionsConfFile;
[System.Serializable]
public class OptionsConfFile {
public float Music, Effects;
public string Language; }
Проделаем то же самое с другими файлами, например характеристиками и инвентарём персонажа. Для создания вложенности вроде {"char1":{"hp":5,"armor":..}, "char2":..} ) создайте [System.Serializable] класс Char с полями hp, armor итд, который используйте как тип поля в классе настроек персонажа.
Для того чтобы можно было откатиться к изначальным настройкам переименуем файл options.json в defaultoptions.json. То же самое проделаем с остальными файлами. Так легко создать новый профиль игрока, а обнулить прогресс можно просто удалив текущие файлы настроек.
Добавим в контроллер метод загрузки из файла:
public void LoadConfFile(string file) {
switch (file) {
case ... ТУТ НАЗВАНИЯ ФАЙЛОВ
case "options":
if (File.Exists(Application.streamingAssetsPath + "/" + file + ".json")) {
_confControllerInstance.optionsConfFile = JsonUtility.FromJson<OptionsConfFile>(File.ReadAllText(Application.streamingAssetsPath + "/" + file + ".json")); }
else {
_confControllerInstance.optionsConfFile = JsonUtility.FromJson<OptionsConfFile>(File.ReadAllText(Application.streamingAssetsPath + "/" + "default" + file + ".json")); }
break; } }
И соответственно метод сохранения:
public void SaveConfFile(string file) {
switch (file) {
case ... ТУТ НАЗВАНИЯ ФАЙЛОВ
case "options":
File.WriteAllText(Application.streamingAssetsPath + "/" + file + ".json", JsonUtility.ToJson(_confControllerInstance.optionsConfFile));
break; } }
Чтобы игра знала какие параметры использовать до момента её начала сделаем загрузку в стандартном методе Awake:
private void Awake() {
if (_confControllerInstance == null)
_confControllerInstance = this;
_confControllerInstance.LoadConfFile("options");
_confControllerInstance.SaveConfFile("options"); }
Обратите внимание, что я загрузил и сразу сохранил настройки - это для первого запуска, чтобы создались текущие файлы.
Смена языка
Создадим скрипт C# для обработок кнопок интерфейса, назовём его например PanelController и прикрепим его тоже к камере. В нём объявим событие смены языка:
public event Action LanguageChanged;
и метод, который меняет настройки и вызывает событие смены языка:
public void OnDropDownChanged(Dropdown dropDown) {
if (dropDown.value == 0) {
ConfController._confControllerInstance.optionsConfFile.Language = "English"; }
else {
ConfController._confControllerInstance.optionsConfFile.Language = "Russian"; }
ConfController._confControllerInstance.SaveConfFile("options");
LanguageChanged?.Invoke(); }
Создадим скрипт C# и прикрепим его к каждому объекту с текстовым полем (например на кнопку с названием TutorialButton). В этом скрипте подпишемся в стандартном методе Start на событие смены языка, чтобы язык менялся одновременно во всех местах.
#pragma warning disable 0649
[SerializeField] private PanelController _panelController;
private void Start() {
SetText();
_panelController.LanguageChanged += SetText; }
public void SetText() {
if (ConfController._confControllerInstance.optionsConfFile.Language == "English") {
if (name == "TutorialButton") {
GetComponent<Text>().text = "Use...
Обратите внимание, что я отключил предупреждение о том, что приватное поле не принимает значение, поэтому повышается вероятность забыть перетащить в инспекторе камеру с прикреплённым скриптом PanelController в место поля на каждом объекте с мультиязычным текстом.
Если удивили в листинге синюю звёздочку и не поняли что это, то это Яндекс.Дзен автоматически превращает # без пробела (в "# endif" я поставил пробел тоже, так как Дзен запрещает использовать хэштег больше 10 раз) в #
Загрузка из файла на Android
В Unity-эдиторе всё работает как надо, файлы хорошо грузятся и меняются. Но после сброса .apk на android-смартфон файлов нет. Проблема заключается в том, что Unity на Android не работает напрямую с файлами (я уже давал ссылку на документацию где описаны отличия платформ - там всего несколько строк текста, но это важно).
Поэтому немного изменим методы загрузки и сохранения:
#if UNITY_EDITOR
_confControllerInstance.optionsConfFile = JsonUtility.FromJson<OptionsConfFile>(File.ReadAllText(Application.streamingAssetsPath + "/" + file + ".json"));
Debug.Log("load file");
# endif
#if UNITY_ANDROID
WWW wwwfile = new WWW(Application.streamingAssetsPath + "/" + file + ".json");
while (!wwwfile.isDone) { }
var filepath = string.Format("{0}/{1}", Application.persistentDataPath, "tmp.json");
File.WriteAllBytes(filepath, wwwfile.bytes);
_confControllerInstance.optionsConfFile = JsonUtility.FromJson<OptionsConfFile>(File.ReadAllText(filepath));
# endif
И соответственно сохранения:
#if UNITY_EDITOR
File.WriteAllText(Application.streamingAssetsPath + "/" + file + ".json", JsonUtility.ToJson(_confControllerInstance.optionsConfFile));
# endif
#if UNITY_ANDROID
string filepath = string.Format("{0}/{1}", Application.persistentDataPath, file + ".json");
File.WriteAllText(filepath, JsonUtility.ToJson(_confControllerInstance.optionsConfFile));
# endif
Если будете копировать из моего примера, то временные переменные выносите из блока switch тоже в директивах # if UNITY_ANDROID и # endif.
Также надо помнить, что читать из файла, который был в StreamingAssets надо с помощью класса WWW. Но когда я загрузил и сохранил файл (в Awake), то повторно чтобы загрузить уже сохранённый файл обращаться к нему надо уже просто по пути: string.Format("{0}/{1}", Application.persistentDataPath, file + ".json") или Path.Combine(Application.persistentDataPath, file + ".json").
_____________________________________
Надеюсь новичкам окажется полезной эта заметка (это я замечу по большим пальцам вверх), что будет мотивировать меня продолжать публикации на тему Unity.
Возможно Вам будет полезно узнать как импортировать модели из Magica Voxel в Blender и Unity. Там я вставил в Unity воксельную модель, нарисованную в прямом эфире, и полазил по ней, заскриптовав простой контроллер персонажа.
_____________________________________
Upd: Смена языка доступна "из коробки" начиная с версии Unity 2020.2 . Для этого есть специальный пакет Localization. Он добавит в Player Settings соответствующую вкладку и соответственно открывает настройку для переключения языка одной строкой кода (локализации текстов вносятся прям в текстовых полях). Но это не отменяет сказанного выше, так как оно в общем-то подходит и для других случаев.
#unity3d #разработка игр #android #c#