Помогите с оптимизацией

Раздел, посвящённый самому важному - скорости.

Помогите с оптимизацией

Сообщение Leon_Kote 05 дек 2018, 18:08

Делаю небольшую игру на основе процедурно-генерируемого мира, столкнулся с проблемой что при передвижении все чанки пытаются загрузиться за один кадр и из-за этого ловишь фриз на несколько секунд. В скрипте WorldGen видно, что я пытался разгрузить код с помощью карантина, но у меня не получилось. Прошу вашей помощи. Для полного представления вот проект *клик*
Сначала создаётся квадрат из чанков 9*9, по середине которого находится игрок, с помощью функции CreateChunk(), которая в циклах.
После создания всех чанков они начинают отрисовываться функцией Chunk.UpdateChunk()
Важное замечание: отрисовывать чанки надо начинать только после создания всех, чтобы на их стыке не было дыр.
При пересечении границ чанков весь квадрат из чанков 9*9 проверяется на наличие чанков вокруг игрока на этом расстоянии, если их не достаёт, то скрипт добавляет чанки и уничтожает те, которые находятся дальше этого расстояния.
Синтаксис:
Используется csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WorldGen : MonoBehaviour {

        public static List<Chunk> Chunks = new List<Chunk>();

        public GameObject Player;
        public GameObject Chunk;

        Vector2 OldPlayerPos;
        public Vector2 PlayerPos;

        // Use this for initialization
        void Start () {
                for (int x = 0; x < 9; x++)
                {
                        for (int z = 0; z < 9; z++)
                        {
                                CreateChunk(new Vector3((PlayerPos[0] - 4 + x) * 16, 0, (PlayerPos[1] - 4 + z) * 16));
                        }
                }
                foreach (Chunk Chunk in WorldGen.Chunks)
                {
                        Chunk.UpdateChunk();
                }
        }

        // Update is called once per frame
        void Update () {
                PlayerPos = new Vector2(Mathf.Floor(Player.transform.position.x / 16), Mathf.Floor(Player.transform.position.z / 16));
                if (PlayerPos == OldPlayerPos) return;
                StartCoroutine(Generate());
                OldPlayerPos = PlayerPos;
        }

        IEnumerator Generate()
        {
                for (int x = 0; x < 9; x++)
                {
                        for (int z = 0; z < 9; z++)
                        {
                                int i;
                                for (i = 0; i < Chunks.Count; i++)
                                {
                                        if (Chunks[i].transform.position == new Vector3((PlayerPos[0] - 4 + x) * 16, 0, (PlayerPos[1] - 4 + z) * 16)) break;
                                }
                                if (i == Chunks.Count)
                                {
                                        CreateChunk(new Vector3((PlayerPos[0] - 4 + x) * 16, 0, (PlayerPos[1] - 4 + z) * 16));
                                        //yield return new WaitForSeconds(1);
                                }
                        }
                }
                for (int i = 0; i < Chunks.Count; i++)
                {
                        Vector3 PlyPos = Player.transform.position;
                        PlyPos[1] = 0;
                        if (Vector3.Distance(PlyPos, Chunks[i].transform.position) > 120)
                        {
                                Destroy(Chunks[i].gameObject);
                                Chunks.RemoveAt(i);
                                //yield return new WaitForSeconds(1);
                        }
                }
                for (int i = 0; i < Chunks.Count; i++)
                {
                        Chunks[i].UpdateChunk();
                        //yield return new WaitForSeconds(1);
                }
                yield return null;
        }

        void CreateChunk(Vector3 ChunkPos)
        {
                GameObject CreatedChunk = Instantiate(Chunk, ChunkPos, Quaternion.identity);
                CreatedChunk.GetComponent<Chunk>().GenerateChunk();
        }
}
Leon_Kote
UNец
 
Сообщения: 6
Зарегистрирован: 05 дек 2018, 09:29

Re: Помогите с оптимизацией

Сообщение Ziza 06 дек 2018, 03:45

Корутины вам здесь не помогут)
Основная проблема в самом подходе - вызывать очень прожорливый UpdateChunk() у всех, хотя по факту вызвать вам его надо только у добавленных и их соседей. Это вам уже ускорит генерацию раз в 10. Тем более, что вы ее вызываете даже у удаленных со сцены, так как вы удаляете только gameObject, но в списке они как были так и остаются. Нужно или переиспользовать или удалять совсем, а то у вас утечка памяти происходит, что тоже чревато в долгосрочной перспективе.

