c# - 基于字节数组的地形中的流水

标签 c# arrays unity-game-engine graph-theory terrain

我使用字节数组来描述游戏中的地形,每个字节值代表不同的 block 类型

我创建了一个网格来模拟水的传播,我为一个 block 上的水量提供一个字节值,然后通过增加一个 block 的值来传播它,这会导致它传播到具有水少了,我做了一个小视频来展示:(值55是一堵墙,0空气,1到4水,它是自上而下的视角)

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

但是我在完成它时遇到了困难,我不希望水位上升到 4 以上,当已经处于值 4 的 block 变为 5 时,我希望它流过所有其他 block ,直到所有包含的水都被值为 4。但是,在我当前的代码中,如果我设置条件,使水无论怎样流动,如果它的值为 4 或更大,它都会以无限循环结束。

这是当前的相关代码:

 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;
    }

这是我试图添加的条件,但它在所有可用 block 达到 4 之前就中断了,(水被困在两个 block 之间的无限循环中,这两个 block 无限地互相传递水)

    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)

希望得到有关如何解决此问题的建议,谢谢!

最佳答案

这是您正在寻找的行为吗?

Demo

假设你有一个字节类型的2D网格,你可以使用DFS或BFS来实现这一点。 就我而言,我创建了一个具有 Value 属性的 Cell 类,设置该属性后,会自动更新单元格的文本和颜色。

接下来,我制作了一个网格管理器来存储单元格的 2D 网格。触发功能驻留在管理器中。每当我触发某个单元格时,它就会运行 DFS 从该单元格开始更新。 DFS逻辑有条件检查边界和墙壁,也限制水的溢出。

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 方法中,我以一定的速率迭代网格,并检查是否有任何单元格具有相邻的单元格,使得它们的水位相差 2。我填充该单元格并将其标记为已更新,以便它们不会在当前帧中再次更新。

网格更新方法是一个简单的 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;
}

希望这有帮助!

关于c# - 基于字节数组的地形中的流水,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60960372/

相关文章:

c# - 如何在 Unity3D Google Cardboard 中限制头部旋转?

c# - 从 Powershell 调用的 c# 中的 StringExpansion

c# - 如何用OpenCV进行图像光照校正?

python - 如何将数据框中的序列拟合到多类问题? |凯拉斯 | Pandas |值错误 : setting an array element with a sequence

c# - 为什么会发生这种情况? (SharpFont/Monogame 错误)

c# - 一旦 ARCore 在 Unity 应用程序中加载,文本就会消失

c# - 多个 Entity Framework 表破坏 WCF

c# - 我可以嵌套 Parallel.Invoke 方法吗?

c++ - C2664 无法从 'initializer list' 转换参数

unity-game-engine - 新的 Unity 2017 命名约定如何运作?