Форум программистов, компьютерный форум, киберфорум
Unity, Unity3D
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.74/47: Рейтинг темы: голосов - 47, средняя оценка - 4.74
325 / 114 / 7
Регистрация: 01.05.2011
Сообщений: 283
Записей в блоге: 3
1

[Unity, лайфхак] Как сделать адекватный синглтон (singleton или уникальный для сцены) компонент

05.03.2017, 20:27. Показов 9061. Ответов 13
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Что такое singleton – я надеюсь знают все.

Какие цели лично я преследую при использовании синглтона?
Во-первых – легкий доступ к экземпляру класса в коде любого компонента.
Во-вторых – запрет на добавление нескольких компонентов в сцену, если по логике этот компонент должен быть уникальным (настройки, BestScore…..). Причем запрет должен действовать и на уровне редактора, и на уровне кода.
В-третьих – все должно быть понятно, поведение компонента естественно и ожидаемо, логика синглтона не должна мешать нормальной работе компонента и не должна сказываться на производительности готового приложения.

В интернете достаточно много примеров по реализации синглтона на Unity. С ленивой инициализацией, с инициализацией в Awake или Reset, с кэшированием ссылок и т.д. и т.п. Приведу пример своего компонента:
Код на С#
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Callbacks;
#endif
 
[ExecuteInEditMode]
public abstract class UniqueComponentAtScene<T> : MonoBehaviour where T : UniqueComponentAtScene<T>
{
    private static readonly List<T> _instances = new List<T>();
    public static T Instance {
        get {
            var inst = _instances.FindAll(x => x.GetType() == typeof (T));
            var count = inst.Count;
            if (count == 1)
                return inst[0];
            return null;
        }
    }
 
    internal static bool CheckUniqueness(Behaviour component)
    {
        var t = component.GetType();
        var result = Resources.FindObjectsOfTypeAll(t);
#if UNITY_EDITOR
        result = result.Where(x => !EditorUtility.IsPersistent(x)).ToArray();
#endif
        return result.Length == 1;
    }
 
    [SerializeField, HideInInspector]
    private bool _isFirstInstance;
 
    protected virtual void Awake()
    {
        // If scene has any Behaviour of current type - destroy this component, else add it to instances collection
 
        // First, check ref collection
        if (_instances.All(x => x.GetType() != GetType()))
        {
            // Second, check scene
            if (CheckUniqueness(this))
                _isFirstInstance = true;
 
            if (_isFirstInstance)
            {
                _instances.Add((T)this);
                return;
            }
        }
 
        Invoke("Delete", 0);
    }
 
    protected virtual void OnDestroy()
    {
         var t = (T)this;
        if (_instances.Contains(t))
            _instances.Remove(t);
    }
 
    private void Delete()
    {
        DestroyImmediate(this);
    }
}


Все это, конечно, прекрасно. И пока не сильно отличается от того, что можно найти на первой странице гугла по запросу «синглтон в юнити». Но я не увидел, чтобы авторы статей рассматривали 2 проблемы, с которыми столкнулся я:
1) При пересборке проекта (читай – при ЛЮБОМ изменении в любой части кода) коллекция _instances отчищается, т.к. класс UniqueComponentAtScene не является сериализуемым. А быть он таким не может, потому что нам заранее неизвестно, насколько сложные будут наследующие классы. Возможно их нельзя будет сделать сериализуемыми в принципе. И «ближайший» вызов метода Awake, где _instances заполняется, произойдет только в случае запуска проекта в редакторе.
Т.е. поведение компонента UniqueComponentAtScene – НЕ ОЖИДАЕМО, и заставляет каждый раз жмакать кнопку Play в редакторе. Можно забыть про это и словить внезапную ошибку NullReferenceException.
2) Если наследующий UniqueComponentAtScene класс1 будет иметь атрибут RequireComponent с указанием на класс2, который тоже является наследником UniqueComponentAtScene – то произойдет ошибка при добавлении дубля компонента класса1 на сцену.

C#
1
2
3
4
5
6
7
8
public class Class2 : UniqueComponentAtScene<Class2>
{
}
 
