많은 작은 충돌기를 더 큰 충돌기로 결합 있는지 모두 확인하는

수천 개의 격자 사각형으로 구성된 타일 맵을 사용하여 게임을 만들고 있습니다. 현재 각 사각형에는 충돌을 확인하기위한 사각형 충돌체가 있습니다.

여기에 이미지 설명을 입력하십시오

그러나 수천 개의 작은 블록으로 인해 충돌이 있는지 모두 확인하는 것은 비효율적입니다. 만약 타일 맵이 미리 이런 식으로 나타날 것이라는 것을 알았다면, 수천 개의 작은 것 대신에 3 개 또는 4 개의 큰 콜 라이더를 사용할 수있었습니다.

인접한 여러 작은 타일을 최대 큰 타일로 결합하기위한 일종의 표준 알고리즘이 있습니까? 그렇다면 누군가가 여기에 설명하거나 그러한 알고리즘에 대한 문헌을 가리킬 수 있습니까?

대안 적으로, 이러한 방식으로 타일 콜 라이더를 전처리하는 것은 완전히 잘못된 접근법 일 수있다. 그렇다면 매우 많은 수의 충돌체의 효율성을 다루는 올바른 방법은 무엇입니까?



답변

이 알고리즘 이 love2d engine ( lua language )에 유용하다는 것을 알았습니다.

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 예제를 따르십시오. 빨간색으로 벽이 충돌하는 것을 볼 수 있습니다.


답변

파괴 가능한 지형을 만들려고한다면 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;
}

이상적으로는 훨씬 더 자세한 게시물을 작성합니다. 그러나 이것은 도움이 될 수 있습니다. 궁금한 점이 있으면 언제든지 문의하거나 문의하십시오.