Но лагать даже после этих правок все равно будет, вы сами посмотрите со стороны что вы делаете, например в UpdateChunk():
Синтаксис:
Используется csharp
for (int x = 0; x < 16; x++)
{
        for (int y = 0; y < 64; y++)
        {
                for (int z = 0; z < 16; z++)
                {
                        if (x == 15)
                        {
                                foreach (Chunk Chunk in WorldGen.Chunks)
                                {
                                        if (transform.position + new Vector3(16, 0, 0) != Chunk.transform.position) continue;
                                        if (Chunk.Map[0, y, z] == 0) BlockFront(x, y, z, Blocks[Map[x, y, z], 0]);
                                }
                        }
                        ...
        }
}
 

Вы понимаете, что этот foreach переберет 81 элемент (при карте 9x9) 16x64 раза?) Т.е. вот эта проверка:
Синтаксис:
Используется csharp
if (transform.position + new Vector3(16, 0, 0) != Chunk.transform.position) continue;

Выполнится 82944 раза. Хотя отношения к вышеприведенным циклам вообще не имеет, вы могли вполне вынести ее за цикл, а отсюда foreach вообще убрать и написать просто что-то вроде:
Синтаксис:
Используется csharp
if (x == 15 && hasFrontBlock)

Таких вот foreach у вас там дальше по коду еще три штуки. При этом и сам этот UpdateChunk() тоже ведь вызывается у вас для каждого объекта карты, т.е. как минимум 9x9 раз. Для того чтобы вы оценили масштаб посчитаем и получим, что вот эти проверки на позиции происходят при каждом выходе персонажем за блок 26.873.856 раз. Оценили?) Оптимизируйте код и все поедет) И подумайте над более удобным хранением карты и проверках, проверять все по позиции тоже такое себе по производительности, и чревато багами из-за погрешности float в векторах.
Аватара пользователя
Ziza
UNец
 
Сообщения: 38
Зарегистрирован: 02 ноя 2018, 23:07

Re: Помогите с оптимизацией

Сообщение Leon_Kote 06 дек 2018, 08:22

Ziza писал(а):Корутины вам здесь не помогут)
Основная проблема в самом подходе - вызывать очень прожорливый UpdateChunk() у всех, хотя по факту вызвать вам его надо только у добавленных и их соседей. Это вам уже ускорит генерацию раз в 10. Тем более, что вы ее вызываете даже у удаленных со сцены, так как вы удаляете только gameObject, но в списке они как были так и остаются. Нужно или переиспользовать или удалять совсем, а то у вас утечка памяти происходит, что тоже чревато в долгосрочной перспективе.

Но лагать даже после этих правок все равно будет, вы сами посмотрите со стороны что вы делаете, например в UpdateChunk():
Синтаксис:
Используется csharp
for (int x = 0; x < 16; x++)
{
        for (int y = 0; y < 64; y++)
        {
                for (int z = 0; z < 16; z++)
                {
                        if (x == 15)
                        {
                                foreach (Chunk Chunk in WorldGen.Chunks)
                                {
                                        if (transform.position + new Vector3(16, 0, 0) != Chunk.transform.position) continue;
                                        if (Chunk.Map[0, y, z] == 0) BlockFront(x, y, z, Blocks[Map[x, y, z], 0]);
                                }
                        }
                        ...
        }
}
 

Вы понимаете, что этот foreach переберет 81 элемент (при карте 9x9) 16x64 раза?) Т.е. вот эта проверка:
Синтаксис:
Используется csharp
if (transform.position + new Vector3(16, 0, 0) != Chunk.transform.position) continue;

Выполнится 82944 раза. Хотя отношения к вышеприведенным циклам вообще не имеет, вы могли вполне вынести ее за цикл, а отсюда foreach вообще убрать и написать просто что-то вроде:
Синтаксис:
Используется csharp
if (x == 15 && hasFrontBlock)

Таких вот foreach у вас там дальше по коду еще три штуки. При этом и сам этот UpdateChunk() тоже ведь вызывается у вас для каждого объекта карты, т.е. как минимум 9x9 раз. Для того чтобы вы оценили масштаб посчитаем и получим, что вот эти проверки на позиции происходят при каждом выходе персонажем за блок 26.873.856 раз. Оценили?) Оптимизируйте код и все поедет) И подумайте над более удобным хранением карты и проверках, проверять все по позиции тоже такое себе по производительности, и чревато багами из-за погрешности float в векторах.