[RequireComponent(typeof(Class2))]
public class Class1 : UniqueComponentAtScene<Class1>
{
}
Заключается ошибка в том, что после добавления Class1 на сцену, где уже есть Class1, движок подтянет Class2 – и добавит на GO. После этого для каждого из новых компонентов произойдет вызов Awake (т.к. синглтон то у нас ExecuteInEditMode), что должно удалить оба компонента. Ага, щаааззззз. Юнити говорит, что Class2 удалить нельзя, потому что Class1 ссылается на него. Хотя Class1 удаляет спокойно. И повлиять на это мы не можем, т.к. не управляем порядком вызова Awake для отдельных компонентов в сцене.

Ну и после такого долгого введения перейдем непосредственно к лайфхаку Для решения первой проблемы я использую такой хитрый атрибут, как DidReloadScripts. Упоминаний про него я нигде не встречал и вообще наткнулся совершенно случайно. Он позволяет пометить статичный метод, который будет вызван в редакторе после пересборки проекта или при первой загрузке редактора. После этого нам надо найти всех наследников UniqueComponentAtScene с помощью рефлексии и найти экземпляры этих классов на сцене. Когда все экземпляры, или компоненты, или синглтоны, найдены – вызываем для них Awake.
Правда здесь тоже есть нюанс – почему-то метод, расположенный в абстрактном генерик классе и помеченный атрибутом DidReloadScripts, вызывает ошибку "TypeLoadException: A type load exception has occurred". Толи баг, толи моих знаний языка не хватает для понимания… В общем, необходимо добавить прямо в файл с кодом UniqueComponentAtScene скриптуемый объект, в котором уже прописать нужный метод с атрибутом DidReloadScripts. Т.е. полный код файла UniqueComponentAtScene.cs будет выглядеть так:

Полный код примера на С#
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Callbacks;
#endif
 
[ExecuteInEditMode]
public abstract class UniqueComponentAtScene<T> : MonoBehaviour where T : UniqueComponentAtScene<T>
{
    private static readonly List<T> _instances = new List<T>();
    public static T Instance {
        get {
            var inst = _instances.FindAll(x => x.GetType() == typeof (T));
            var count = inst.Count;
            if (count == 1)
                return inst[0];
            return null;
        }
    }
 
    internal static bool CheckUniqueness(Behaviour component)
    {
        var t = component.GetType();
        var result = Resources.FindObjectsOfTypeAll(t);
#if UNITY_EDITOR
        result = result.Where(x => !EditorUtility.IsPersistent(x)).ToArray();
#endif
        return result.Length == 1;
    }
 
    [SerializeField, HideInInspector]
    private bool _isFirstInstance;
 
    protected virtual void Awake()
    {
        // If scene has any Behaviour of current type - destroy this component, else add it to instances collection
 
        // First, check ref collection
        if (_instances.All(x => x.GetType() != GetType()))
        {
            // Second, check scene
            if (CheckUniqueness(this))
                _isFirstInstance = true;
 
            if (_isFirstInstance)
            {
                _instances.Add((T)this);
                return;
            }
        }
 
        Invoke("Delete", 0);
    }
 
    protected virtual void OnDestroy()
    {
         var t = (T)this;
        if (_instances.Contains(t))
            _instances.Remove(t);
    }
 
    private void Delete()
    {
        DestroyImmediate(this);
    }
}
 
#if UNITY_EDITOR
internal sealed class UniqueComponentAtSceneReloader : ScriptableObject
{
    [DidReloadScripts]
    private static void ForceReload()
    {
        var allTypes = typeof(UniqueComponentAtScene<>).Assembly.GetTypes();
        var derivedTypes = new List<Type>();
        GetAllDerivedTypesRecursively(allTypes, typeof(UniqueComponentAtScene<>), ref derivedTypes);
 
        foreach (var type in derivedTypes)
        {
            var components = Resources.FindObjectsOfTypeAll(type)
                .Where(x => !EditorUtility.IsPersistent(x))
                .Cast<MonoBehaviour>()
                .ToList();
 
            foreach (var c in components)
                c.Invoke("Awake", 0);
        }
    }
 
    private static void GetAllDerivedTypesRecursively(Type[] types, Type type1, ref List<Type> results)
    {
        if (type1.IsGenericType)
        {
            GetDerivedFromGeneric(types, type1, ref results);
        }
        else
        {
            GetDerivedFromNonGeneric(types, type1, ref results);
        }
    }
 
