Unity удалить объект из памяти
Юнити подвисает когда пытаюсь удалить объекты в цикле, и начинается дикая утечка памяти.
Что ей не нравится?
А чёго бы ему не тормозить, если ты 100500 раз по списку проходишь, почём зря ?
А почему мой варик работать не хочет?
Там порядка 100 объектов на сцене.
Или юнька настолько тормозная?)
хм.. прикинем.. 100!/(100-botcount)! = ?
А причем тут факториалы? Чтобы найти нужный об.ект, достаточно раз по списку пройти. Или там какой то особенный извращенный способ?
Denadan
> GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
>
> foreach(GameObject enemy in enemies)
> GameObject.Destroy(enemy);
А можно изменять коллекцию во время foreach?
obrez
при том что если у тебя несколько ботов, на что номекает while - поиск будет производится заново по всем объектам для каждого из них. а поиск по всем объектам в юнити очень тормознутый и не рекомендуется для "повсеместного" использования
alt3d
хз почему упомянут я, но нет нельзя. но дестрой не изменяет коллекцию, он даже не уничтожает объект - просто помечает его для удаления(что в данном случае не важно, даже если использовать DestroyImmediate - у нас остается нетронутой оригинальная коллекция, пусть и некоторые её члены указывают на уничтоженные объекты). вот если добавить что-то типа enemies.Remove(enemy), тогда работать не будет
find тяжелый. Его в принципе не рекомендуется использовать в массовом порядке.
Лучше регистрировать юниты в массиве на старте, и через массив же удалять.
Denadan
И в чем проблема для 4 ботов и 100 об.ектов?
obrez
> Или юнька настолько тормозная?)
не настолько
просто если хочешь сделать что-то быстро - применяй быстрые способы это сделать
GameObject.Find не для этого сделан
он ещё так умеет, например: GameObject.Find("/landscape/platform/bot") ,т.е. "умный" поиск по иерархии явно более трудоемок, чем тебе требуется
выше верно советуют, во-первых не перебирать 100500 раз, а получить сразу список всех и по нему уже проходить, а во-вторых метод FindGameObjectsWithTag, который попроще, и соответственно производительней Find (не забудь тэги проставить объектам)
obrez
я чуть поправил пост, и уже пару раз ответили пока правил -_-
Find тяжелый, понятно.
А почему while(Find) валит юньку, а 4 раза подряд Find нет, не понятно :)
obrez
> А почему while(Find) валит юньку, а 4 раза подряд Find нет, не понятно :)
А почему 100500 раз подряд Find валит юньку, а 4 раза подряд Find нет. дуй в документацию и изучай как работает "while"
тебе выше все описали. но не судьба походу догнать
seaman, Тут скорее задвоение (затроение, и т.д.)
robertono не удаляет базовые меши после создания большого меша
robertono, посмоьтри и попробуй следующее:
1. возможна ошибка в скрипте : в новый меш входит старый меш + все(!) меши объектов, а не только новорожденные
2. оценивай количество памяти соразмерно генерации объектов. количество занятой памяти должно рости линейно от количества объектов. если растет по экспоненте, значит ты дублируешь данные в меш. (см п.1)
3. Посмотри Destroy чего ты делаешь. Возможно, что Destroy убивает объект, а не удаляет меш. Сделай руками вызов дестроя меша из скрипта.
проверь экземпляр меша он д.б. равен null
1. возможна ошибка в скрипте : в новый меш входит старый меш + все(!) меши объектов, а не только новорожденные
Комбинирую я меш так: сначала беру первые 100 кубов из массива, комбинирую, потом следующие 100 + тот который уже скомбинирован и так делается меш.
1. Проверяешь нет ли на них ссылок. Если нашел - обнуляешь, или лучше перестраиваешь приложение так, чтобы этих ссылок просто не было.
Нету, всё было обнулено.
я не помню как это назвается отображение DC и вершин.UnityStats.drawCalls ,UnityStats.triangles ,UnityStats.vertices
что это ? В юнити такого нету.
А кстати, можно ли как то в OnGUI вывести из статистики что то конкретное? Например сколько сейчас памяти видео заполнено.
Комбинирую я меш так: сначала беру первые 100 кубов из массива, комбинирую, потом следующие 100 + тот который уже скомбинирован и так делается меш.
Накидал код по быстрому взяв за основу пример. Сам меши не ковырял, но ,думаю, ты допилишь до рабочего.
public Mesh MycombineMesh(Component Mymesh,Component[] meshFilter) <
Mesh NewMesh;
Component[] WorkMeshFilters = new Component[meshFilter.length];
CombineInstance[] combine = new CombineInstance[meshFilters.Length];
combine[0].mesh = Mymesh.sharedMesh;
combine[0].transform = Mymesh.transform.localToWorldMatrix;
Из полезного тебе здесь только
что это ? В юнити такого нету.
А кстати, можно ли как то в OnGUI вывести из статистики что то конкретное? Например сколько сейчас памяти видео заполнено.
Накидал код по быстрому взяв за основу пример. Сам меши не ковырял, но ,думаю, ты допилишь до рабочего.
Незнаю зачем Вы сделали для меня этот пример. Хотя я вроде не говорил что всё уже готово..
Мой код-велосипед из 318 строчек, выполняющий покадровое комбинирование. Я всё таки выложу это.
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
public class CombineQueue : MonoBehaviour
<
public GameObject destroyObj;
public int maxFrame = 100; //Указывает максимальное колличество комбинирования в 1 кадр
int curFrame; //Указывает сколько будет обработано объектов в текущий кадр. Current Frame
//CombineInstance[] combine;
//instanceClass newInstance = new instanceClass();
//newInstance.subject = "subject";
//newInstance.body = "body";
//public GameObject[] Building;
//public CombineInstance[] Instance;
int i = 0;
int frame = 0;
bool cc = true;
bool allow = false;
string curStatus = "";
Material emptyMat;
void Start ()
<
emptyMat = (Material)Resources.Load ("Diffuse");
//maxFrame--;
>
public void AddToQueue (CombineInstance[] insList, Transform[] cube, GameObject caller)
<
//Debug.Log ("New instances! Count : " + insList.Length + " . Caller: " + caller.name);
for (int i = 0; i < insList.Length - 1; i++) < //Внимание! Мы не проверяем последний item, так как он дубликат первого item.
BuildingClass newInstance = new BuildingClass ();
newInstance.instance = insList [i];
newInstance.cube = cube [i];
newInstance.gameobject = caller;
InstanceList.Add (newInstance);
>
/*
foreach (CombineInstance ins in insList) <
BuildingClass newInstance = new BuildingClass ();
newInstance.instance = ins;
newInstance.gameobject = caller;
InstanceList.Add (newInstance);
>
*/
//Debug.Log ("Instances added! Length: " + InstanceList.Count);
>
IEnumerator CoroutineCombine (GameObject go, CombineInstance[] instances, int verts)
<
BuildingMeshes bMeshes = go.GetComponent ();
if (bMeshes.Meshes.Count == 0) < //Если у здания нету ещё не одной скомбинированной группы
GameObject obj = new GameObject ();
obj.AddComponent ("MeshFilter");
obj.AddComponent ("MeshRenderer");
obj.renderer.material = emptyMat;
obj.transform.parent = go.gameObject.transform;
obj.name = go.name + "_Combined_1";
obj.GetComponent ().mesh = new Mesh ();
obj.GetComponent () .mesh.CombineMeshes (instances);
obj.AddComponent ("MeshCollider");
obj.gameObject.active = true;
GameObject obj = new GameObject ();
obj.AddComponent ("MeshFilter");
obj.AddComponent ("MeshRenderer");
obj.renderer.material = emptyMat;
obj.transform.parent = go.gameObject.transform;
obj.name = go.name + "_Combined_" + bMeshes.Meshes.Count;
//obj.GetComponent ().mesh = new Mesh ();
obj.GetComponent () .mesh.CombineMeshes (AddOne (instances, newOne));
obj.AddComponent ("MeshCollider");
obj.gameObject.active = true;
Destroy (bMeshes.Meshes [bMeshes.Meshes.Count - 1].mesh);
Resources.UnloadUnusedAssets();
//System.GC.Collect();
bMeshes.Meshes [bMeshes.Meshes.Count - 1].mesh = obj;
//GameObject gameObj = bMeshes.Meshes[bMeshes.Meshes.Count - 1].mesh;
//Mesh mesh = bMeshes.Meshes[bMeshes.Meshes.Count - 1].mesh.GetComponent ().mesh;
//Transform transform = bMeshes.Meshes[bMeshes.Meshes.Count - 1].mesh.transform;
//GameObject destroyObj = bMeshes.Meshes[bMeshes.Meshes.Count - 1].mesh;
//int vert = bMeshes.Meshes[bMeshes.Meshes.Count - 1].vertices;
//bMeshes.Meshes.RemoveAt(bMeshes.Meshes.Count - 1);
//destroyObj.GetComponent ().mesh = null;
//Destroy(destroyObj);
//destroyObj = null;
//newOne = null;
//bMeshes.Meshes[bMeshes.Meshes.Count -1].mesh = obj;
//CombinedMeshes newInstance = new CombinedMeshes();
//newInstance.mesh = obj;
//newInstance.vertices = vert;
//bMeshes.Meshes.Add(newInstance);
>
>
>
StopCoroutine("CoroutineCombine");
yield return 0;
>
void Update ()
<
if (Input.GetKeyDown (KeyCode.H)) <
Resources.UnloadUnusedAssets();
>
//Считаем сколько объектов будет обработано в следующий кадр
if (InstanceList.Count - 1 >= maxFrame) <
curFrame = maxFrame - 1;
curStatus = "maxFrame ";
> else if (InstanceList.Count - 1 < maxFrame) <
curFrame = InstanceList.Count - 1;
curStatus = "elseFrame ";
//Debug.Log(InstanceList.Count + " " + curFrame);
>
if (InstanceList.Count != 0)
//Считаем сколько разных групп
for (int i = 0; i GameObject go = InstanceList [i].gameobject;
if (!Exists (go))
Buildings.Add (go);
>
//Пробуем работать с каждой группой
foreach (GameObject go in Buildings) <
Instance.Clear (); //Очищаем список для создания нового списка комбинирования
Objects.Clear (); //Очищаем список для создания нового списка объектов (это для распределения по мешам в 60к вершин)
for (int i = 0; i GameObject instanceObj = null;
if (i < InstanceList.Count)
instanceObj = InstanceList [i].cube.gameObject;
else if (i >= InstanceList.Count)
instanceObj = InstanceList [InstanceList.Count - 1].cube.gameObject;
//Debug.Log(instanceObj);
if (instanceObj != null) <
if (instanceObj.GetComponent () != null) <
if (InstanceList [0].gameobject == go) <
//Debug.Log("i = " + i + " " + curStatus + "CurFrame: " + curFrame + " " + "Count: " + InstanceList.Count + " " + frame);
Instance.Add (InstanceList [0].instance);
Objects.Add (InstanceList [0].cube.gameObject.transform);
InstanceList [0].cube.gameObject.SetActive (false);
InstanceList.RemoveAt (0);
//Debug.Log(InstanceList.Count);
//curFrame--;
allow = true;
>
>
>
>
//Debug.Log(Buildings.Count);
if (allow)
//Combine (go, ToBuiltin (Instance), vertexCount (Objects));
StartCoroutine(CoroutineCombine(go,ToBuiltin (Instance),vertexCount (Objects)));
allow = false;
>
frame++;
>
>
void Combine (GameObject go, CombineInstance[] instances, int verts)
<
BuildingMeshes bMeshes = go.GetComponent ();
if (bMeshes.Meshes.Count == 0) < //Если у здания нету ещё не одной скомбинированной группы
GameObject obj = new GameObject ();
obj.AddComponent ("MeshFilter");
obj.AddComponent ("MeshRenderer");
obj.renderer.material = emptyMat;
obj.transform.parent = go.gameObject.transform;
obj.name = go.name + "_Combined_1";
obj.GetComponent ().mesh = new Mesh ();
obj.GetComponent () .mesh.CombineMeshes (instances);
obj.AddComponent ("MeshCollider");
obj.gameObject.active = true;
GameObject obj = new GameObject ();
obj.AddComponent ("MeshFilter");
obj.AddComponent ("MeshRenderer");
obj.renderer.material = emptyMat;
obj.transform.parent = go.gameObject.transform;
obj.name = go.name + "_Combined_" + bMeshes.Meshes.Count;
//obj.GetComponent ().mesh = new Mesh ();
obj.GetComponent () .mesh.CombineMeshes (AddOne (instances, newOne));
obj.AddComponent ("MeshCollider");
obj.gameObject.active = true;
Destroy (bMeshes.Meshes [bMeshes.Meshes.Count - 1].mesh);
bMeshes.Meshes [bMeshes.Meshes.Count - 1].mesh = obj;
//GameObject gameObj = bMeshes.Meshes[bMeshes.Meshes.Count - 1].mesh;
//Mesh mesh = bMeshes.Meshes[bMeshes.Meshes.Count - 1].mesh.GetComponent ().mesh;
//Transform transform = bMeshes.Meshes[bMeshes.Meshes.Count - 1].mesh.transform;
//GameObject destroyObj = bMeshes.Meshes[bMeshes.Meshes.Count - 1].mesh;
//int vert = bMeshes.Meshes[bMeshes.Meshes.Count - 1].vertices;
//bMeshes.Meshes.RemoveAt(bMeshes.Meshes.Count - 1);
//destroyObj.GetComponent ().mesh = null;
//Destroy(destroyObj);
//destroyObj = null;
//newOne = null;
//bMeshes.Meshes[bMeshes.Meshes.Count -1].mesh = obj;
//CombinedMeshes newInstance = new CombinedMeshes();
//newInstance.mesh = obj;
//newInstance.vertices = vert;
//bMeshes.Meshes.Add(newInstance);
>
>
>
//Resources.UnloadUnusedAssets(); //Чистим память от мусора что бы не забивать её
>
bool Exists (GameObject inputGo)
<
foreach (GameObject go in Buildings) <
if (inputGo == go)
return true;
>
return false;
>
CombineInstance[] ToBuiltin (List input)
<
CombineInstance[] combine = new CombineInstance[input.Count];
int i = 0;
foreach (CombineInstance instance in input) <
combine [i] = instance;
i++;
>
return combine;
>
int vertexCount (List objects)
<
if (objects.Count > 0) <
int verts = 0;
foreach (Transform tr in objects) <
verts += tr.gameObject.GetComponent ().mesh.vertexCount;
>
return verts;
> else
return 0;
>
CombineInstance[] AddOne (CombineInstance[] array, GameObject newObj)
<
CombineInstance[] combine = new CombineInstance[array.Length + 1];
for (int i = 0; i < array.Length; i++) <
combine [i] = array [i];
>
combine [combine.Length - 1].mesh = newObj.GetComponent ().mesh;
combine [combine.Length - 1].transform = newObj.transform.localToWorldMatrix;
//Debug.Log("Input: " + array.Length + " " + "Output: " + combine.Length);
return combine;
>
CombineInstance[] AddOne (CombineInstance[] array, Mesh mesh, Transform trans)
<
CombineInstance[] combine = new CombineInstance[array.Length];
for (int i = 0; i < array.Length; i++) <
combine [i] = array [i];
>
combine [combine.Length - 1].mesh = mesh;
combine [combine.Length - 1].transform = trans.localToWorldMatrix;
return combine;
>
>
public class BuildingClass
<
public CombineInstance instance;
public Transform cube;
public GameObject gameobject;
>
Добавляются туда меши с помощью функции AddToQueue, до этого обрабатывается в моём MeshMerger:
using UnityEngine;
using System.Collections;
using System.Threading;
public class MeshMerger : MonoBehaviour
<
//Этот скрипт получает объект который нужно скомбинировать
//Сортирует, делит по массивам, отправляет на переработку в CombineQueue
MeshFilter[] meshFilters;
Transform[] cubes;
CombineInstance[] combine;
//Функция Combine с массивом объектов пока отключена!
public void Combine(GameObject[] GameObjects,GameObject father) <
for(int i = 0;i < GameObjects.Length;i++)<
meshFilters[i] = GameObjects[i].GetComponent ();
>
combine = new CombineInstance[meshFilters.Length];
int a = 0;
while (a < meshFilters.Length) <
combine [a].mesh = meshFilters [a].mesh;
combine [a].transform = meshFilters [a].transform.localToWorldMatrix;
//meshFilters [i].gameObject.active = false;
a++;
>
//GetComponent().AddToQueue(combine,GameObjects,father);
//Debug.Log("Request from CombineMesh. Sent request to CombineQueue as multiple objects");
>
public void Combine (GameObject GameObj)
<
meshFilters = GameObj.GetComponentsInChildren ();
cubes = GameObj.GetComponentsInChildren ();
cubes[0] = cubes[cubes.Length - 1];
cubes[cubes.Length - 1] = null;
combine = new CombineInstance[meshFilters.Length];
int i = 0;
while (i < meshFilters.Length) <
combine [i].mesh = meshFilters [i].mesh;
combine [i].transform = meshFilters [i].transform.localToWorldMatrix;
//meshFilters [i].gameObject.active = false;
i++;
>
GetComponent().AddToQueue(combine,cubes,GameObj);
//Debug.Log("Request from CombineMesh. Sent request to CombineQueue as one object");
>
>
Разработка компьютерных игр. С чего начать? Это просто! С нуля до разработчика игр: как начать создавать свою игру. Заказать создание игры.
При создании объекта, строки или массива, память для его хранения выделяется из центрального пула, который называется куча (heap). Когда использование элемента прекращается, память, которую он занимал, можно будет освободить и использовать для чего-нибудь ещё. В прошлом, выделение и освобождение этих блоков памяти с помощью вызовов соответствующих методов в основном лежало на плечах программистов. Теперь за вас памятью автоматически управляют среды выполнения, например, движок Mono у Unity. Автоматическое управление памятью требует меньше усилий при написании кода, чем прямое выделение / освобождение и значительно уменьшает потенциал для утечки памяти (ситуации, когда память была выделена, но впоследствии так и не была освобождена).
Значимые и ссылочные типы
При вызове функции, значения её параметров копируются в зону памяти, зарезервированную специально для этого вызова. Типы данных, которые занимают всего лишь несколько байт могут быть скопированы легко и быстро. Однако, обычно объекты, строки и массивы гораздо больше, и было бы очень неэффективно копировать эти данные как обычно. К счастью, это не обязательно; реальное место хранения для больших элементов выделяется из кучи и для запоминания его местоположения используется небольшое “указательное” значение. После этого, во время передачи параметра нужно будет скопировать только указатель. Пока система среды выполнения может найти определяемый указателем элемент, можно использовать одиночную копию данных так часто, как это требуется.
Типы, которые хранятся напрямую и копируются при передаче параметра, называются значимыми типами (value types). В них включены integer, float, boolean и структурные типы Unity (например, Color и Vector3 ). Типы, которые выделяются из кучи, после чего доступ к ним получается при помощи указателя, называются ссылочными типами, т.к. значения хранящиеся в переменной только “ссылаются” на реальные данные. Примеры ссылочных типов включают объекты, строки и переменные.
Выделение и сборка мусора
Менеджер памяти отслеживает зоны в куче, которые определены как неиспользуемые. При запросе нового блока памяти (допустим, при создании экземпляра объекта), менеджер выбирает неиспользуемую зону, из которой следует выделить блок, и затем удаляет выделенную память из зоны известного неиспользуемого пространства. Последующие запросы обрабатываются тем же способом, пока в неиспользуемой зоне будет достаточно места для выделения блока необходимого размера. Доступ к ссылочному элементу в куче может быть получен только пока есть ссылочные переменные, которые могут его найти. Если все ссылки к блоку памяти пропадут (т.е. ссылочные переменные были переназначены или они являются локальными переменными, которые теперь вне контекста), то занимаемая им память может быть ещё раз безопасно выделена.
Чтобы определить, какие блоки кучи больше не используются, менеджер памяти просматривает все активные ссылочные переменные и отмечает блоки, к которым они ссылаются как “live” (используемые). В конце поиска, любое пространство между используемыми блоками менеджером считается пустым и в будущем может быть использовано для выделения. По очевидным причинам, процесс обнаружения и освобождения неиспользуемой памяти известен как сборка мусора(Garbage Collection, или GC для сокращения).
Оптимизация
Сборка мусора - это автоматический и невидимый программисту процесс, но процесс сборки на деле требует некоторого времени “закулисной” работы процессора. При правильном использовании, автоматическое управление памятью обычно не уступает по производительности ручному выделению. Тем не менее, для программиста важно избегать ошибок, которые будут вызывать сборку чаще чем надо и выражаться в задержках работы.
Есть несколько алгоритмов с сомнительной репутацией, которые могут быть ночным кошмаром для GC, хотя на первый взгляд они выглядят невинно. Постоянное объединение строк - классический пример:-
Ключевой деталью является то, что новые части не добавляются к строке один за одним. На самом деле, в каждой итерации цикла предыдущее содержание переменной “умирает” - выделяется целая новая строка для размещения в ней оригинальной части и новой части в конце. Т.к. строка становится длиннее, то с увеличивающимся значением i, значение потребляемого пространства кучи также повышается и с лёгкостью достигает сотни байтов свободного пространства кучи при каждом вызове этой функции. Если вам нужно объединить много строк вместе, то более подходящим вариантом будет класс Mono библиотеки System.Text.StringBuilder.
Однако, даже повторяющееся соединение не вызовет много неприятностей, если не вызывать его часто, и в Unity под этим обычно подразумевается каждый кадр. Что-то вроде:-
…будет выделять новые строки при каждом вызове Update и генерировать постоянный поток нового мусора. Большую часть этого можно сохранить, обновляя текст только тогда, когда счёт меняется:-
Другая потенциальная проблема появляется, когда функция возвращает массив:-
Это очень элегантный и удобный тип функции, если создаётся новый массив заполненный значениями. Однако, если её постоянно вызывать, то каждый раз будет выделяться свежая память. Т.к. массивы могут быть очень большими, свободное пространство кучи может быть постоянно использовано, что приведёт к частой сборке мусора. Единственный способ избежать этой проблемы, это извлечь пользу из того факта, что массив - ссылочный тип. Массив, использованный в функции как параметр, может быть использован внутри этой функции и результаты останутся после возврата функции. Функция, вроде указанной выше, часто может быть замещена чем-нибудь вроде:-
Она просто замещает существующие данные массива новыми значениями. Хотя она и требует стартового выделения массива, чтобы быть выполненной в коде вызова (который выглядит не очень элегантно), функция не будет создавать какого-либо нового мусора, когда она будет вызвана.
Запрос на сборку мусора
Как упоминалось выше, лучше всего избегать выделений настолько, насколько возможно. Однако, учитывая, что полностью от них избавиться нельзя, есть 2 основные стратегии, которые вы можете использовать для минимизации их влияния на игровой процесс:-
Маленькая куча с быстрой и частой сборкой мусора
Эта стратегия лучше всего подходит играм с долгими сессиями игрового процесса, когда стабильный FPS стоит на первом месте. Подобная игра обычно будет выделять небольшие блоки почаще, но эти блоки будут в использовании совсем не долго. Типичный размер кучи при использовании подобной стратегии на iOS составляет около 200 КБ, и сборка мусора занимает где-то 5 миллисекунд на iPhone 3G. Если размер кучи увеличить до 1 МБ, то сборка займёт где-то 7 миллисекунд. Следовательно, иногда будет разумно запрашивать сборку мусора раз в определённый интервал. В результате сборка будет проходить чаще чем строго необходимо, но сборка пройдёт быстрее с минимальным влиянием на игровой процесс:-
Однако вам следует использовать эту технику аккуратно и проверять статистку профайлера, чтобы убедиться, что это действительно уменьшает время сборки мусора для вашей игры.
Большая куча с медленной, но не частой сборкой мусора
Эта стратегия лучше всего подходит для игр, где выделения (и последующие сборки) памяти относительно редки и их можно провести во время пауз в игровом процессе. Таким образом кучу можно сделать максимально большой (но не на столько, чтобы перегрузить память системы, что может вызвать закрытие вашего приложения). Однако, среда запуска Mono по возможности избегает автоматического расширения кучи. Вы можете расширить кучу вручную путём предварительного выделения некоторого пространства во время запуска (т.е. вы вызываете “бесполезный” объект, который выделен только для влияния на менеджер памяти):-
Достаточно большую кучу не следует заполнять под завязку между этими паузами в игровом процессе, включающими в себя сборку. Когда такая пауза происходит, вы можете напрямую запросить сборку:-
Опять же, вам следует аккуратно использовать эту стратегию и обращать внимание на статистику профайлера, нежели просто предполагать, что желаемый эффект достигнут.
Повторно используемые пулы объектов
Если много случаев, в которых вы можете избежать генерации мусора просто уменьшив число создающихся и уничтожающихся объектов. Существуют различные типы объектов в играх, такие как снаряды, которые встречаются в игре снова и снова, даже если одновременно в игре их будет находиться небольшое количество. В таких случаях зачастую можно использовать объект ещё раз, нежели уничтожать старый и замещать его новым.
Дополнительная информация
Захотелось поделиться неожиданной для меня находкой, связанной с тем как выделяется память и как легко столкнуться с крупной утечкой, которую без перезапуска не почистить.
Как столкнулся, в нашем проекте есть заготовленные куски уровней в виде prefab, в них и меши и логика и эффекты. При генерации уровень собирается из нескольких блоков. Так же есть Scriptable Object для хранения описания игрового уровня, какие блоки, что за уровень и.т.п., на менеджере всего этого дела висел список этих описаний уровней. В определенный момент столкнулся с тем что свободной оперативной памяти 0.
Так же в проекте Scriptable Object, в через инспектор накинута ссылка на prefab. Лежать может в любой папке.
В чем собственно проблема? Как только Unity видит переменную со ссылкой на scriptable object из проекта, он тянет его в память, а потом и все на что он ссылается, в том числе префабы блоков. А префабы блоков тянут конечно весь тот контент, что содержат. Для меня лично было не очевидно что ссылки на контейнеры данных, scriptable object, потянут за собой по цепочке пол проекта в память :) А у меня бюджет памяти не очень большой тем временем.
Решение. Можно загружать описания уровня по имени из Resources, разбить их вообще на два файла, в одном текстовая информация, для выбора уровней, а в другом связи с ассетами. Второй грузить только когда они нужны на сцене. Можно еще и с относительно новыми Adressables повозиться.
Есть ещё одна менее очевидная проблема, если загрузить Scriptable Object в память при помощи:
preset = Resources.Load("ScriptablePreset") as ScriptableOne;
И потом удалить со сцены объект, который это делал (или вообще все объекты и даже сцену другую загрузить), то загруженный Scriptable Object будет висеть мертвым грузом в памяти и вместе с ним все на что ссылается он и далее по цепочке. Можно звать Garbage Collector, Resources.UnloadUnusedAssets, грузить другие сцены, все будет бестолку и сколько угодно времени, в памяти будет висеть то, на что никто уже не ссылается. Получалось что уровень я создал, потом удалил, но в памяти весь контент висит дальше, получается большой утечка памяти и через сколько то запусков на устройстве с сильно ограниченной памятью, типа Switch/Mobile игра крашнется.
Решение. Но как же быть? Выход то простой, нужно на OnDestruction явно обнулить переменную, где мы хранили указатель на Scriptable Object, GC сочтет это за руководство к действию.
private void OnDestroy()
Вот так, старые добрые деструкторы пиши. На всякий случай даже уточнил на форуме Unity не баг ли это, нет, "так и задумано". В profiler / profile analyzer | memory profiler такие ситуации отследить не очень то просто, у ассетов будет просто не указано кто его удерживает в памяти, т.к. обьект удален, и становится непонятно почему его сборщик мусора не ловит.
p.s. Все тестирование использования памяти было выполнено в development билдах с подключением профайлера, как на ПК, так и на консоли.
Пару месяцев назад перескочил на юнити и уже почти заканчиваем разработку, но тут всплыл какой то глюк с утечкой памяти, даже в юнити запускаешь несколько раз игру и память тает на глазах. Потом я узнал что, Ангри бердс епик оказывается тоже на юнити сделали и я думаю, многие заметили какой кошмар получился, игра вылетает и жутко тормозит, хотя по идее, у них достаточно ресурсов чтоб нанять хороших разрабов.
Собственно хочу обратится с вопросом к сообществу, если какие то хитрости при разработке на юнити, чтоб не возникло ужасных последствий утечки памяти?
p.s. Вопрос касается разработки 2Д игр.
Вариантов может быть много. Надо понимать, что внутренние ресурсы игровые, такие как текстуры, звуки итд - это не managed code.
Первый кандидат - это статическая ссылка на ресурсы из скрипта - тогда Unity не будет их удалять.
Еще можно нагенерить ресурсов из кода (меш там создавать, текстурку) каждый кадр новый или и ссылку на них забыть удалить. Натечь может много.
Но по фотографии это не лечится - нужно профайлером гонять.
Про это я первым делом подумал, что надо пробежаться по коду и все проверить. Меня смущает почему АнгриБердс не смогли сделать нормально игру, может есть какой то жуткий камень в этом.
DenBraun
> если какие то хитрости при разработке на юнити, чтоб не возникло ужасных последствий утечки памяти?
Есть, уходить с юнити на нормальный C++ движок.
Dmitry10
> Есть, уходить с юнити на нормальный C++ движок.
На С++ нету утечек что ли ? :)
innuendo
сделаешь сам утечки, тогда и будут :)
innuendo
> На С++ нету утечек что ли ?
Если их специально не делать, то нету.
war_zes
> сделаешь сам утечки, тогда и будут :)
+100500
Dmitry10
> > На С++ нету утечек что ли ?
> Если их специально не делать, то нету.
innuendo
Так об этом и речь, поэтому и рекомендую забить на криворукий юнити и перейти на нормальный C++ движок.
Dmitry10
> и перейти на нормальный C++ движок.
На котором также возможны мемлики от кривых рук ? :)
innuendo
перейти на нормальный C++ движок, где мемликов нет.
Dmitry10
> рекомендую забить на криворукий юнити и перейти на нормальный C++ движок.
Есть мнение, что в мемликах ТС виноват вовсе не Юнити.
Черт, не распознал Джимника сразу.
Dmitry10
> перейти на нормальный C++ движок, где мемликов нет.
Если руки кривые - мемлики бывают даже там, где их нет :)
Ogra
> Есть мнение, что в мемликах ТС виноват вовсе не Юнити.
Да, я тоже слышал, что это на него зелёные человечки порчу наводят.
> Если руки кривые - мемлики бывают даже там, где их нет :)
В точку, поэтому лучше не связываться с криворукими движками, типа Юниту, потому что даже если твой код будет идеально безупречен, сам Юнити будет тормозить и лагать.
Dmitry10
> даже если твой код будет идеально безупречен, сам Юнити будет тормозить и
> лагать.
Читайте также: