Как сделать сохранение/загрузку объектов без GetComponent?

Программирование на Юнити.

Как сделать сохранение/загрузку объектов без GetComponent?

Сообщение Инженер 10 апр 2020, 14:05

Хочу сделать сохранение и загрузку объектов в реальном времени во время игры. В большом открытом мире объекты спаунятся только в некотором радиусе вокруг игрока. Оказываясь за пределами радиуса, объекты сохраняются и уничтожаются. Возможно, в какие-то моменты перемещения игрока на автомобиле, нужно будет одновременно сохранять и загружать сотни объектов за кадр. Поэтому производительность на первом месте. Обрабатываться таким образом будет все- деревья, трава, персонажи, заборы и т.д.

У каждого объекта, помимо позиции и вращения, есть переменные из скриптов, которые необходимо сохранять. Например, у автомобиля- количество топлива, у персонажа- здоровье, голод и т.д.

Проблема в том, что когда я спауню объект, необходимо выполнить GetComponent, который ищет на объекте главный скрипт и задает нужные значения переменным. При сохранении, опять же, выполняется GetComponent, чтобы извлечь необходимые данные и сериализовать их. Конечно, можно искать компонент только один раз, при спауне объекта, а потом хранить кэшированную ссылку где-то, но это не отменяет того факта, что GetComponent жрет ресурсы и я бы хотел вообще отказаться от его использования.

Я думал, что нашел обходной путь. Вызывал функцию на объекте, используя gameObject.SendMessage("Функция на объекте"). Эта вызванная функция отправляет все данные этого объекта в public static class, из которого, в свою очередь, данные уже подхватываются скриптом сохранения/загрузки. Этот путь оказался не только грязным, но и на много более тормознутым, чем GetComponent. Этот способ в тесте на 1 000 000 операций сожрал почти секунду, в то время как GetComponent выполнялся за 200 мс.

В общем, я не знаю, как обойти использование GetComponent и оптимизировать сохранение/загрузку объектов. Что можно придумать?
Инженер
UNIт
 
Сообщения: 88
Зарегистрирован: 22 май 2016, 11:13

Re: Как сделать сохранение/загрузку объектов без GetComponent?

Сообщение 1max1 10 апр 2020, 14:38

1. Делаем словарь:
Dictionary<int, Data> dictionary;
Где ключ - ид объекта (его хеш), а значение класс с данными.
Конечно с распаковкой будут траблы если ты решишь добавить наследование, но я думаю ничего страшного)
Аватара пользователя
1max1
Адепт
 
Сообщения: 5505
Зарегистрирован: 28 июн 2017, 10:51

Re: Как сделать сохранение/загрузку объектов без GetComponent?

Сообщение Инженер 10 апр 2020, 15:02

1max1 писал(а):1. Делаем словарь:
Dictionary<int, Data> dictionary;


И как это поможет получить доступ к переменным внутри скриптов, висящих на объекте? Вопрос в том, как избавиться от GetComponent.
Инженер
UNIт
 
Сообщения: 88
Зарегистрирован: 22 май 2016, 11:13

Re: Как сделать сохранение/загрузку объектов без GetComponent?

Сообщение 1max1 10 апр 2020, 15:19

Я тебе и сказал как избавиться от GetComponent - хранить данные в другом месте. Есть такая парадигма (или паттерн), ECS. Там нет компонентов, висящих на объекте, можешь почитать.
Аватара пользователя
1max1
Адепт
 
Сообщения: 5505
Зарегистрирован: 28 июн 2017, 10:51

Re: Как сделать сохранение/загрузку объектов без GetComponent?

Сообщение Инженер 10 апр 2020, 18:13

1max1 писал(а):Я тебе и сказал как избавиться от GetComponent - хранить данные в другом месте. Есть такая парадигма (или паттерн), ECS. Там нет компонентов, висящих на объекте, можешь почитать.


Я читал про ECS, поверхностно правда, но мне показалось, что это слишком усложняет все, т.к. лишает удобства компонентной системы Unity.

Ты мне предлагаешь полностью избавиться от компонентов на объектах или отправлять данные с компонентов в какой-то глобальный словарь?