    private static void GetDerivedFromGeneric(Type[] types, Type type, ref List<Type> results)
    {
        var derivedTypes = types.Where(t => t.BaseType != null && t.BaseType.IsGenericType && t.BaseType.GetGenericTypeDefinition() == type).ToList();
        results.AddRange(derivedTypes);
        foreach (Type derivedType in derivedTypes)
        {
            GetAllDerivedTypesRecursively(types, derivedType, ref results);
        }
    }
 
    private static void GetDerivedFromNonGeneric(Type[] types, Type type, ref List<Type> results)
    {
        var derivedTypes = types.Where(t => t != type && type.IsAssignableFrom(t)).ToList();
 
        results.AddRange(derivedTypes);
        foreach (Type derivedType in derivedTypes)
        {
            GetAllDerivedTypesRecursively(types, derivedType, ref results);
        }
    }
}
#endif



Со второй проблемой способа борьбы я не придумал. Разве только словесно запретить использовать RequireComponent с указанием на наследников UniqueComponentAtScene
Если вы знаете, что делать со второй проблемой – с радостью выслушаю ваше решение. Ну и вообще замечания, предложения, пожелания приветствуются!

_______________________________________
site: ocp.onl
e-mail: hello@ocp.onl
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
05.03.2017, 20:27
Ответы с готовыми решениями:

Unity как сделать чтобы при старте игры работало 2 сцены одновременно. Типо на первой сцене меню и главные скрипты
а на второй сцене сама локация вот пример...

Можно ли сделать так, чтобы при запуске сцены unity сам нажимал на нужную кнопку?
можно ли сделать так ,чтобы при запуску сцены unity сам нажимал на нужную тебе кнопку ?

Unity 2d как рандомно загружать заготовленные сцены
Нужна помощь по unity. У меня есть 4 сцен. Игрок появляется на первой. После нажатия на...

Компонент для создания двухмерной сцены
Подскажите какой компонент подходит для создания бесконечного пространства и как он работает...

13
753 / 600 / 204
Регистрация: 06.08.2015
Сообщений: 2,432
05.03.2017, 20:57 2
А для чего такие сложности? Массивы синглтонов + наследование от синглтонов? Зачем?
0
325 / 114 / 7
Регистрация: 01.05.2011
Сообщений: 283
Записей в блоге: 3
05.03.2017, 21:48  [ТС] 3
Ну есть несколько причин:
1) Чтобы отделить мух от котлет. Механизм синглтонов отдельно, механизм игровой логики отдельно Вся их связь в одной строчке - base.Awake в наследнике.
2) Для уменьшения повторяемости кода. Если несколько синглтонов - то для каждого придется писать заново обработку. А т.к. в Unity конструкторы классов, наследных от MonoBehaviour, недоступны - то придется все запихивать в Awake. И читаемость это не улучшит.
3) По наследованию от UniqueComponentAtScene можно понимать, что компонент должен быть уникальным. С другой стороны, если просто дописывать св-во Instance в каждый такой класс - это будет совсем не наглядно.

А как вы используете синглтоны в юнити? Может покажете пример?
0
753 / 600 / 204
Регистрация: 06.08.2015
Сообщений: 2,432
05.03.2017, 22:25 4
Цитата Сообщение от ImAlexSmith Посмотреть сообщение
Механизм синглтонов отдельно, механизм игровой логики отдельно
Синглтон - это архитектура, а не игровая логика.
Цитата Сообщение от ImAlexSmith Посмотреть сообщение
Если несколько синглтонов - то для каждого придется писать заново обработку
Синглтон на то и синглтон, чтобы быть единственным и уникальным. Это, можно сказать, альтернативная реализация статического класса.
Цитата Сообщение от ImAlexSmith Посмотреть сообщение
что компонент должен быть уникальным
Смотри выше ))

Вот так, например, можно, чтобы не искать компонент везде, где нужна локализация:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
 
// для работы скрипта необходимо кидать на компонент ключ (писать прямо в тексте)
// файл локализации:
// ключ
// значение
 
public class TextManager : MonoBehaviour {
    public Dictionary<string, string> texts = new Dictionary<string, string>();
    public static TextManager Instance { get; private set; }
 