Тогда нужен новый способ нахождения соседних чанков. Я был бы очень благодарен, если бы вы подсказали другой метод.
Leon_Kote
UNец
 
Сообщения: 6
Зарегистрирован: 05 дек 2018, 09:29

Re: Помогите с оптимизацией

Сообщение Ziza 06 дек 2018, 09:27

Leon_Kote писал(а):Тогда нужен новый способ нахождения соседних чанков. Я был бы очень благодарен, если бы вы подсказали другой метод.

По хорошему бы да, нужен, определять по позиции не очень удачная затея, но в этом случае нужно будет перепилить код наверное на треть. Но чтобы от вложенных избавиться новый способ не нужен, у вас же нет никакой зависимости от внешних циклов. Можно для начала сделать просто вот так:
Синтаксис:
Используется csharp
public void UpdateChunk()
{
        Chunk front = null;
        Chunk back = null;
        Chunk right = null;
        Chunk left = null;
        var position = transform.position;
       
        foreach (Chunk chunk in WorldGen.Chunks)
        {
                var chunkPosition = chunk.transform.position;
                if (position + new Vector3(16, 0, 0) == chunkPosition)
                        front = chunk;
                else if (position - new Vector3(16, 0, 0) == chunkPosition)
                        back = chunk;
                else if (position + new Vector3(0, 0, 16) == chunkPosition)
                        right = chunk;
                else if (position - new Vector3(0, 0, 16) == chunkPosition)
                        left = chunk;
        }

        for (int x = 0; x < 16; x++)
        {
                for (int y = 0; y < 64; y++)
                {
                        for (int z = 0; z < 16; z++)
                        {
                                if (Map[x, y, z] == 0)
                                        continue;

                                if ((x < 15 && Map[x + 1, y, z] == 0) || (x == 15 && front != null && front.Map[0, y, z] == 0))
                                        BlockFront(x, y, z, Blocks[Map[x, y, z], 0]);
                               
                                if ((x > 0 && Map[x - 1, y, z] == 0) || (x == 0 && back != null && back.Map[15, y, z] == 0))
                                        BlockBack(x, y, z, Blocks[Map[x, y, z], 1]);
                               
                                if ((z < 15 && Map[x, y, z + 1] == 0) || (z == 15 && right != null && right.Map[x, y, 0] == 0))
                                        BlockRight(x, y, z, Blocks[Map[x, y, z], 2]);
                               
                                if ((z > 0 && Map[x, y, z - 1] == 0) || (z == 0 && left != null && left.Map[x, y, 15] == 0))
                                        BlockLeft(x, y, z, Blocks[Map[x, y, z], 3]);
                               
                                if (y < 63 && Map[x, y + 1, z] == 0)
                                        BlockTop(x, y, z, Blocks[Map[x, y, z], 4]);
                                if (y > 0 && Map[x, y - 1, z] == 0)
                                        BlockBot(x, y, z, Blocks[Map[x, y, z], 5]);
                        }
                }
        }
       
        Mesh.Clear();
        Mesh.vertices = newVertices.ToArray();
        Mesh.uv = newUV.ToArray();
        Mesh.triangles = newTriangles.ToArray();
        Mesh.RecalculateNormals();

        Collider.sharedMesh = null;
        Collider.sharedMesh = Mesh;

        newVertices.Clear();
        newUV.Clear();
        newTriangles.Clear();

        faceCount = 0;
}

Игра стала заметно меньше лагать, кстати) Если зависание на 5 сек, конечно можно назвать "Лагание") Осталось избавиться от вызова у каждого и уже должно стать терпимо. А если все еще будет подлагивать, тогда вот уже можно будет пробовать сглаживать корутинами)
Последний раз редактировалось Ziza 06 дек 2018, 12:31, всего редактировалось 1 раз.
Аватара пользователя
Ziza
UNец
 
Сообщения: 38
Зарегистрирован: 02 ноя 2018, 23:07

Re: Помогите с оптимизацией

Сообщение 1max1 06 дек 2018, 10:44

