Проточная вода в байтовом массиве Terrain - PullRequest
2 голосов
/ 01 апреля 2020

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

Я создал сетку для имитации распространения воды, я даю байтовое значение для количество воды в блоке, а затем я распределяю его, увеличивая значение одного блока, что приводит к его распространению на блоки, в которых меньше воды, я сделал небольшое видео, чтобы показать: (значение 55 - стена, 0 воздух , От 1 до 4 воды, перспектива сверху вниз)

https://www.youtube.com/watch?v=SOmnejfmPe0

Однако у меня возникли проблемы с завершением, я не хочу, чтобы уровень воды поднимался выше 4, когда блок, который уже имеет значение 4, переходит к 5, я хочу, чтобы он проходил через все остальные блоки, пока вся содержащаяся вода не будет иметь значение 4. Однако с моим текущим кодом, если я установлю условие так, чтобы вода не текла независимо от того, что если оно имеет значение 4 или более, оно заканчивается бесконечным l oop.

, здесь текущий соответствующий код:

 public Vector3Int PosToCheck;
    public IEnumerator FlowRoutine(int x, int y, int z)
    {
        PosToCheck = KeepPosInBounds(new Vector3Int(x - 1, y, z));
        if ( GridArray[x,y,z] > GridArray[PosToCheck.x,PosToCheck.y,PosToCheck.z] + 1  && GridArray[PosToCheck.x, PosToCheck.y, PosToCheck.z] != 55)
        {
            GridArray[x, y, z] -= 1;
            GridArray[PosToCheck.x, PosToCheck.y, PosToCheck.z] += 1;

            yield return StartCoroutine(FlowRoutine(PosToCheck.x, PosToCheck.y, PosToCheck.z));
        }
        else
        {
            PosToCheck = KeepPosInBounds(new Vector3Int(x, y + 1, z));
            if (GridArray[x,y,z] > GridArray[PosToCheck.x,PosToCheck.y,PosToCheck.z] + 1  && GridArray[PosToCheck.x, PosToCheck.y, PosToCheck.z] != 55)
            {
                GridArray[x, y, z] -= 1;
                GridArray[PosToCheck.x, PosToCheck.y, PosToCheck.z] += 1;

                yield return StartCoroutine(FlowRoutine(PosToCheck.x, PosToCheck.y, PosToCheck.z));
            }
            else
            {
                PosToCheck = KeepPosInBounds(new Vector3Int(x + 1, y, z));
                if (GridArray[x,y,z] > GridArray[PosToCheck.x,PosToCheck.y,PosToCheck.z] + 1  && GridArray[PosToCheck.x, PosToCheck.y, PosToCheck.z] != 55)
                {
                    GridArray[x, y, z] -= 1;
                    GridArray[PosToCheck.x, PosToCheck.y, PosToCheck.z] += 1;

                    yield return StartCoroutine(FlowRoutine(PosToCheck.x, PosToCheck.y, PosToCheck.z));
                }
                else
                {
                    PosToCheck = KeepPosInBounds(new Vector3Int(x , y-1, z));
                    if (GridArray[x,y,z] > GridArray[PosToCheck.x,PosToCheck.y,PosToCheck.z] + 1  && GridArray[PosToCheck.x, PosToCheck.y, PosToCheck.z] != 55)
                    {
                        GridArray[x, y, z] -= 1;
                        GridArray[PosToCheck.x, PosToCheck.y, PosToCheck.z] += 1;

                        yield return StartCoroutine(FlowRoutine(PosToCheck.x, PosToCheck.y, PosToCheck.z));
                    }
                }
            }
        }

        UpdateAllButtons();

        yield return null;
    }

    public Vector3Int KeepPosInBounds(Vector3Int newPos)
    {
        if (newPos.x < 0) { newPos.x = XGridSize - 1; }
        if (newPos.y < 0) { newPos.y = YGridSize - 1; }
        if (newPos.z < 0) { newPos.z = ZGridSize - 1; }
        if (newPos.x >= XGridSize) { newPos.x = 0; }
        if (newPos.y >= YGridSize) { newPos.y = 0; }
        if (newPos.z >= ZGridSize) { newPos.z = 0; }

        return newPos;
    }

, и это условие, которое я пытался добавить но он разбивается до того, как все доступные блоки равны 4 (вода попадает в бесконечное число l oop между двумя блоками, которые бесконечно передают воду друг другу)

    if ((GridArray[x,y,z] > GridArray[PosToCheck.x,PosToCheck.y,PosToCheck.z] + 1 || GridArray[x, y, z]>=WaterCap) && GridArray[PosToCheck.x, PosToCheck.y, PosToCheck.z] != 55)

Буду признателен за советы о том, как решить эту проблему Спасибо!

1 Ответ

3 голосов
/ 01 апреля 2020

Это поведение, которое вы ищете?

Demo

Если у вас есть двумерная сетка байтового типа, вы можете использовать DFS или BFS для достичь этого. В моем случае я создал класс Cell со свойством Value, которое при его установке автоматически обновляет текст и цвет ячейки.