    void Awake()
    {
        if (Instance != null)
        {
            Destroy(this.gameObject);
            return;
        }
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }
 
    /// <summary>
    /// Загрузка из Resources - НЕ задавать расширение!
    /// </summary>
    /// <param name="_filename">имя файла</param>
    public void LoadLocalizationResources(string _filename)
    {
        TextAsset ta = Resources.Load<TextAsset>(_filename);
        ProceedText(ta.text);
    }
 
    /// <summary>
    /// загрузка из StreamingAssets - задавать полное имя файла
    /// если в имени файла нет http:/ или ftp:/ - загружается локально
    /// </summary>
    /// <param name="_filename">задавать полное имя файла, поверка наличия http:/ + ftp:/ + :/</param>
    /// <param name="CallbackOnDone">Колбэк для метода после обработки локализации</param>
    public void LoadLocalizationAsset(string _filename, System.Action CallbackOnDone)
    {
        texts.Clear();
        if (!_filename.Contains("http:/") && !_filename.Contains("ftp:/") && !_filename.Contains(":/"))
            _filename = System.IO.Path.Combine("file://"+Application.streamingAssetsPath, _filename);
 
        StartCoroutine(LoadAsset(_filename, CallbackOnDone));
    }
 
    IEnumerator LoadAsset(string _filename, System.Action CallbackOnDone)
    {
        if (_filename.Contains("://"))
        {
            WWW www = new WWW(_filename);
            yield return www;
            ProceedText([url]www.text);[/url]
            CallbackOnDone();
        }
    }
 
   private void ProceedText(string _s)
    {
        texts.Clear();
        string[] lines = _s.Split(new string[] { "\r\n" }, System.StringSplitOptions.None);
        for (int i=0; i<lines.Length-2; i = i + 2)
        {
            texts.Add(lines[i], lines[i + 1]);
        }
    }
 
    /// <summary>
    /// Замена текста в компоненте Text
    /// </summary>
    /// <param name="_component">Ссылка на компонент</param>
   public void ReplaceText(Text _component)
    {
        try
        {
            _component.text = texts[_component.text];
        }
        catch (KeyNotFoundException)
        {
            Debug.Log("Key '"+_component.text+"' not found!");
        }
    }
 
    /// <summary>
    /// Замена текста во всех компонентах Text
    /// </summary>
   public void ReplaceAllText()
    {
        Text[] txt = Object.FindObjectsOfType<Text>();
        if (txt.Length > 0)
            foreach (Text _t in txt)
                this.ReplaceText(_t);
    }
 
    /// <summary>
    /// Замена текста в переменной
    /// </summary>
    /// <param name="_s">строка с ключом</param>
    /// <returns></returns>
   public string ReplaceString(string _s)
    {
        try
        {
            return texts[_s];
        }
        catch (KeyNotFoundException)
        {
            Debug.Log("Key '" + _s + "' not found!");
            return _s;
        }
 
    }
}
0
325 / 114 / 7
Регистрация: 01.05.2011
Сообщений: 283
Записей в блоге: 3
05.03.2017, 23:22  [ТС] 5
Цитата Сообщение от Cr0c Посмотреть сообщение
Синглтон - это архитектура, а не игровая логика.
В англоязычном сообществе принято использовать термин singletone для обозначения уникального компонента, т.е. именно ваш пример

Цитата Сообщение от Cr0c Посмотреть сообщение
Синглтон на то и синглтон, чтобы быть единственным и уникальным. Это, можно сказать, альтернативная реализация статического класса.
Давайте без прописных истин, всем это прекрасно известно Я это и делал

Вы привели пример с менеджером локализации. Хорошо, давайте разберемся.
1) Если вам еще нужно сделать менеджер ввода, менеджер настроек уровня, еще три менеджера и четыре других каких-то уникальных компонента? В каждый вы будете дописывать
C#
1
2
3
4
5
6
7
8
9
10
 
         public static TextManager Instance { get; private set; }
         ....
         if (Instance != null)
        {
            Destroy(this.gameObject);
            return;
        }
        Instance = this;
        DontDestroyOnLoad(gameObject);
Это, конечно, тоже вариант. Но, как я писал выше, можно же уменьшить повторяемость кода.

