Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Unity_в_действии_Джозеф_Хокинг_Рус.pdf
Скачиваний:
83
Добавлен:
21.06.2022
Размер:
26.33 Mб
Скачать

11.3. Обработка хода игры      291

Листинг 11.25. Реакция на гибель персонажа в сценарии UIController

...

Messenger.AddListener(GameEvent.LEVEL_FAILED, OnLevelFailed);

...

Messenger.RemoveListener(GameEvent.LEVEL_FAILED, OnLevelFailed);

...

private void OnLevelFailed() { StartCoroutine(FailLevel());

}

private IEnumerator FailLevel() { levelEnding.gameObject.SetActive(true);

levelEnding.text = "Level Failed"; ¬ Используем ту же самую текстовую метку, но с другим сообщением.

yield return new WaitForSeconds(2);

Managers.Player.Respawn();

Managers.Mission.RestartCurrent(); ¬ После двухсекундной паузы начинаем текущий уровень сначала.

}

...

Запустите игру и позвольте врагам сделать несколько выстрелов; появится сообщение о проигрыше. Прекрасная работа — теперь игрок может завершать уровни и начинать их заново в случае неудачного прохождения! Пришло время научить игру следить за успехами игрока.

11.3. Обработка хода игры

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

11.3.1. Сохранение и загрузка достижений игрока

Процессы сохранения и загрузки являются важными частями большинства игр. Для этой цели в Unity и Mono добавлена функциональность ввода-вывода. Но перед тем как мы начнем с ней работать, следует добавить в сценарии MissionManager и InventoryManager метод UpdateData(). Он будет функционировать по тому же принципу, что и в сценарии PlayerManager, предоставляя внешнему по отношению к диспетчеру коду возможность обновлять данные внутри диспетчера. Отредактированные версии диспетчеров даны в листингах 11.26 и 11.27.

Листинг 11.26. Метод UpdateData() в сценарии MissionManager

...

public void Startup(NetworkService service) { Debug.Log("Mission manager starting...");

_network = service;

UpdateData(0, 1); ¬ Отредактируйте эту строку, используя новый метод. status = ManagerStatus.Started;

292      Глава 11. Объединение фрагментов в готовую игру

}

public void UpdateData(int curLevel, int maxLevel) { this.curLevel = curLevel;

this.maxLevel = maxLevel;

}

...

Листинг 11.27. Метод UpdateData() в сценарии InventoryManager

...

public void Startup(NetworkService service) { Debug.Log("Inventory manager starting...");

_network = service;

UpdateData(new Dictionary<string, int>()); ¬ Инициализируем пустой список.

status = ManagerStatus.Started;

}

public void UpdateData(Dictionary<string, int> items) { _items = items;

}

public Dictionary<string, int> GetData() { ¬ Необходима функция чтения для сохранения данных. return _items;

}

...

Теперь, когда метод UpdateData() появился в разных диспетчерах, новый модуль кода позволит нам сохранять данные. Для сохранения нам потребуется процедура, называемая сериализацией данных.

ОПРЕДЕЛЕНИЕ  Сериализовать (serialize) означает перекодировать пакет данных в форму, допускающую сохранение.

Нашу игру мы сохраним в виде двоичных данных, но имейте в виду, что C# допускает также сохранение в виде текстовых файлов. Например, строки в формате JSON, с которыми вы работали в главе 9, представляли собой данные, сериализованные в виде текста. В предыдущих главах мы пользовались методом PlayerPrefs, но сейчас нам нужно сохранить локальный файл (метод PlayerPrefs предназначен для сохранения набора значений объемом до одного мегабайта). Создайте сценарий DataManager (см. следующий листинг).

ВНИМАНИЕ  В интернет-играх отсутствует доступ к файловой системе. Это сделано в целях безопасности, то есть интернет-игры не могут сохранять свое состояние в виде локальных файлов. Сохранение данных в этом случае осуществляется путем их отправки на сервер.

Листинг 11.28. Сценарий для диспетчера данных

using UnityEngine;

using System.Collections;

using System.Collections.Generic;

11.3. Обработка хода игры      293

using System.Runtime.Serialization.Formatters.Binary; using System.IO;

