将许多小碰撞器组合成更大的碰撞器


13

我正在使用由数千个网格正方形组成的图块地图创建游戏。目前,每个广场上都有一个广场对撞机,用于检查碰撞。

在此处输入图片说明

但是,对于成千上万个微小的块,检查它们是否全部碰撞是低效率的。如果我事先知道tilemap看起来像这样,那么我可能只使用了3或4个大型对撞机,而不是数千个小型对撞机:

在此处输入图片说明

是否存在某种标准算法,可将许多小相邻的小块合并为最大的小块?如果是这样,有人可以在这里描述它,还是指向有关这种算法的文献?

另外,也许以这种方式预处理瓦片碰撞器完全是错误的方法。如果是这样,应对大量对撞机效率的正确方法是什么?


您是否打算使地形可破坏?
jgallant 2015年

@乔恩 我没有考虑过。我想象允许破坏性将使问题变得更加棘手(因为其中一个小型对撞机可能会被摧毁,这意味着大型组合对撞机需要重新计算,对吗?)
Craig Innes

是。这就是为什么我问。通常,您会将所有地形合并成一个网格。如果计划允许地形可破坏,则可以使用另一种方法,该方法仅在外部块上设置碰撞器。您将预先计算什么块是“边缘块”,然后为这些块分配可合并的对撞机。(jgallant.com/images/uranus/chunk.png- 图像陈旧且不完美,但演示了该技术)您正在使用什么作为游戏引擎/平台?
jgallant 2015年

@Jon我正在使用Unity作为我的游戏引擎,并使用BoxCollider2D组件来处理贴砖碰撞。我没有提到我的特定平台,因为我认为它可能对游戏开发者堆栈交换更有用,以获得对该问题的更一般的答案。关于您的“边缘块”方法,您能否提交一个包含该方法算法精确信息的答案?还是您有关于此类技术资源的链接?
Craig Innes

1
我确实为此有一个Unity实现,因为它并不是真正的枯燥乏味,所以需要花一些时间来编写。我目前在工作,源代码在家里。如果您可以等到今晚才能得到答案。看起来是这样的: jgallant.com/images/landgen.gif
jgallant 2015年

Answers:


5

我发现此算法对于love2d引擎(lua语言)很有用

https://love2d.org/wiki/TileMerging

-- map_width and map_height are the dimensions of the map
-- is_wall_f checks if a tile is a wall

local rectangles = {} -- Each rectangle covers a grid of wall tiles

for x = 0, map_width - 1 do
    local start_y
    local end_y

    for y = 0, map_height - 1 do
        if is_wall_f(x, y) then
            if not start_y then
                start_y = y
            end
            end_y = y
        elseif start_y then
            local overlaps = {}
            for _, r in ipairs(rectangles) do
                if (r.end_x == x - 1)
                  and (start_y <= r.start_y)
                  and (end_y >= r.end_y) then
                    table.insert(overlaps, r)
                end
            end
            table.sort(
                overlaps,
                function (a, b)
                    return a.start_y < b.start_y
                end
            )

            for _, r in ipairs(overlaps) do
                if start_y < r.start_y then
                    local new_rect = {
                        start_x = x,
                        start_y = start_y,
                        end_x = x,
                        end_y = r.start_y - 1
                    }
                    table.insert(rectangles, new_rect)
                    start_y = r.start_y
                end

                if start_y == r.start_y then
                    r.end_x = r.end_x + 1

                    if end_y == r.end_y then
                        start_y = nil
                        end_y = nil
                    elseif end_y > r.end_y then
                        start_y = r.end_y + 1
                    end
                end
            end

            if start_y then
                local new_rect = {
                    start_x = x,
                    start_y = start_y,
                    end_x = x,
                    end_y = end_y
                }
                table.insert(rectangles, new_rect)

                start_y = nil
                end_y = nil
            end
        end
    end

    if start_y then
        local new_rect = {
            start_x = x,
            start_y = start_y,
            end_x = x,
            end_y = end_y
        }
        table.insert(rectangles, new_rect)

        start_y = nil
        end_y = nil
    end
end
Here's how the rectangles would be used for physics.
-- Use contents of rectangles to create physics bodies
-- phys_world is the world, wall_rects is the list of...
-- wall rectangles

for _, r in ipairs(rectangles) do
    local start_x = r.start_x * TILE_SIZE
    local start_y = r.start_y * TILE_SIZE
    local width = (r.end_x - r.start_x + 1) * TILE_SIZE
    local height = (r.end_y - r.start_y + 1) * TILE_SIZE

    local x = start_x + (width / 2)
    local y = start_y + (height / 2)

    local body = love.physics.newBody(phys_world, x, y, 0, 0)
    local shape = love.physics.newRectangleShape(body, 0, 0,
      width, height)

    shape:setFriction(0)

    table.insert(wall_rects, {body = body, shape = shape})