2) Смотрим дальше. Например, нерадивый геймдизайнер случайно добавил на сцену три менеджера локализации и забыл про это. Позже, потратив на настройку одного из них много-много времени - он решил проверить результат. И запустил сцену. И вот тут совсем не обязательно, что глупый Юнити не удалит настроенный компонент, оставив пустой в качестве синглтона. Соответственно надо пометить компонент ExecuteInEditMode, и запоминать, какой из компонентов добавлен первым (_isFirst). Чтобы удалять остальные, но не трогать его.

Посмотрите, пожалуйста, какие цели были поставлены - запретить повторное добавление компонента в сцену, если такой уже есть. Стандартная реализация из гугла этому не препятствует.
0
295 / 244 / 128
Регистрация: 24.12.2014
Сообщений: 708
06.03.2017, 03:58 6
Цитата Сообщение от ImAlexSmith Посмотреть сообщение
А т.к. в Unity конструкторы классов, наследных от MonoBehaviour, недоступны
Что вам мешает сделать классы без наследования MonoBehaviour или вообще без любого наследования? Нужную инициализацию можно сделать и в статическом конструкторе и сам класс сделать статическим, тогда доступ к синглтону будет ещё короче - без свойства Instance. Приведу пример с той же локализацией:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System.IO;
 
public static class Localization {
 
    // список всех языков
    public static string[] AllLangs { get; private set; }
    // текущий язык
    public static string CurrentLang { get; private set; }
    // массив текстов локализации
    public static string[] Text { get; private set; }
 
    static Localization() {
        // создаём массив доступный языков 
        // или можно получить данные с какой-то папки или файла
        AllLangs = new string[ 3 ] { "ru", "en", "uk" };
 
        // инициализируем любым образом текущий язык
        // в данном случае я указал первый попавшийся элемент массива
        SwitchLang( AllLangs[ 0 ] );
    }
 
    // метод для вызова смены языка
    public static void SwitchLang( string lang ) {
        // можно добавить проверку на то, есть ли элемент в массиве
        // и такой файл существует физически
        CurrentLang = lang;
        Text = File.ReadAllLines( "какой-то путь" + lang + ".txt" ); // заполняем данные
    }
 
}
После чего можно в любом месте кода получить доступ, например так:
C#
1
2
3
4
5
public class MainMenu : MonoBehaviour {
     void OnGUI() {
          GUI.Label( new Rect( 50, 50, 100, 32 ), Localization.Text[ 0 ] );
     }
}
Здесь "Localization.Text" инициализируется ещё до момента добавления компонента MainMenu до любого из объектов в Unity.

Правда не знаю как будет вести себя производительность, возможно приложение будет дольше грузится или выбрасывать исключения, поскольку Unity не всегда дружит со статическими конструкторами, которые работают с компонентами юнити. Но с файлами проблем не возникало.

Цитата Сообщение от ImAlexSmith Посмотреть сообщение
В каждый вы будете дописывать
Механизм синглтонов это и подразумевает, чтобы для каждого такого объекта был единый статический экземпляр класса.

Если я ошибаюсь, то напишите каким вы видите процесс доступа к синглтонам. Судя по вашим идеям вы ходите сделать что-то типа менеджера синглтонов Чтобы было примерно так:
МЕНЕДЖЕР.[ТИП_ИЛИ_НАЗВАНИЕ_КОНКРЕТНОГО_СИНГЛОНА].[ВОЗМОЖНО_ЕЩЁ_INSTANCE].МЕТОД_СИНГЛОТА
или для примера
Setting<Localization>.Instance.SetLang( "ru" );

Неужели такой вид будет легче читать и писать, чем например этот:
Localization.SetLang( "ru" );

