Вступление
Организация любого, хотя-бы малость серьезного проекта требует хорошей организации кода. Проекты, разрабатываемые в среде Unity3D не являются исключением и, по мере роста проекта, его организация может сыграть не малую роль в качестве исходного продукта.
В данной статье мы постарались не только описать такой подход к организации кода, как Singleton (в народе называемый паттерном проектирования), но и рассмотреть наиболее комфортные и правильные подходы к обработке событий и поговорить об удобности кода в целом.
Итак, в этой статье мы затронем следующие моменты:
- Введение
- Как работает Singleton
- Реализация Singleton в Unity3D
- Взаимодействие с Singleton
- О плюсах и минусах Singleton
- Немного практических примеров
- Заключение
Как работает Singleton
Прежде чем начать разбираться в схеме работы паттерна Singleton, необходимо понять что это. Singleton (Синглтон) — некий менеджер, через который производится управление игровыми скриптами. Как правило, синглтоны сохраняются от сцены к сцене без повторной реинициализации (наподобие глобального объекта).
На простейшем примере работу Singleton можно объяснить следующим образом:
В игре присутствуют глобальные объекты (менеджеры), которые будут находиться в игре всегда и могут быть доступны из любого скрипта, что может быть полезно для создания классов управления музыкой, сетевыми функциями, локализацией и всем тем, что используется в единственном экземпляре. Помимо менеджеров в игре будут использоваться и множественные объекты: интерфейсы, игровые персонажи и объекты игрового мира. Все эти объекты будут плотно взаимодействовать с нашими менеджерами для достижения конечной цели.
Рассмотрим для примера организацию работы в мобильной игре:
В нашем случае Singleton — это объект переходящий от сцене к сцене, служащий для управления всеми объектами определенного типа в рамках игровой сцены (игры в целом).
На схеме ниже мы изобразили схему работы на примере мобильной пошаговой онлайн-игры:
Чтобы иметь полную картину, рассмотрим архитектуру этой игры. В данном случае помимо объектов Singleton у нас будут присутствовать следующие элементы:
- Объекты для подгрузки Singleton (Так называемый Bootstrap-класс)
- Объекты игровой логики (объекты управления сценариями)
- Контроллеры (Например: контроллер игрока)
- Модели данных (объекты для сериализации данных, получаемых с сервера)
- Объекты интерфейса
- Прочие статические игровые объекты
Таким образом мы сможем создать удобную и чистую архитектуру проекта с которой в дальнейшем не возникнет сложностей при масштабировании.
Реализация Singleton в Unity3D
Для более легкого восприятия мы продолжим рассматривать архитектуру мобильной онлайн игры и посмотрим, как все что мы описали выше, будет выглядеть на практике.
Классы-Менеджеры
Основа всего метода проектирования — собственно сами классы менеджеры, которые находятся в игре в единственном экземпляре и могут быть вызваны в любой момент. Для создания такого класса менеджера мы можем описать следующий код:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//=============================================
// Audio Manager
//=============================================
public class AudioManager: MonoBehaviour {
public static AudioManager instance = null; // Экземпляр объекта
// Метод, выполняемый при старте игры
void Start () {
// Теперь, проверяем существование экземпляра
if (instance == null) { // Экземпляр менеджера был найден
instance = this; // Задаем ссылку на экземпляр объекта
} else if(instance == this){ // Экземпляр объекта уже существует на сцене
Destroy(gameObject); // Удаляем объект
}
// Теперь нам нужно указать, чтобы объект не уничтожался
// при переходе на другую сцену игры
DontDestroyOnLoad(gameObject);
// И запускаем собственно инициализатор
InitializeManager();
}
// Метод инициализации менеджера
private void InitializeManager(){
/* TODO: Здесь мы будем проводить инициализацию */
}
}
На примере выше мы создали основу для одного из игровых менеджеров (в нашем случае это менеджер Audio). Не обязательно проводить инициализацию через метод Start(). Вы также можете использовать для этого метод Awake(), чтобы ваш объект был готов еще до старта сцены.
Теперь мы допишем наш класс, чтобы он умел загружать и сохранять параметры звука и музыки в игре:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//=============================================
// Audio Manager
//=============================================
public class AudioManager: MonoBehaviour {
public static AudioManager instance = null; // Экземпляр объекта
public static bool music = true; // Параметр доступности музыки
public static bool sounds = true; // Параметр доступности звуков
// Метод, выполняемый при старте игры
void Start () {
// Теперь, проверяем существование экземпляра
if (instance == null) { // Экземпляр менеджера был найден
instance = this; // Задаем ссылку на экземпляр объекта
} else if(instance == this){ // Экземпляр объекта уже существует на сцене
Destroy(gameObject); // Удаляем объект
}
// Теперь нам нужно указать, чтобы объект не уничтожался
// при переходе на другую сцену игры
DontDestroyOnLoad(gameObject);
// И запускаем собственно инициализатор
InitializeManager();
}
// Метод инициализации менеджера
private void InitializeManager(){
// Здесь мы загружаем и конвертируем настройки из PlayerPrefs
music = System.Convert.ToBoolean (PlayerPrefs.GetString ("music", "true"));
sounds = System.Convert.ToBoolean (PlayerPrefs.GetString ("sounds", "true"));
}
// Метод для сохранения текущих настроек
public static void saveSettings(){
PlayerPrefs.SetString ("music", music.ToString ()); // Применяем параметр музыки
PlayerPrefs.SetString ("sounds", sounds.ToString ()); // Применяем параметр звуков
PlayerPrefs.Save(); // Сохраняем настройки
}
}
Итак, готово. Теперь наш менеджер аудио умеет загружать и сохранять настройки звуков и музыки. Теперь встает следующий вопрос о том, как мы можем это использовать. На примере ниже, мы продемонстрировали простой пример взаимодействия с менеджером:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//=============================================
// Audio Muter Class
//=============================================
public class AudioMuter : MonoBehaviour {
// Публичные параметры
public bool is_music = false; // Это объект с музыкой?
// Приватные параметры
private AudioSource _as; // Audio Source
private float base_volume = 1F; // Базовая громкость
// Инициализация компонента при старте игры
void Start () {
_as = this.gameObject.GetComponent<AudioSource> (); // Получаем компонент AS
base_volume = _as.volume; // Получаем базовую громкость из AS
}
// Каждый кадр мы проверяем параметры и устанавливаем громкость
void Update () {
// Для начала проверим, музыка это или нет
if (is_music) {
_as.volume = (AudioManager.music)?base_volume:0F;
} else {
_as.volume = (AudioManager.sounds)?base_volume:0F;
}
}
}
На примере выше мы создали компонент, позволяющий нам автоматически включать/отключать AudioSource на объекте на основе статичных полей music и sounds в нашем менеджере.
Bootstrap-класс
Допустим, что у вас уже существует несколько менеджеров. Для того, чтобы не выгружать их в каждую сцену как объект отдельно, вы можете создать так называемый Bootstrap-класс, который будет цеплять объекты из заранее созданных префабов. Обязательности в Boostrap-объекте нет, однако мы рекомендуем использовать его просто для вашего удобства.
Рассмотрим наш класс Boostrap-а:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//=============================================
// Game Classes Loader
//=============================================
public class GameLoader : MonoBehaviour {
// Ссылки на менеджеров
public GameObject game_manager; // Game Base Manager
public GameObject audio_manager; // Audio Manager
public GameObject lang_manager; // Language Manager
public GameObject net_manager; // Network Manager
// Метод пробуждения объекта (перед стартом игры)
void Awake () {
// Инициализация игровой базы
if (GameBase.instance == null) {
Instantiate (game_manager);
}
// Инициализация аудио менеджера
if (AudioManager.instance == null) {
Instantiate (audio_manager);
}
// Инициализация менеджера языков
if (LangManager.instance == null) {
Instantiate (lang_manager);
}
// Инициализация сетевого менеджера
if (NetworkManager.instance == null) {
Instantiate (net_manager);
}
}
}
Теперь мы можем использовать Boostrap и добавлять в него новые префабы менеджеров без необходимости их размещения на каждой сцене игры.
Модели данных
Использование моделей данных необязательно, однако вы можете создать их для быстрой обработки данных с сервера и хранения данных в клиенте без необходимости повторных запросов. (к примеру для кеширования данных о пользователях в игре).
В нашем случае после запроса к серверу мы будем выгружать полученные данные в модели и обрабатывать их данные. Рассмотрим простейшую модель данных:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class responceModel{
public bool complete = false; // Статус операции
public string message = ""; // Сообщение об ошибке (в случае если complete = false)
}
На примере выше у нас изображена модель данных, которая будет служить для обработки базовых статусов, получаемых с сервера в формате JSON. Таким образом, когда мы обращаемся к нашему игровому серверу мы получаем 2 вида ответа:
При успешном обращении мы получаем ответ следующего вида:
{
complete: true, // Статус операции
data: {} // Объект с запрошенными данными
}
А при ошибке мы получаем ответ следующего вида:
{
complete: false, // Статус операции
message: "" // Сообщение об ошике
}
Таким образом мы можем парсить ответ сервера при помощи JSON десериализации и нашей модели данных:
responceModel responce = JsonUtility.FromJson<responceModel>(request.text); // Парсинг JSON
if(responce.complete){
/* TODO: Делаем что-то с полученными данными */
Debug.Log(responce.data);
}else{
/* TODO: Выводим ошибку */
Debug.Log(responce.message);
}
Контроллеры
Контроллеры будут служить нам для работы множественных объектов в игре (к примеру, противники в игре, либо контроллер игрока). Контроллеры создаются самым обычным способом и цепляются на объекты в игре в качестве компонентов.
Пример простого контроллера игрока:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//=============================================
// PLAYER CONTROLLER
//=============================================
public class PlayerController : MonoBehaviour {
// Публичные объекты
[Header ("Player Body Parts")]
public GameObject[] hairs;
public GameObject[] faces;
public GameObject[] special;
// Инициализация компонента
void Start () {
}
// Апдейт фрейма
void Update () {
}
// Обновить на игроке его части тела
public void updateParts (){
// Работа с волосами
for (int i = 0; i < hairs.Length; i++) {
if (i == NetworkManager.instance.auth.player_data.profile_data.body.hairs) {
hairs [i].SetActive (true);
} else {
hairs [i].SetActive (false);
}
}
/* TODO: Тоже самое для других частей тела */
}
}
На примере выше в части кода, где мы обновляем части тела игрока, мы используем модель данных с информацией о профиле игрока, которая была непосредственно подключена в менеджере сети.
Рассмотрим данную строку:
if (i == NetworkManager.instance.auth.player_data.profile_data.body.hairs){
Здесь мы видим, что идет сравнение индекса в цикле с идентификатором волос в модели данных игрока. Данная модель представлена в экземпляре объекта менеджера сети (NetworkManager), где был инициализирован объект для работы с авторизацией (auth), внутри которого размещены модели данных (player_data => profile_data => body).
Взаимодействие с Singleton
Для взаимодействия с менеджерами мы будем использовать либо экземпляр объекта (instance), либо прямое обращение для статических параметров.
Пример работы с instance:
public bool _hair = NetworkManager.instance.auth.player_data.profile_data.body.hairs;
На примере выше мы использовали свойство instance для получения данных о волосах игрока в менеджере NetworkManager.
Пример прямого взаимодействия со static-параметрами:
public bool _sounds = AudioManager.sounds;
На примере выше мы обратились напрямую к статичному свойству sounds в менеджере AudioManager.
О плюсах и минусах Singleton
Плюсы:
+ Нет необходимости постоянной настройки и описаний полей скриптов в инспекторе
+ К менеджерам можно обращаться через свойство instance
+ Удобный рефакторинг кода
+ Компактность кода
Минусы:
— Сильная зависимость кода
— Доступ только к скриптам-менеджерам в единственном экземпляре
Немного практических примеров
Использование делегатов
Мы можем сделать наш код более отзывчивым, добавив в менеджеры функции-делегаты. Таким образом для каждой функции может быть создан метод обратного вызова (callback).
Рассмотрим данный пример:
// Задаем функции-делегаты
public delegate void OnComplete();
public delegate void OnError(string message);
// Создаем наш метод, использующий делегаты
public void checkNumber(int number, OnComplete success, OnError fail){
if(number<10){
success(); // Вызываем метод OnComplete
}else{
fail("Вы ввели число большее 10!"); // Вызываем метод с ошибкой
}
}
На простом примере выше мы создали метод, который вызываем функцию success, если параметр number был меньше 10 и функцию error, когда параметр был больше или равен 10 соответственно.
Использовать данный метод можно следующим способом:
public void testMethod(){
int _number = Random.Range(0,50); // Случайное число
// Вызываем созданный нами метод проверки числа
checkNumber(_number, (()=>{ // Здесь вызывается метод Success
/* TODO: Делаем что-то при успешном выполнении */
Debug.Log("Все хорошо!");
}), ((string text)=>{ // Здесь вызывается метод Fail
Debug.Log(text); // Выводим текст, полученный в аргументе Callback функции
testMethod(); // Перезапускаем метод до тех пор, пока число не станет <10
}));
}
Таким образом мы можем создавать код с управляемым результатом. Теперь мы плавно переходим к примеру использования вместе с Singleton.
Делегаты в связке с Coroutine в Singleton
Для наиболее удобного и правильного взаимодействия с сервером мы можем использовать связку Coroutine-функций и делегатов, тем самым получая возможность отправлять асинхронные запросы и обрабатывать ответ сервера. Ниже мы подготовили пример NetworkManager-а с использованием Coroutine-функций и делегатов.
Рассмотрим данный пример NetworkManager-а:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//=============================================
// Network Manager
//=============================================
public class NetworkManager : MonoBehaviour {
// Публичные параметры
public static NetworkManager instance = null; // Экземпляр менеджера
public static string server = "https://mysite.com/api"; // URL сервера
// Публичные ссылки на подобъекты менеджера
public APIAuth auth; // Объект авторизации
public APIUtils utils; // Объект утилит
// Инициализация менеджера
void Awake () {
// Проверяем экземпляр объекта
if (instance == null) {
instance = this;
} else if(instance == this){
Destroy(gameObject);
}
// Даем понять движку, что его не нужно уничтожать
DontDestroyOnLoad(gameObject);
// Инициализируем нашего менеджера
InitializeManager();
}
// Инициализация менеджера
public void InitializeManager(){
auth = new APIAuth (server + "/auth/"); // Подключаем подобъект авторизации
utils = new APIUtils (server + "/utils/"); // Подключаем подобъект утилит
}
}
//=============================================
// API Auth Manager
//=============================================
public class APIAuth{
// Приватные параметры
private string controllerURL = ""; // Controller URL
//=============================================
// Конструктор объекта
//=============================================
public APIAuth(string controller){
controllerURL = controller;
}
//=============================================
// Метод для авторизации
//=============================================
public delegate void OnLoginComplete();
public delegate void OnLoginError(string message);
public IEnumerator SingIn(string login, string password, OnLoginComplete complete, OnLoginError error){
// Формируем данные для отправки
WWWForm data = new WWWForm();
data.AddField("login", login);
data.AddField("password", password);
data.AddField("lang", LangManager.language);
// Отправляем запрос на сервер
WWW request = new WWW(controllerURL + "/login/", data);
yield return request;
// Обрабатываем ответ сервера
if (request.error != null) { // Ошибка отправки запроса
error ("Не удалось отправить запрос на сервер");
} else { // Ошибок при отправке не было
try{
responceModel responce = JsonUtility.FromJson<responceModel>(request.text);
if(responce.complete){
complete(); // Вызываем Success Callback
}else{
error (responce.message); // Do error
Debug.Log("API Error: " + responce.message);
}
}catch{
error ("Не удалось обработать ответ сервера");
Debug.Log("Ошибка обработки ответа сервера. Данные ответа: " + request.text);
}
}
}
/* TODO: Здесь будут остальные методы для работы с авторизацией */
}
//=============================================
// Теперь создаем подобъект утилит по образу и подобию авторизации
//=============================================
public class APIUtils{
private string controllerURL = "";
// Аналогичный конструктор класса
public APIUtils(string controller){
controllerURL = controller;
}
//=============================================
// Проверка версии клиента игры
//=============================================
public delegate void OnClientVersionChecked();
public delegate void OnClientVersionError(string message);
public IEnumerator CheckClientVersion(string version, OnClientVersionChecked complete, OnClientVersionError error){
// Создаем данные
WWWForm data = new WWWForm();
data.AddField("version", version);
data.AddField("lang", LangManager.language);
// Отправляем запрос
WWW request = new WWW(controllerURL + "/checkVersion/", data);
yield return request;
// Обрабатываем ответ
if (request.error != null) {
error ("Не удалось отправить запрос на сервер");
} else {
try{
responceModel responce = JsonUtility.FromJson<responceModel>(request.text);
if(responce.complete){
complete();
}else{
error (responce.message);
Debug.Log("API Error: " + responce.message);
}
}catch{
error ("Не удалось обработать ответ сервера");
Debug.Log("Ошибка обработки ответа сервера. Данные ответа: " + request.text);
}
}
}
}
Теперь мы можем использовать это по назначению:
// Простая функция для вызова проверки
public void checkMyGame(){
StartCoroutine(NetworkManager.instance.utils.CheckClientVersion(Application.version,
(()=>{ // Если все прошло успешно
/* TODO: Здесь мы выполняем загрузку игры после успешной проверки версии игры */
}), ((string msg) => { // Если возникла ошибка
/* TODO: Здесь мы просим пользователя обновить версию клиента игры */
Debug.Log(msg);
})));
}
Таким образом, вы можете выполнять код NetworkManager и управлять его методами при помощи Callback-функций из любой сцены игры.
Заключение
Вообще, тема Singleton-ов и паттернов в целом в рамках проектов на Unity3D заслуживает отдельной книги и рассказать все в одной статье не получится. Ниже мы прикрепили несколько полезных материалов, где вы можете почитать об этом подробнее.
Список полезных материалов:
Комментарии (30)
Igor_Sib
07.11.2017 16:43Проще сделать базовый Singleton класс, например как здесь:
brightreasongames.com/the-singleton-post
Потом наследовать примерно так:
public class SomeSingleton : SingletonBehaviour<SomeSingleton>
2morrowMan
07.11.2017 19:03// Метод, выполняемый при старте игры void Start () { // Теперь, проверяем существование экземпляра if (instance == null) { // Экземпляр менеджера был найден instance = this; // Задаем ссылку на экземпляр объекта } else if(instance == this){ // Экземпляр объекта уже существует на сцене Destroy(gameObject); // Удаляем объект } // Теперь нам нужно указать, чтобы объект не уничтожался // при переходе на другую сцену игры DontDestroyOnLoad(gameObject); // И запускаем собственно инициализатор InitializeManager(); }
Что будет когдаinstance != null
?CodeBits Автор
07.11.2017 20:26Будет instance = this;
Тут идет два варианта: либо instance=null, либо instance=this.
Эта часть кода проверяет на дубликаты при переходе на другую сцену.2morrowMan
07.11.2017 22:55По-моему, ваше уловие
else if(instance == this)
не выполнится никогда, так как, если instance не null, то и не this точно, так как метод Start класса MonoBehaviour, движок вызывает только один раз при инициализации компонента.CodeBits Автор
08.11.2017 07:34Мой косяк. Нужно было сделать немного по другому.
if(instance!=null) Destroy(gameObject); instance = this;
DyadichenkoGA
08.11.2017 14:40Даже забьём на то, что по идее нужно разрушать компонент, а в этом случае уничтожается геймобжект. Последний зашёл, значит ты и прав? Реиницилизация в каждой сцене где присутствует? Зачем?
Я вообще против синглтонов монобехов. Но если уж юзать, то есть вот такая реализация (в самом низу), она даже лочит где надо. wiki.unity3d.com/index.php/Singleton
Но в целом синглтоны монобехи не нужны в большинстве случаев. Да и не удобны в целом.DyadichenkoGA
08.11.2017 14:48Правда у меня к этой реализации дженериком, есть ещё проверка от дурака, чтобы нельзя было навесить компонентой и т.п.
Arugin
07.11.2017 20:26Стоит отметить, что движок называется Unity. А unity3d — это домен, на котором хостится сайт.
CodeBits Автор
07.11.2017 20:27Я понимаю что он называется Unity. Во многих ресурсах раньше он фигурировал как Unity3D, так что осталась привычка. Даже где-то очень давно на официальном сайте видел Unity3D название фигурировало, но потом стало просто Unity.
avdept
07.11.2017 21:22Синглетон не добро и не зло. Есть кейсы в которые он приносит удобства, есть наоборот, проблемы. Один из примеров — объект настроек(settings) вашего приложения(игры), к которому вы имеете доступ отовсюду, без лишних телодвижений.
CodeBits Автор
08.11.2017 07:36Соглашусь, в вебе мне достаточно симпатизирует синглтон в качестве основы для веб-сайтов. Тот же CodeIgniter мне безумно нравится. Однако в Unity использование Singletone сплошь и рядом не есть хорошо из-за большого количества связываний. Как по мне в рамках разработки игры он больше подходит для чего-то глобального.
ShinRa
08.11.2017 08:20Те кто пишут что синглтон зло, просто не умеют его использовать. Начитаются всяких статей про антипаттерны и везде вставят свое «не используйте синглтон». Именно в Unity этот паттерн полезен.
CodeBits Автор
08.11.2017 09:13Тут скорее зависит от того, как его использовать. Если использовать без знания и понимания зачем это все нужно — то любой паттерн будет бесполезен. Сам по себе Unity впринципе имеет удобную архитектуру на основе компонентов — для простых проектов и этого хватит с лихвой.
ShinRa
08.11.2017 14:21Вот, «компоненты» ключевое слово, помнится кто-то даже говорил, что в Unity не ООП, а КОП — компонентно-ориентированное программирование. Попытки применять чистое ООП и различные паттерны в создании игр на Unity может привести к головной боли. Я для себя принял правило не пытаться раздувать архитектуру и максимально использовать готовые решения, а так же активно пользоваться документацией и туториалами на офсайте, очень много там хитростей полезных разобрано.
DyadichenkoGA
08.11.2017 14:30В Unity он просто менее опасен, так как там нечасто кто-то работает в много потоков. У самого паттерна есть очевидные плюсы и минусы. Основной при неправильном использовании — это нереальная связность системы. В случае многопоточности с синглтонами очень неудобно работать. Если забыть про дедлоки и сделать нормальные блокировки и т.п., то всё равно сложно отследить порядок морфирования при дебаге. Сингтлон просто позволяет меньше думать над тем, как архитектурно красиво прокинуть некоторые сервисы, которые скорее всего будут в единичном экземпляре. Я использую синглтоны часто, но это не отменяет огромного количества недостатков. Просто, на мой взгляд, основное преимущество — это скорость разработки. За которую правда придётся платить в будущем.
paxantre
08.11.2017 09:10Вот пример реализации синглтона в юнити, без использования объекта на сцене, соответственно, при переходе между сценами не надо следить за тем чтобы объект не был уничтожен. Класс бутстрапер тоже не нужен, так как синглтон сам создает свой экземпляр при первом к нему обращении.
public class SomeSingleton : ScriptableObject { private static SomeSingleton _instance; public SomeSingleton () { if (_instance != null) { return; } _instance = this; } public static SomeSingleton Instance { get { if (_instance == null) { ScriptableObject.CreateInstance<SomeSingleton>(); } return _instance; } } }
CodeBits Автор
08.11.2017 09:11Интересный пример, тоже имеет место быть. Но мне лично удобней использовать Bootstraper-ы для контроля + у меня не на всех сценах нужно проводить обращение к Singleton, но сам по себе он должен выполнить задачу при инициализации (например загрузить настройки языка).
DyadichenkoGA
08.11.2017 14:20Не имеет места быть ни ваш, ни пример из статьи. Это просто не синглтоны. Так как с таким описанием класса я могу спокойно создать много инстанстов. А реализация выше творить будет вообще полный треш с точки зрения мусора. У синглтона должен быть приватный конструктор, как минимум. В случае монобеха, там всё вообще сложнее. А описанные реализации — это очень опасные и даже вредные конструкции.
paxantre
09.11.2017 00:40Ваш пример реализации в студию!
DyadichenkoGA
09.11.2017 00:57Вообще ссылку я скинул выше. Я терпеть не могу монобеховские синглтоны, так как они требуют слишком много обвязки вокруг. Но скажем если взять пример клиента из моей статьи про Unet то, вот не монобеховский синглтон
github.com/Nox7atra/Simple-Unity-Client-Server/blob/master/Assets/Scripts/Networking/Client.cs
В случае с монобеховским в том же репозитории есть не совсем синглтон, но шаблонный (я просто немного упростил пример выше, так как многопоточностью в коде и не пахло, а пример выше учитывает и её) Он находится в каталоге Utils
dimitrimus
08.11.2017 11:18А зачем инициализировать объект, если у нас уже есть инстанс? Ну то есть при вызове Destroy, мы дальше пойдём инициализировать объект. Это баг или фича?
CodeBits Автор
08.11.2017 12:12Вызов InitializeManager(); не обработается после Destroy, т.к. объекта уже не будет. Так же как в PHP после Exit код не будет обрабатываться
Kotozayac
09.11.2017 08:31Кроме того, что синглтоном называют некий аггрегирующий класс, ничего не понял. Дочитал до слова "responce" и кажется понял — почему.
Igor_Sib
09.11.2017 08:41Singleton это не агрегирующий класс, это класс экземпляр которого может быть только один. Ну и средство быстрого глобального доступа к этому экземпляру (public static field или property).
redfs
CodeBits Автор
Опечатался про проектирование. Исправил.
Да, думаю следует сказать, что использование синглтона сплошь и рядом зло.
Sdima1357
А зла не хватает.Сильно и регулярно.
CodeBits Автор
Вот на самом деле смешная ситуация с программистами. Работали у меня несколько человек, дак банально даже синглтон не могли использовать, но при этом считали себя высококвалифицированными специалистами — лезли в Yii и в Laravel, но лезли видимо в слепую без представления. Понятное дело что такие люди проработали не долго, но суть в том, что таких кадров встречается много. Даже в плане кода смотришь порой продукты крупных компаний, а там не то что синглтоном не пахнет, там даже ООП как такового принципы не используются.
Sdima1357
Синглтон — средство, как например средство от тараканов. Не стоит добавлять его в каждое блюдо, но для против тараканов он эффективнее тапка.Мораль — нет плохих средств, но есть плохие программисты. Главное, чтоб код был симпатичный и понятный.