Сегодня хотелось бы кратко рассказать об использовании событий для избавления от мусора и предоставления комплексной и динамической функциональности коду. Следует заметить, что данная статья ориентирована скорее на начинающих, которым хочется узнать больше о системах событий.
Разбирать все это дело мы будем на примере системы управления звуком. Данная система будет позволять нам включать/отключать музыку и звуки в настройках игры.
Прежде чем мы создадим класс-менеджер, который будет управлять настройками звука и музыки, мы создадим простой сериализуемый класс. Он будет использоваться в качестве модели данных для сохранения в JSON.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//=============================================
// AudioSettingsModel
// @usage model for audio settings
//
// Developed by CodeBits Interactive
// https://cdbits.net/
//=============================================
[System.Serializable]
public class AudioSettingsModel
{
public bool music = true; // Флаг, отвечающий за музыку
public bool sounds = true; // Флаг, отвечающий за звуки
}
Теперь можно приступить к написанию класса-менеджера. Его мы будем устанавливать на самой первой сцене. Данный менеджер будет глобальным объектом и не будет удаляться при переходе со сцены на сцену.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
//=============================================
// Audio Manager
// @usage works with audio settings
//
// Developed by CodeBits Interactive
// https://cdbits.net/
//=============================================
[AddComponentMenu("Game Managers/Audio Manager")]
public class AudioManager : MonoBehaviour{
// Публичные параметры
public static AudioManager instance = null; // Инстанс менеджера
public static AudioSettingsModel settings = null; // Модель аудио настроек
private static string _settings_path = ""; // Путь к файлу настроек аудио
// Инициализация менеджера
void Awake(){
// Устанавливаем путь для сохранения настроек
_settings_path = Application.persistentDataPath + "/audioSettings.gdf";
// Проверяем, задан ли инстанс нашего менеджера
if (instance == null){ // Инстанс не задан
instance = this; // Установить в инстанс текущий объект
}
// Устанавливаем параметр, который указывает на то,
// что данный объект не должен удаляться при выгрузке
// уровня
DontDestroyOnLoad(gameObject);
// Инициализируем настройки нашего менеджера
InitializeSettings();
}
// Инициализация менеджера
private void InitializeSettings(){
// Если модель настроек не задана
if (settings == null) settings = new AudioSettingsModel(); // Создаем новую модель
if (File.Exists(_settings_path)){ // Если существует файл с настройками
loadSettings(); // Загружаем файл настроек звука
}
}
// Загрузить аудио настройки
public void loadSettings(){
string _data = File.ReadAllText(_settings_path); // Считываем весь текст из файла
settings = JsonUtility.FromJson<AudioSettingsModel>(_data); // Десериализуем его в текущую модель
}
// Сохранить аудио настройки
public void saveSettings(){
string _json_data = JsonUtility.ToJson(settings); // Сериализуем текущие настройки из модели
File.WriteAllText(_settings_path, _json_data); // Сохраняем в наш файл
}
// Создаем делегаты для нашего события, которое в дальнейшем
// будет использоваться для отслеживания изменений настроек аудио
public delegate void AudioSettingsChanged(); // Добавить новый делегат
public event AudioSettingsChanged OnAudioSettingsChanged; // Создать на его основе событие
// Включить/выключить звуки
public void toggleSounds(bool enabled){
settings.sounds = enabled; // Изменить настройки звуков в текущей модели
saveSettings(_settings_path, settings); // Сохранить настройки
if (OnAudioSettingsChanged != null) OnAudioSettingsChanged(); // Вызвать наше событие
}
// Включить/выключить музыку
public void toggleMusic(bool enabled){
settings.music = enabled; // Изменить настройки музыки в текущей модели
saveSettings(_settings_path, settings); // Сохранить настройки
if (OnAudioSettingsChanged != null) OnAudioSettingsChanged(); // Вызвать наше событие
}
}
Теперь, когда менеджер готов, вы можете создать на вашей начальной сцене пустой объект и назвать его, к примеру "_AUDIO_MANAGER", после чего добавить на него наш класс-менеджер. Сделать это можно просто вызвав меню добавления компонента на объекте и выбрав «Game Managers» => «Audio Manager».
После этого, нам необходимо написать компонент, который мы будем пристыковывать к каждому объекту с AudioSource.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//=============================================
// Audio Muter
// @usage on/off audio sources on objects
//
// Developed by CodeBits Interactive
// https://cdbits.net/
//=============================================
[AddComponentMenu("Audio/Audio Muter Component")]
public class AudioMuter : MonoBehaviour {
// Публичные параметры компонента
public bool is_music = false; // Данный флаг дает понять нашему классу, является ли AudioSource звуком или музыкой.
// Приватные параметры
private AudioSource _as; // AudioSource
private float _base_volume = 1F; // Базовая громкость AudioSource
// Инициализация объекта
void Start(){
// Получаем компонент AudioSource и его изначальную громкость
_as = this.gameObject.GetComponent<AudioSource>(); // Получить компонент
_base_volume = _as.volume; // Получить базовую громкость
// Здесь мы добавляем слушатель, который будет выполнять метод _audioSettingsChanged,
// когда настройки музыки/звуков были изменены
AudioManager.instance.OnAudioSettingsChanged += _audioSettingsChanged; // Установить
// Ну и на старте мы должны проверить текущее состояние звуков/музыки
_audioSettingsChanged();
}
// При уничтожении объекта
void OnDestroy(){
AudioManager.instance.OnAudioSettingsChanged -= _audioSettingsChanged; // Уничтожаем слушатель
}
// Данный метод служит для включения/отключения громкости AudioSource
private void _audioSettingsChanged(){
if (is_music)
_as.volume = (AudioManager.settings.music) ? _base_volume : 0F;
if (!is_music)
_as.volume = (AudioManager.settings.sounds) ? _base_volume : 0F;
}
}
Таким образом мы можем управлять звуками/музыкой в игре. Данный пример не в коем случае не говорит, как следует делать это правильно, а лишь демонстрирует работу системы событий и слушателей в Unity3D.
И напоследок хочется поговорить о том, что мы сейчас использовали. В примере ниже, был объявлен делегат, из которого был создан слушатель:
public delegate void AudioSettingsChanged();
public event AudioSettingsChanged OnAudioSettingsChanged;
Вы можете установить выполнение слушателя при определенных условиях и цеплять к ним определенные методы, которые будут выполняться при достижении этих условий.
А при помощи делегатов, на основе которого мы создали слушатель, вы можете создавать Callback-функции. Особенно полезно это может быть для асинхронных методов (к примеру при отправке асинхронных POST-запросов).
Надеюсь, вам пригодится мой небольшой опыт в этом деле и вы сможете применить данный пример для своих проектов. Также буду рад ответить на ваши вопросы (если кому-то что-то непонятно).
Комментарии (6)
CodeBits Автор
15.06.2018 12:32Про naming guidelines прошу прощения. Профессиональная болячка при работе в команде где каждый использует свой предпочитаем способ и я использую вырванные куски кода из своего проекта.
В остальном по замечаниям хорошо. Спасибо за советы!
sith
15.06.2018 18:29Спасибо за статью. Пара вопросов:
1. Зачем нужен MonoBehaviour ещё и как Singleton для AudioManager?
2. Почему бы
public delegate void AudioSettingsChanged(); public event AudioSettingsChanged OnAudioSettingsChanged;
не заменить в данном случае на:
public Action OnAudioSettingsChanged;
Goldseeker
15.06.2018 22:03+1Manager'ы, повсеместная статика, класс с кучей ответсвенностей, Monobehaviour'ы, которые вполне могут быть и обычными классами, кастомные делегаты вместо Action. Это очень плохой код и очень плохое решение поставленной задачи.
Feelnside
16.06.2018 14:02Чтобы комментарий был ещё и полезным, было бы грамотно расписать:
— почему повсеместная статика плохо, в каких ситуациях она может привести к проблемам;
— почему классы с кучей ответственностей не следует практиковать, чем их можно бы заменить;
— объяснить, в каких случаях не следует наследовать Monobehaviour и почему;
— в чем разница между делегатами и экшенами, почему в данной ситуации лучше использовать Action вместо делегатов.
Ну а так получается автор и читатели так и не поймут, почему код очень плохой. Я бы конечно попытался расписать и ответить на данные вопросы, но я не имею достаточного опыта, чтобы учить писать код, потому было бы интересно и более важно — полезно почитать обоснование ваших заметок.
Sinatr
У меня всегда вызывает некоторое недоверие код, автор которого не следует naming guidelines.
Начинающих Unity (как я) или начинающих C#? Если второе, то советую обратить внимание на то, что обьявлять свои делегаты уже давно не нужно, события объявляются используя И опять же, стоит следовать guidelines, как минимум добавив sender к вашему делегату, а иначе придется внутри обработчика опять обращаться к синглтону, что добавляет связности и усложняет тестирование.Почему используете поля вместо свойств для публичных членов? Это какая-то особенность Unity?
CodeBits Автор
Гайды майкрософта использовать боюсь, поскольку есть такая штука что в Unity есть некоторые различия и на мобильных устройствах все любит отваливается. А сверить как то поленился.(((