Учитывая, что вы хотите ещё сделать какое-то наследование, то скорее всего "Чтобы отделить мух от котлет" превратится в кашу, причём густую Пусть будет несколько менеджеров, но ими будет легче управлять и риск наткнуться на NullReferenceException будет минимальным.
0
753 / 600 / 204
Регистрация: 06.08.2015
Сообщений: 2,432
06.03.2017, 07:52 7
Цитата Сообщение от ImAlexSmith Посмотреть сообщение
Например, нерадивый геймдизайнер случайно добавил на сцену три менеджера локализации и забыл про это
Ну так тут код ни причём. Геймдизу надо по рукам надавать.
Цитата Сообщение от ImAlexSmith Посмотреть сообщение
Но, как я писал выше, можно же уменьшить повторяемость кода.
Экономия на спичках? Да ещё с Linq'ом?
0
325 / 114 / 7
Регистрация: 01.05.2011
Сообщений: 283
Записей в блоге: 3
06.03.2017, 13:30  [ТС] 8
Цитата Сообщение от wmysterio Посмотреть сообщение
Что вам мешает сделать классы без наследования MonoBehaviour или вообще без любого наследования?
Вот это очень правильный вопрос! Чтобы была возможность работать с компонентом в редакторе Unity, как с любым другим. Вообще, судя по отзывам здесь и на геймдеве, что-то я засомневался в правильности именования "компонента, который должен быть единственным на сцене" - синглтоном )) Ну и ладно, главное что я определил задачи, которые мне надо решить - и уже от этого отталкивался

Цитата Сообщение от wmysterio Посмотреть сообщение
Механизм синглтонов это и подразумевает, чтобы для каждого такого объекта был единый статический экземпляр класса.
У меня так и есть Просто доступ идет через [ClassName].Instance.[fieldOrPropertyOrMethod]
Менеджера, как такового нет.

Цитата Сообщение от wmysterio Посмотреть сообщение
NullReferenceException
Может и, скорее всего, будет. Но это не плохо. Такая ошибка ловится легко на этапе разработки. И единственная причина, почему вылезает такой эксепшен - потому что компонент не добавлен в сцену. Можно ему даже сделать обработку: при получении NullReferenceException - добавляем новый GO с единственным компонентом в корень сцены.

Добавлено через 3 часа 16 минут
Цитата Сообщение от Cr0c Посмотреть сообщение
Экономия на спичках? Да ещё с Linq'ом?
Не совсем понимаю, что вы имеете против Linq ? Ну при большом желании можно развернуть в более классический код.

Цитата Сообщение от Cr0c Посмотреть сообщение
Ну так тут код ни причём. Геймдизу надо по рукам надавать.
Интересное замечание. Этот метод подходит для большой компании. Когда у программистов есть время и возможность написать регламент по использованию движка/фреймворка. А после его поддерживать в актуальном состоянии. Если кто-то не прочитал инструкцию и запорол проект - это его проблемы, из ЗП вычтут. Я же рассматриваю ситуацию, когда команда состоит из буквально нескольких человек. Тем самым "нерадивым геймдизайнером" могу быть и я сам, через пару недель после написания компонента и добавления его в сцену. И если кто-то в команде потратил время зря, и ему придется заново настраивать компонент - то и получение готового продукта откладывается на это самое время. Соответственно, можно сказать, что Я несу издержки. А мне оно надо ? Да и к тому же все мы люди. Человеческий фактор - самый непредсказуемый.
А в общем то вы согласны, что ТАКАЯ проблемная ситуация может произойти?
0
753 / 600 / 204
Регистрация: 06.08.2015
Сообщений: 2,432
06.03.2017, 21:25 9
Цитата Сообщение от ImAlexSmith Посмотреть сообщение
Не совсем понимаю, что вы имеете против Linq ?
Начиная от кучи мусора после него (что заставляет чаще срабатывать GC) и до его медлительности при частых вызовах.
Вот этот код
C#
1
2
3
4
5
6
7
8
9
    public static T Instance {
        get {
            var inst = _instances.FindAll(x => x.GetType() == typeof (T));
            var count = inst.Count;
            if (count == 1)
                return inst[0];
            return null;
        }
    }