public class DataManager : MonoBehaviour, IGameManager { public ManagerStatus status {get; private set;}

private string _filename;

private NetworkService _network;

public void Startup(NetworkService service) { Debug.Log("Data manager starting...");

_network = service;

_filename = Path.Combine(

Application.persistentDataPath, "game.dat"); ¬ Генерируем полный путь к файлу game.dat.

status = ManagerStatus.Started;

}

public void SaveGameState() {

Dictionary<string, object> gamestate = new Dictionary<string, object>(); ¬ Словарь, который будет подвергнут сериализации.

gamestate.Add("inventory", Managers.Inventory.GetData());

gamestate.Add("health", Managers.Player.health);

gamestate.Add("maxHealth", Managers.Player.maxHealth);

gamestate.Add("curLevel",

Managers.Mission.curLevel);

gamestate.Add("maxLevel",

Managers.Mission.maxLevel);

FileStream stream = File.Create(_filename); ¬ Создаем файл по указанному адресу.

BinaryFormatter formatter

= new BinaryFormatter();

formatter.Serialize(stream, gamestate); ¬ Сериализуем объект Dictionary как

stream.Close();

содержимое созданного файла.

}

 

public void LoadGameState() {

if (!File.Exists(_filename)) { ¬ Переход к загрузке только при наличии файла.

Debug.Log("No saved game"); return;

}

Dictionary<string, object> gamestate; ¬ Словарь для размещения загруженных данных.

BinaryFormatter formatter = new BinaryFormatter(); FileStream stream = File.Open(_filename, FileMode.Open);

gamestate = formatter.Deserialize(stream) as Dictionary<string, object>;

stream.Close();

Managers.Inventory.UpdateData((Dictionary<string,

int>)gamestate["inventory"]); ¬ Обновляем диспетчеры, снабжая их десериализованными данными.

Managers.Player.UpdateData((int)gamestate["health"],

(int)gamestate["maxHealth"]);

Managers.Mission.UpdateData((int)gamestate["curLevel"],

294      Глава 11. Объединение фрагментов в готовую игру

(int)gamestate["maxLevel"]);

Managers.Mission.RestartCurrent();

}

}

В методе Startup() с помощью свойства Application.persistentDataPath, которое Unity предоставляет как место для сохранения файлов, генерируется путь к файлу. Конкретный путь к файлу на каждой платформе будет своим, но Unity рассматривает его отвлеченно с помощью статической переменной (кстати, в путь включаются данные из полей Company Name и Product Name, входящих в состав настроек Player Settings, поэтому, если нужно, отредактируйте эти переменные). Метод File.Create() создает двоичный файл; если вам нужен текстовый файл, воспользуйтесь методом

File.CreateText().

ВНИМАНИЕ  При конструировании пути для разных платформ используются разные разделители. В C# это обстоятельство учитывается с помощью поля Path.DirectorySeparatorChar.

Откройте сцену Startup и найдите объект Game Managers. Добавьте к этому объекту в качестве компонента сценарий DataManager, а затем вставьте новый диспетчер в сценарий Managers, как показано в листинге 11.29.

Листинг 11.29. Добавление диспетчера DataManager в сценарий Managers.cs

...

[RequireComponent(typeof(DataManager))]

...

public static DataManager Data {get; private set;}

...

void Awake() {

DontDestroyOnLoad(gameObject);

Data = GetComponent<DataManager>();

Player = GetComponent<PlayerManager>();

Inventory = GetComponent<InventoryManager>();

Mission = GetComponent<MissionManager>();

_startSequence = new List<IGameManager>(); ¬ Диспетчеры запускаются по порядку. _startSequence.Add(Player);

_startSequence.Add(Inventory); _startSequence.Add(Mission); _startSequence.Add(Data);

StartCoroutine(StartupManagers());

}

...

ВНИМАНИЕ  Так как диспетчер DataManager пользуется остальными диспетчерами (с целью их обновления), нужно сделать так, чтобы в загрузочной последовательности остальные диспетчеры фигурировали раньше.

Наконец, чтобы получить возможность пользоваться функциями в диспетчере Data­ Manager, добавим кнопки, как показано на рис. 11.9. Создайте две кнопки, сделав их потомками объекта HUD Canvas (а не всплывающего окна Inventory).