Не смог разобраться в коде, поэтому спрошу.
Мир бесконечно генерируется или один раз в начале, а потом просто инстансами добавляются чанки на позиции?
И зачем нужен метод UpdateChunk?
Аватара пользователя
1max1
Адепт
 
Сообщения: 1616
Зарегистрирован: 28 июн 2017, 10:51

Re: Помогите с оптимизацией

Сообщение Leon_Kote 06 дек 2018, 16:41

Ziza писал(а):По хорошему бы да, нужен, определять по позиции не очень удачная затея, но в этом случае нужно будет перепилить код наверное на треть. Но чтобы от вложенных избавиться новый способ не нужен, у вас же нет никакой зависимости от внешних циклов. Можно для начала сделать просто вот так:
Синтаксис:
Используется csharp
public void UpdateChunk()
{
        Chunk front = null;
        Chunk back = null;
        Chunk right = null;
        Chunk left = null;
        var position = transform.position;
       
        foreach (Chunk chunk in WorldGen.Chunks)
        {
                var chunkPosition = chunk.transform.position;
                if (position + new Vector3(16, 0, 0) == chunkPosition)
                        front = chunk;
                else if (position - new Vector3(16, 0, 0) == chunkPosition)
                        back = chunk;
                else if (position + new Vector3(0, 0, 16) == chunkPosition)
                        right = chunk;
                else if (position - new Vector3(0, 0, 16) == chunkPosition)
                        left = chunk;
        }

        for (int x = 0; x < 16; x++)
        {
                for (int y = 0; y < 64; y++)
                {
                        for (int z = 0; z < 16; z++)
                        {
                                if (Map[x, y, z] == 0)
                                        continue;

                                if ((x < 15 && Map[x + 1, y, z] == 0) || (x == 15 && front != null && front.Map[0, y, z] == 0))
                                        BlockFront(x, y, z, Blocks[Map[x, y, z], 0]);
                               
                                if ((x > 0 && Map[x - 1, y, z] == 0) || (x == 0 && back != null && back.Map[15, y, z] == 0))
                                        BlockBack(x, y, z, Blocks[Map[x, y, z], 1]);
                               
                                if ((z < 15 && Map[x, y, z + 1] == 0) || (z == 15 && right != null && right.Map[x, y, 0] == 0))
                                        BlockRight(x, y, z, Blocks[Map[x, y, z], 2]);
                               
                                if ((z > 0 && Map[x, y, z - 1] == 0) || (z == 0 && left != null && left.Map[x, y, 15] == 0))
                                        BlockLeft(x, y, z, Blocks[Map[x, y, z], 3]);
                               
                                if (y < 63 && Map[x, y + 1, z] == 0)
                                        BlockTop(x, y, z, Blocks[Map[x, y, z], 4]);
                                if (y > 0 && Map[x, y - 1, z] == 0)
                                        BlockBot(x, y, z, Blocks[Map[x, y, z], 5]);
                        }
                }
        }
       
        Mesh.Clear();
        Mesh.vertices = newVertices.ToArray();
        Mesh.uv = newUV.ToArray();
        Mesh.triangles = newTriangles.ToArray();
        Mesh.RecalculateNormals();

        Collider.sharedMesh = null;
        Collider.sharedMesh = Mesh;

        newVertices.Clear();
        newUV.Clear();
        newTriangles.Clear();

        faceCount = 0;
}

Игра стала заметно меньше лагать, кстати) Если зависание на 5 сек, конечно можно назвать "Лагание") Осталось избавиться от вызова у каждого и уже должно стать терпимо. А если все еще будет подлагивать, тогда вот уже можно будет пробовать сглаживать корутинами)

Спасибо вам. Вы сказали, что по хорошему нужно перепилить код на треть, это не страшно, и мне бы хотелось узнать поподробнее, что вы имеете ввиду. Мне правда нужно, чтобы всё это было играбельно.
Leon_Kote
UNец
 
Сообщения: 6
Зарегистрирован: 05 дек 2018, 09:29

Re: Помогите с оптимизацией

Сообщение Leon_Kote 06 дек 2018, 16:55

1max1 писал(а):Не смог разобраться в коде, поэтому спрошу.
Мир бесконечно генерируется или один раз в начале, а потом просто инстансами добавляются чанки на позиции?
И зачем нужен метод UpdateChunk?