вызывает Linq при каждом обращении к синглтону. А этих вызовов может быть и десяток за каждый кадр. Сколько мусора потенциально может скопиться? Насколько чаще будут провалы фпс из-за вызова GC?
Нет, Linq допустим только в методах НЕ КАЖДЫЙ кадр, а в идеале только там, где без него вообще никак.
Цитата Сообщение от ImAlexSmith Посмотреть сообщение
Когда у программистов есть время и возможность написать регламент по использованию движка/фреймворка.
В маленькой команде и небольшом проекте можно текущую архитектуру держать на виду, техники прототипирования и реализации на текущей архитектуре проекта. Документация излишня, не на продажу же фреймворк )))
C#
1
private static readonly List<T> _instances = new List<T>();
Для чего список, когда синглтон одного типа может быть всего один?
0
325 / 114 / 7
Регистрация: 01.05.2011
Сообщений: 283
Записей в блоге: 3
06.03.2017, 22:48  [ТС] 10
Цитата Сообщение от Cr0c Посмотреть сообщение
_instances.FindAll(x => x.GetType() == typeof (T))
За пол минуты без IDE (в с решарпером в два клика) это разворачивается в
C#
1
2
3
4
5
for (var i = 0; i < _instances.Count; i++)
{
if (_instances[i].GetType() == typeof(T))
 inst.Add(_instances[i]);
}
если Вам так будет удобнее Да и вообще, скороcть работы linq - это тема для холивара. Бессмысленно это обсуждать.

Цитата Сообщение от Cr0c Посмотреть сообщение
Для чего список, когда синглтон одного типа может быть всего один?
Ох беда... Вы, походу, не понимаете смысл генерик листа? В этом списке лежат ссылки на экземпляры любого класса, наследника UniqueComponent - т.е. любого уникального компонента. Кэширование, чтобы не просматривать объекты на сцене каждый раз. При вызове Instance мы приводим элемент из списка к нужному типу. Использование выглядит примерно так:
C#
1
2
3
 LevelSettings.Instance.Save();
        BestScore.Instance.Add(new BestScoreRecord(LevelSettings.Instance.PlayerName, LevelSettings.Instance.Score));
        BestScore.Instance.Save();
А проверка на уникальность компонента перед добавлением в данный список происходит в Awake. Т.е. в данном списке всегда лежит только по одной ссылке на каждый уникальный компонент, если он вообще добавлен на сцену
0
295 / 244 / 128
Регистрация: 24.12.2014
Сообщений: 708
07.03.2017, 17:47 11
Цитата Сообщение от ImAlexSmith Посмотреть сообщение
Да и вообще, скороcть работы linq - это тема для холивара
Возможно вы не поверите, но если использовать в update или достаточно часто таким образом Instance, то производительность существенно падает. Может это не заметно современных машинах, но моём старом ведре разница заметная. Предлагаю вам в место списка использовать словарь типа этого:
C#
1
Dictionary<System.Type, T>
где ключ - это тип "синглтона" а значение объект "синглтона". Тогда вы можете проверять на существование уникального "синглтона" банальной проверкой:
C#
1
2
3
4
if( !_instances.ContainsKey( typeof(T) ) ) {
     return null;
}
return _instances[ typeof(T) ];
Таким образом Вы избегаете перебора по коллекции, так как ContainsKey имеет скорость o(1), в то время как со списком - o(n), где n - это грубо говоря количество элементов в массиве.

Ключи в словарях должны быть всегда уникальными, по-этому это можно использовать для того, чтобы контролировать уникальность. При запуске приложения можно легко отловить место, где происходит добавление дубликата. Но так как ваша цель сделать это на уровне редактора, то вряд ли эта цель достижима, разве что через какие-то костыли )
LevelSettings.Instance.Save();
BestScore.Instance.Add(new BestScoreRecord(LevelSettings.Instance.PlayerName, LevelSettings.Instance.Score));
BestScore.Instance.Save();
И это ради этого создаётся всё это? Омг... Ну, можете использовать - никто не запрещает. Проще же было бы добавить систему статических и вложенных классов, и было бы проще и быстрее ) Скажем так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public static class Settings {
 
     public static class Level {
          static Level() {
               PlayerName = "";
               Score = 0;
          }
          public static string PlayerName { get; set; }
          public static int Score{ get; set; }
          public static void Save() { /* ... */ }
          public static void Reset() {
               PlayerName = "";
               Score = 0;
          }
     }
 
     public static void Reset() { // на каждом уровне можно очищать(сбивать по-умолчанию уровень) данные
          Level.Reset();
          /* ... */
     }
 
     public static void Save() { 
          Level.Save();
          /* ... */
     }
 
}
 
//using static Settings; // для тех, кому лень писать каждый раз Settings, а хочет так:
// Level.Reset();
// Save();
public class DemoLevel : MonoBehavior {
 