Затем я создал диспетчер сетки, хранящий 2D-сетку ячеек. Функция триггера находится в диспетчере. Всякий раз, когда я запускаю определенную ячейку, запускается DFS для обновления, начиная с этой ячейки. У лога DFS c есть условия для проверки границ и стен, а также ограничения переполнения водой.

void Trigger(int x, int y)
{
    bool[,] visited = new bool[yCount, xCount];

    for(int i=0; i<yCount; i++)
    {
        for(int j=0; j<xCount; j++)
        {
            visited[i, j] = false;
        }
    }

    DfsUpdate(y, x, visited);
}

void DfsUpdate(int i, int j, bool[,] visited)
{
    // Check out of bounds
    if (i < 0 || i >= yCount) return;
    if (j < 0 || j >= xCount) return;

    if (visited[i, j]) return;

    // Mark as visited
    visited[i, j] = true;

    if (cells[i, j].Value == 55) // Its a wall
        return;

    if (cells[i, j].Value == 0)
    {
        // Needs 1 update and return
        cells[i, j].Value++;
        return;
    }

    // Restrict upto 4 if its water
    cells[i, j].Value = (byte) Mathf.Min(cells[i, j].Value + 1, 4);

    // Recursively call neighbouting cells
    DfsUpdate(i + 1, j, visited);
    DfsUpdate(i - 1, j, visited);
    DfsUpdate(i, j + 1, visited);
    DfsUpdate(i, j - 1, visited);
}

Вы также можете использовать BFS, но DFS работает нормально, и реализация проще в случае DFS.

Редактировать:

Теперь для заполнения сетки требуется 72 клика. Проверьте в левом нижнем углу, чтобы увидеть количество щелчков мыши.

Updated Preview

Я изменил код. Теперь, когда я нажимаю на ячейку, если она уже 4 (заполнена), она находит ближайшую сетку, которая не полностью заполнена, и добавляет 1 к ней, в противном случае она заполняется сама. В методе Update я выполняю итерацию по сетке с определенной скоростью и проверяю, есть ли ячейки, имеющие соседа, так что их уровни воды имеют разницу в два. Я заполняю эту ячейку и отмечаю ее как обновленную, чтобы они не обновлялись снова в текущем кадре.

Метод обновления сетки - это простая BFS, которая добавляет 1 к ближайшему незаполненному соседу, если таковой существует .

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

Вот код.

void GridUpdate(int i, int j)
{
    bool[,] visited = new bool[yCount, xCount];
    Queue<int> iQ = new Queue<int>();
    Queue<int> jQ = new Queue<int>();
    iQ.Enqueue(i);
    jQ.Enqueue(j);

    while(iQ.Count > 0)
    {
        int si = iQ.Dequeue();
        int sj = jQ.Dequeue();

        if (!CheckBounds(si, sj)) continue;

        if (cells[si, sj].Value == 55) continue;

        if (cells[si, sj].Value < 4)
        {
            cells[si, sj].Value++;
            return;
        }

        // Up
        iQ.Enqueue(si - 1);
        jQ.Enqueue(sj);

        // Down
        iQ.Enqueue(si + 1);
        jQ.Enqueue(sj);

        // Left
        iQ.Enqueue(si);
        jQ.Enqueue(sj - 1);

        // Right
        iQ.Enqueue(si);
        jQ.Enqueue(sj + 1);
    }
}

void Update()
{
    timer += Time.deltaTime * 1000f;
    if (timer >= updateDelay)
    {
        timer -= updateDelay;
        bool[,] updated = new bool[yCount, xCount];

        for (int i = 0; i < yCount; i++)
        {
            for (int j = 0; j < xCount; j++)
            {
                if (updated[i, j] || cells[i,j].Value == 55) continue;

                if (CheckBounds(i-1, j) && !updated[i-1, j] && cells[i, j].Value - cells[i - 1, j].Value >= 2)
                {
                    updated[i - 1, j] = true;
                    cells[i - 1, j].Value++;
                    cells[i, j].Value--;
                }
                else if (CheckBounds(i+1, j) && !updated[i + 1, j] && cells[i, j].Value - cells[i + 1, j].Value >= 2)
                {
                    updated[i + 1, j] = true;
                    cells[i + 1, j].Value++;
                    cells[i, j].Value--;
                }
                else if (CheckBounds(i, j-1) && !updated[i, j-1] && cells[i, j].Value - cells[i, j-1].Value >= 2)
                {
                    updated[i, j-1] = true;
                    cells[i, j-1].Value++;
                    cells[i, j].Value--;
                }
                else if (CheckBounds(i, j+1) && !updated[i, j + 1] && cells[i, j].Value - cells[i, j + 1].Value >= 2)
                {
                    updated[i, j + 1] = true;
                    cells[i, j + 1].Value++;
                    cells[i, j].Value--;
                }
            }
        }
    }
}

// Checks if a given i and j are not out of bounds of the grid
bool CheckBounds(int i, int j)
{
    if (j < 0 || j >= xCount) return false;
    if (i < 0 || i >= yCount) return false;
    return true;
}

Надеюсь, это поможет!

...