Сейчас у меня уже существует Dictionary, но он содержит данные обо всех объектах мира, даже тех, что не загружены на сцену. Я к нему обращаюсь, только когда надо сохранить туда объект перед тем, как уничтожить со сцены или когда я спауню объект на сцене и, соответственно, извлекаю из Dictionary данные о нем. При выходе из игры Dictionary разлагается на List keys и List values и сохраняется на жесткий диск. То есть я не храню там актуальные данные, они обновляются только при уничтожении объектов, чтобы впоследствии заспаунить их с правильной позицией, разворотом, количеством топлива и т.д.

А что ты мне предлагаешь? Хранить там постоянные актуальные данные каждого объекта на сцене каждый кадр?
Инженер
UNIт
 
Сообщения: 88
Зарегистрирован: 22 май 2016, 11:13

Re: Как сделать сохранение/загрузку объектов без GetComponent?

Сообщение Инженер 10 апр 2020, 21:05

Я последовал вашему совету. Именно такая реализация предполагалась?

Глобальный класс, хранящий словарь всех объектов и класс-контейнер Data для переменных:

Синтаксис:
Используется csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Test
{
        public static class Global
        {
                public static Dictionary<int, Data> dic = new Dictionary<int, Data>();
        }
       
        public class Data
        {
                public int fuel;
        }
}


Следующий компонент вешается на объект и постоянно отсылает данные в словарь:

Синтаксис:
Используется csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Test
{
        public class ComponentOnObject : MonoBehaviour
        {
                public int fuel;

                void Start()
                {
                        Data vehData = new Data();
                        vehData.fuel = fuel;
                        Global.dic.Add(1, vehData);
                }

                // Update is called once per frame
                void Update()
                {
                        Global.dic[1].fuel = fuel;
                }
        }
}


Ниже- это часть кода, где собственно проверяется скорость работы этого способа:

Синтаксис:
Используется csharp
               
                void SendGlobal()
                {
                        stopWatch.Start();
                       
                        for(int i = 0; i < 1000000; i++)
                        {
                        variable = Global.dic[1].fuel;
                        }
                       
                        stopWatch.Stop();
                        long result = stopWatch.ElapsedMilliseconds;
                        Debug.Log("SendGlobal result= " + result);
                        stopWatch.Reset();
                }


Способ получился в 4 раза быстрее GetComponent, но все еще медленнее раз в 10, чем кэширование переменных. Но все же мне не нравится все это, повышается связность кода, плохо, что объекту надо помнить про глобальное хранилище объектов, что он не может существовать сам по себе, а скрипт сохранения/загрузки- сам по себе. Все это выглядит очень тупо. Если честно, не приятно думать о том, что придется использовать такую систему. Она слишком запутанная. Может, есть вариант, как сделать это быстрее GetComponent и менее запутанно, чем попытался сделать я?
Инженер
UNIт
 
Сообщения: 88
Зарегистрирован: 22 май 2016, 11:13

Re: Как сделать сохранение/загрузку объектов без GetComponent?

Сообщение 1max1 10 апр 2020, 22:38

Ну да, это неудобно и связывает код, но в любом случае, чтобы избежать многократного использования GetComponent, тебе надо хранить ссылку на компонент, а ссылку можно получить только через GetComponent, то есть, хотя бы раз придется закешировать это дело.
+ При создании новых объектов, если они однотипные можно использовать пул, брать старые неиспользуемые объекты, вместо создания новых, на компоненты которых, уже есть ссылки.
Аватара пользователя
1max1
Адепт
 
Сообщения: 5505
Зарегистрирован: 28 июн 2017, 10:51

Re: Как сделать сохранение/загрузку объектов без GetComponent?

Сообщение Tolking 10 апр 2020, 23:22

Это тест чего? Доступа к элементу словаря в котором один элемент? Это зачем? Сделай миллион (или сколько там объектов планируется) элементов и тогда проверяй...

Запутанность системы, которая "помнит" все объекты, которые есть, которые были, и которые еще не были тебя не пугает? И по тексту можно подумать, что она уже есть и работает, и не устраивает только получение компонента с объекта... Это не проблема... В старте компонента вызывай эту систему, и заполняй значения компонента. А при удалении компонента отправляй ей новые значения...

