Страница 1 из 1

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

СообщениеДобавлено: 05 дек 2018, 18:08
Leon_Kote
Делаю небольшую игру на основе процедурно-генерируемого мира, столкнулся с проблемой что при передвижении все чанки пытаются загрузиться за один кадр и из-за этого ловишь фриз на несколько секунд. В скрипте 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();
        }
}

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

СообщениеДобавлено: 06 дек 2018, 03:45
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 в векторах.

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

СообщениеДобавлено: 06 дек 2018, 08:22
Leon_Kote
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 в векторах.

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

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

СообщениеДобавлено: 06 дек 2018, 09:27
Ziza
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 сек, конечно можно назвать "Лагание") Осталось избавиться от вызова у каждого и уже должно стать терпимо. А если все еще будет подлагивать, тогда вот уже можно будет пробовать сглаживать корутинами)

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

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

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

СообщениеДобавлено: 06 дек 2018, 16:41
Leon_Kote
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 сек, конечно можно назвать "Лагание") Осталось избавиться от вызова у каждого и уже должно стать терпимо. А если все еще будет подлагивать, тогда вот уже можно будет пробовать сглаживать корутинами)

Спасибо вам. Вы сказали, что по хорошему нужно перепилить код на треть, это не страшно, и мне бы хотелось узнать поподробнее, что вы имеете ввиду. Мне правда нужно, чтобы всё это было играбельно.

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

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

Сначала заранее генерируется место появления игрока, и в дальнейшем, как вы и сказали, добавляются чанки, а пройденные удаляются.
Метод UpdateChunk идёт как продолжение GenerateChunk, который нужно вызывать после создания соседних чанков того экземпляра, у которого этот метод вызывается. Иначе на пересечении у этой группы чанков могут отсутствовать некоторые стороны блоков.

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

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

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

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

Было бы хорошо

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

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

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

СообщениеДобавлено: 08 дек 2018, 13:36
Leon_Kote
Ziza писал(а):Мда...
Вызвал подозрение метод GetTheoreticalByte() точнее странное использование своего же метода.
Берите и пользуйтесь https://github.com/theWildSushii/Mine-In-Unity что вы людям мозги то парите.

Этот метод - временное решение для использования 3D шума на время отладки прогрузки чанков, естественно я буду писать свою обёртку под 3D шум.
Если бы вы внимательно почитали тот же скрипт Chunk в этом репозитории, то увидели бы, что там проблем не меньше.
Я точно не собираюсь делать очередной клон одной, до сих пор популярной в своих кругах, игры.
Я обратился на этот форум не просто так.
В любом случае спасибо за оказанную помощь.

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

СообщениеДобавлено: 08 дек 2018, 22:03
Ziza
Leon_Kote писал(а):Если бы вы внимательно почитали тот же скрипт Chunk в этом репозитории, то увидели бы, что там проблем не меньше.

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

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

Так никто и не говорит, что вы собираетесь делать клон. Такой резкий ответ был по другой причине. Я трачу два часа, чтобы сначала разобраться в коде, подумать как его оптимизировать, чтобы потом узнать, что это уже сделали. И судя по пересекающимся методам вы об этом знали. Бомбанет пожалуй)
Почему бы просто не посмотреть, как они оптимизировали. Их код идет под свободной лицензией, хоть форк делайте, допиливайте и продавайте. Конечно это предполагает открытый исходный код, но вы то форк делать и не будете, а только посмотрите, как ребята оптимизировали код с тем же подходом, что и у вас.

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

СообщениеДобавлено: 07 мар 2019, 17:39
NAGIBATOR228pacan
Тс можешь сразу бросить это гиблое дело на unity, даже если ты сможешь реализовать все с точки зрения скрипта дальше пойдет ещё больший геморой с рендером и производительностью. Такие игры лучше делать не на unity как я понял если ты не хочешь 20-30 фпс на телефонах, да я верю что если поизвращаться можно добиться хороших результатов, но куда легче реализовать это все на каком-нибудь чистом опенгл

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

СообщениеДобавлено: 07 мар 2019, 20:07
seaman
Все зависит от кривизны рук.
Пример. Реалтайм рендеринг процедурно сгенерированной планеты на Unity
https://github.com/Scrawk/Proland-To-Unity

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

СообщениеДобавлено: 11 мар 2019, 09:56
NAGIBATOR228pacan
seaman писал(а):Все зависит от кривизны рук.
Пример. Реалтайм рендеринг процедурно сгенерированной планеты на Unity
https://github.com/Scrawk/Proland-To-Unity

Ну если это не супермега ультра полигональная сфера то это похвально, к сожалению так и не смог запустить проект и посмотреть что да как ибо миллиард ошибок, а чем в кучу этих скриптов залазить легче уж самому попробовать сделать подобное, как раз делаю игру в космосе может даже пригодится
И да даже вот взять майнкрафт, и взять прорисовку 32 на 32 чанка(сейчас в майнкарфте куда больше уже можно отрисовать) абсолютно плоских то это получается 32*32*(16*16 квадов) * 4 вершины то получается 1 миллион вершин, с учетом того что все чанки с минимальным количеством квадов, а там может быть не 16 на 16 квадов, а например 4*16*256 если полный чанк в разрезе и еще больше может быть, и как хорошо юнити это потянет? И это только для видеокарты нагрузка, там еще и процессор прожиарается сильно, может я конечно чего-то не понимаю, но в любом случае делать майнкрафт в консоле будет и то проще чем на юнити. А нынешние прототипы которые я видел дико лагучие даже с отрисовкой куда меньшей чем 32 на 32