Сначала заранее генерируется место появления игрока, и в дальнейшем, как вы и сказали, добавляются чанки, а пройденные удаляются.
Метод UpdateChunk идёт как продолжение GenerateChunk, который нужно вызывать после создания соседних чанков того экземпляра, у которого этот метод вызывается. Иначе на пересечении у этой группы чанков могут отсутствовать некоторые стороны блоков.
Leon_Kote
UNец
 
Сообщения: 6
Зарегистрирован: 05 дек 2018, 09:29

Re: Помогите с оптимизацией

Сообщение Ziza 07 дек 2018, 07:36

Ну, если до завтра подождете, попробую подумать, как поправить, чтобы начало нормально работать. Сегодня не могу - рабочий день, если только вечером удастся добраться, но не факт)
Аватара пользователя
Ziza
UNец
 
Сообщения: 38
Зарегистрирован: 02 ноя 2018, 23:07

Re: Помогите с оптимизацией

Сообщение Leon_Kote 07 дек 2018, 17:05

Ziza писал(а):Ну, если до завтра подождете, попробую подумать, как поправить, чтобы начало нормально работать. Сегодня не могу - рабочий день, если только вечером удастся добраться, но не факт)

Было бы хорошо
Последний раз редактировалось Leon_Kote 08 дек 2018, 13:37, всего редактировалось 1 раз.
Leon_Kote
UNец
 
Сообщения: 6
Зарегистрирован: 05 дек 2018, 09:29

Re: Помогите с оптимизацией

Сообщение Ziza 07 дек 2018, 21:45

Мда...
Вызвал подозрение метод GetTheoreticalByte() точнее странное использование своего же метода.
Берите и пользуйтесь https://github.com/theWildSushii/Mine-In-Unity что вы людям мозги то парите.
Аватара пользователя
Ziza
UNец
 
Сообщения: 38
Зарегистрирован: 02 ноя 2018, 23:07

Re: Помогите с оптимизацией

Сообщение Leon_Kote 08 дек 2018, 13:36

Ziza писал(а):Мда...
Вызвал подозрение метод GetTheoreticalByte() точнее странное использование своего же метода.
Берите и пользуйтесь https://github.com/theWildSushii/Mine-In-Unity что вы людям мозги то парите.

Этот метод - временное решение для использования 3D шума на время отладки прогрузки чанков, естественно я буду писать свою обёртку под 3D шум.
Если бы вы внимательно почитали тот же скрипт Chunk в этом репозитории, то увидели бы, что там проблем не меньше.
Я точно не собираюсь делать очередной клон одной, до сих пор популярной в своих кругах, игры.
Я обратился на этот форум не просто так.
В любом случае спасибо за оказанную помощь.
Leon_Kote
UNец
 
Сообщения: 6
Зарегистрирован: 05 дек 2018, 09:29

Re: Помогите с оптимизацией

Сообщение Ziza 08 дек 2018, 22:03

Leon_Kote писал(а):Если бы вы внимательно почитали тот же скрипт Chunk в этом репозитории, то увидели бы, что там проблем не меньше.

Не вижу, там каких-то фатальных проблем)
Основных проблем, которые были в вашем коде там нет. Ни обновления всех чанков, ни лишних вложенных циклов. Да и на моей машине идет стабильно в 120 FPS. Т.е. с оптимизацией там все нормально. Единственное, что они тоже по позиции определяют, но для вызова 1 раз только во время создания чанка, а не постоянно при добавлении новых - это вполне приемлемо, это и вам в советах то шло чисто как дополнение)

Leon_Kote писал(а):Я точно не собираюсь делать очередной клон одной, до сих пор популярной в своих кругах, игры.

Так никто и не говорит, что вы собираетесь делать клон. Такой резкий ответ был по другой причине. Я трачу два часа, чтобы сначала разобраться в коде, подумать как его оптимизировать, чтобы потом узнать, что это уже сделали. И судя по пересекающимся методам вы об этом знали. Бомбанет пожалуй)
Почему бы просто не посмотреть, как они оптимизировали. Их код идет под свободной лицензией, хоть форк делайте, допиливайте и продавайте. Конечно это предполагает открытый исходный код, но вы то форк делать и не будете, а только посмотрите, как ребята оптимизировали код с тем же подходом, что и у вас.
Аватара пользователя
Ziza
UNец
 
Сообщения: 38
Зарегистрирован: 02 ноя 2018, 23:07


Вернуться в Оптимизация

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

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 4