end

下面按照我当前项目中的love2d示例进行操作。用红色可以看到我的墙壁对撞机。

在此处输入图片说明


有C#版本吗?是否有带有文档注释的版本?该算法可以适用于3D吗?
亚伦·弗兰克

3

如果您要创建可破坏的地形,那么我在Unity中所做的方法就是仅在世界的边缘区域设置碰撞器。因此,例如,这是您要完成的工作:

绿色方块表示包含对撞机的瓷砖

所有这些绿色方块都包含一个对撞机,而其余的则没有。这样可以节省大量计算时间。如果您破坏了一个块,则可以轻松激活相邻块上的对撞机。请记住,激活/停用对撞机是昂贵的,应谨慎进行。

因此,Tile资源如下所示:

统一中的图块资源

它是标准的游戏对象,但也可以合并。另请注意,默认情况下,对撞机设置为禁用。仅当它是边缘图块时,我们才会激活。

如果您正在静态加载世界,则无需合并磁贴。您可以将它们全部加载到一个镜头中,计算它们到边缘的距离,并在需要时应用对撞机。

如果要动态加载,则最好使用图块池。这是刷新循环的编辑示例。它根据当前相机视图加载图块:

public void Refresh(Rect view)
{       
    //Each Tile in the world uses 1 Unity Unit
    //Based on the passed in Rect, we calc the start and end X/Y values of the tiles presently on screen        
    int startx = view.x < 0 ? (int)(view.x + (-view.x % (1)) - 1) : (int)(view.x - (view.x % (1)));
    int starty = view.y < 0 ? (int)(view.y + (-view.y % (1)) - 1) : (int)(view.y - (view.y % (1)));

    int endx = startx + (int)(view.width);
    int endy = starty - (int)(view.height);

    int width = endx - startx;
    int height = starty - endy;

    //Create a disposable hashset to store the tiles that are currently in view
    HashSet<Tile> InCurrentView = new HashSet<Tile>();

    //Loop through all the visible tiles
    for (int i = startx; i <= endx; i += 1)
    {
        for (int j = starty; j >= endy; j -= 1)
        {
            int x = i - startx;
            int y = starty - j;

            if (j > 0 && j < Height)
            {
                //Get Tile (I wrap my world, that is why I have this mod here)
                Tile tile = Blocks[Helper.mod(i, Width), j];

                //Add tile to the current view
                InCurrentView.Add(tile);

                //Load tile if needed
                if (!tile.Blank)
                {
                    if (!LoadedTiles.Contains(tile))
                    {                           
                        if (TilePool.AvailableCount > 0)
                        {
                            //Grab a tile from the pool
                            Pool<PoolableGameObject>.Node node = TilePool.Get();

                            //Disable the collider if we are not at the edge
                            if (tile.EdgeDistance != 1)
                                node.Item.GO.GetComponent<BoxCollider2D>().enabled = false;

                            //Update tile rendering details
                            node.Item.Set(tile, new Vector2(i, j), DirtSprites[tile.TextureID], tile.Collidable, tile.Blank);
                            tile.PoolableGameObject = node;
                            node.Item.Refresh(tile);

                            //Tile is now loaded, add to LoadedTiles hashset
                            LoadedTiles.Add(tile);

                            //if Tile is edge block, then we enable the collider
                            if (tile.Collidable && tile.EdgeDistance == 1)
                                node.Item.GO.GetComponent<BoxCollider2D>().enabled = true;
                        }
                    }                       
                }                  
            }
        }
    }

    //Get a list of tiles that are no longer in the view
    HashSet<Tile> ToRemove = new HashSet<Tile>();
    foreach (Tile tile in LoadedTiles)
    {
        if (!InCurrentView.Contains(tile))
        {
            ToRemove.Add(tile);
        }
    }

    //Return these tiles to the Pool 
    //this would be the simplest form of cleanup -- Ideally you would do this based on the distance of the tile from the viewport
    foreach (Tile tile in ToRemove)
    {
        LoadedTiles.Remove(tile);
        tile.PoolableGameObject.Item.GO.GetComponent<BoxCollider2D>().enabled = false;
        tile.PoolableGameObject.Item.GO.transform.position = new Vector2(Int32.MinValue, Int32.MinValue);
        TilePool.Return(tile.PoolableGameObject);            
    }

    LastView = view;
}

理想情况下,我会写一篇更详细的文章,因为幕后还有很多事情要做。但是,这可能会对您有所帮助。如有疑问,请随时与我联系。


接受dnkdrone的答案,因为它可以直接回答最初提出的问题。但是,已对这个答案表示赞同,因为它为有效的替代方案提供了宝贵的指导
Craig Innes

@CraigInnes没有问题的人。只是想帮忙。积分无关紧要:)
jgallant 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.