Но я, думаю, системы такой у тебя нет. Иначе бы вопрос был - "мне надо инстансить сотни объектов каждый кадр, а это прям фризы, а не падение фпс. Как?". И если бы ты дошёл до пула объектов, который все советуют, в этом случае, то понял что пул хорошо работает с одинаковыми объектами, а когда объекты разные получается, что в памяти нужно держать все... И все равно, если объекты сложные, с коллайдерами (особенно мешколлайдерами) будут ощутимые лаги...

Инстансить сотни объектов за кадр - это ECS... Либо по-старинке размазывать процесс по кадрам...

Во многих играх видно как погружается уровень при движении не из-за того, что создатели тупые...
Ковчег построил любитель, профессионалы построили Титаник.
Аватара пользователя
Tolking
Адепт
 
Сообщения: 2716
Зарегистрирован: 08 июн 2009, 18:22
Откуда: Тула

Re: Как сделать сохранение/загрузку объектов без GetComponent?

Сообщение Инженер 11 апр 2020, 17:38

1max1 писал(а):ссылку можно получить только через GetComponent, то есть, хотя бы раз придется закешировать это дело.


Да, это верно. Я подумал ночью и решил, что есть использовать пул, то вызовов GetComponent будет не так уж и много, в основном объекты будут браться из пула. А система будет выглядеть так:

1. Первый словарь хранит данные для сериализации каждого объекта в мире. Позиция, поворот, сколько топлива, если это машина и т.д. Те данные, которые должны запоминаться игрой даже после перезапуска игры.

2. Второй словарь хранит пул объектов. Но в словаре не сами объекты, а класс ObjectData, который хранит сам gameObject, ссылки на его компоненты (то есть GetComponent будет выполняться только 1 раз при внесении объекта в пул), некоторые другие данные.

3. А третьим будет LinkedList просто со всеми активными объектами на сцене. Опять же, не сами объекты непосредственно, а ObjectData.

Это не считая всяких LinkedList, которые существую только 1 кадр, когда нужно запомнить, какие объекты надо удалить, какие достать из пула и т.д.


Tolking писал(а):Это тест чего? Доступа к элементу словаря в котором один элемент? Это зачем? Сделай миллион (или сколько там объектов планируется) элементов и тогда проверяй...


Это тест скорости доступа к элементу в словаре. Dictionary имеет время доступа, не зависящее от количества элементов, разве не так? Хоть 1 элемент, хоть миллион, все равно будет O(1).

Tolking писал(а):Но я, думаю, системы такой у тебя нет.

Ну, у меня пока 4 куба в качестве подопытных, но я гляжу вперед. Предвижу, что чрезмерное применение GetComponent будет вредить. Чем раньше определиться с архитектурой системы стриминга открытого мира, тем лучше.
Но да, пул вероятно решит эту проблему, если хранить в пуле помимо объекта, ссылки на его компоненты, закэшированные один раз.
Инженер
UNIт
 
Сообщения: 88
Зарегистрирован: 22 май 2016, 11:13

Re: Как сделать сохранение/загрузку объектов без GetComponent?

Сообщение Dewa1s 14 апр 2020, 05:51

Инженер писал(а):Предвижу, что чрезмерное применение GetComponent будет вредить.

Преждевременная оптимизация — корень всех (или большинства) проблем в программировании

Ну выполняется GetComponent миллион раз за 200мс, и что? Это 0.0002 мс на 1 вызов, 0.02мс на сотню вызовов за кадр. Вообще пофиг же, городить велосипеды и лепить ужасную архитектуру чтобы сэкономить десятитысячную долю миллисекунды на вызов? В проекте найдется с десяток другой более узких горлышек. Забей и иди дальше, если вдруг звезды сойдутся и окажется, что это реально имеет хоть какое-то влияние на игру - будешь уже думать, что с этим делать
Аватара пользователя
Dewa1s
Старожил
 
Сообщения: 564
Зарегистрирован: 26 дек 2011, 02:12


Вернуться в Скрипты

Кто сейчас на конференции

Сейчас этот форум просматривают: Yandex [Bot] и гости: 8