     void Awake() { 
          Settings.Level.Reset();
     }
 
     void Start() {
          SaveLevelInfoAndStartMainMenu();
     }
 
     public void SaveLevelInfoAndStartMainMenu() {
          Settings.Save();
          SceneManager.LoadScene( 0 );
     }
 
}
Да и то с такой иерархией это перебор
Цитата Сообщение от ImAlexSmith Посмотреть сообщение
я засомневался в правильности именования "компонента, который должен быть единственным на сцене" - синглтоном
Согласен. Паттерн "синглтона" подразумевает единого объекта в приложении, а не в какой-то её отдельной части, скажем сцене + он должен быть того же типа. У вас же используется список в место объекта, по-этому это не "синглтон", хоть реализация заимствована из паттерна. Об этом собственно Вас и спрашивал товарищ Cr0c, когда упоминал о списке.
0
753 / 600 / 204
Регистрация: 06.08.2015
Сообщений: 2,432
07.03.2017, 22:49 12
Цитата Сообщение от wmysterio Посмотреть сообщение
Паттерн "синглтона" подразумевает единого объекта в приложении, а не в какой-то её отдельной части
Именно поэтому я и привёл в виде примера локализатор, а не контроллер управления.
0
325 / 114 / 7
Регистрация: 01.05.2011
Сообщений: 283
Записей в блоге: 3
10.03.2017, 01:01  [ТС] 13
Ну есть Unity wiki. Какой никакой, а источник стандартизации информации, пускай и сообществом. http://wiki.unity3d.com/index.php/Singleton

Цитата Сообщение от wmysterio Посмотреть сообщение
Проще же было бы добавить систему статических и вложенных классов
Статические классы нельзя редактировать в редакторе (каламбурчик). Ну либо писать для КАЖДОГО такого класса новый инспектор, что тоже геморрой славный, согласитесь?

Цитата Сообщение от wmysterio Посмотреть сообщение
Предлагаю вам в место списка использовать словарь типа этого
За словарь спасибо, что-то не подумал про него.

Вообще я согласен, раз это игра - за оптимизацией надо следить. Все-таки (n) раз в секунду метод вызывается. Использовать массивы вместо листов, for вместо foreach, кэшировать ссылки вместо FindObject и т.д. и т.п. Это все скорее монотонный труд и дело привычки. Т.е. никак не относится к подобной, как я понял, нестандартной задаче
0
295 / 244 / 128
Регистрация: 24.12.2014
Сообщений: 708
10.03.2017, 02:20 14
Цитата Сообщение от ImAlexSmith Посмотреть сообщение
Какой никакой, а источник стандартизации информации, пускай и сообществом
Ну, там паттерн синглтона соблюдается. Не знаю только почему Вы сюда это написали.
Цитата Сообщение от ImAlexSmith Посмотреть сообщение
Статические классы нельзя редактировать в редакторе (каламбурчик). Ну либо писать для КАЖДОГО такого класса новый инспектор, что тоже геморрой славный, согласитесь?
Я навёл код только в качестве альтернативы вашего "синглтона", то есть без использования компонентов как таковых. Не все же классы делать компонентами )
0
10.03.2017, 02:20
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
10.03.2017, 02:20
Помогаю со студенческими работами здесь

Как записать звук со сцены в Unity, а затем сохранить его на рабочий стол?
Допустим, что в игре есть диктофон и игрок может записывать звуки и диалоги внутри игры. После...

Unity сцены. Unity lifecycle
Всем привет. Не понимаю по каким словам искать ответ на этот вопрос. Не совсем понимаю жизненный...

Как сделать анимацию двери в авто на Unity 4 или 5?
Может у кого то есть исходник? Хочу что бы игрок садился в машину и двери открывались, закрывались....

Как сделать уникальный личный кабинет, авторизацию и баланс для каждого пользователя?
как сделать личный кабинет, авторизацию и баланс для каждого пользователя уникальный

Ищется компонент или как сделать
нужен компонент DBGrid визуально оформленный как ленточная форма в Access или как установить...

Как сделать компонент Panel в виде выгнутой или вогнутой формы?
Как сделать компонент Panel в виде выгнутой или вогнутой формы?


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
14
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru