많은 작은 충돌기를 더 큰 충돌기로 결합


13

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

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

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

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

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

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


지형을 파괴 할 계획입니까?
jgallant

@ 존. 나는 이것을 고려하지 않았다. 나는 destructibility이 문제가 상당히 어렵게 만들 것입니다 수 있도록 상상 (작은 colliders 중 하나가 파괴 될 수 있기 때문에, 큰 결합 colliders을 의미하는 것은 바로, 재 계산 될 필요가있을 것이다?)
크레이그 인스

예. 이것이 내가 묻는 이유입니다. 일반적으로 모든 지형을 메쉬로 결합합니다. 터 레인을 파괴 할 수있게하려는 경우 외부 블록에만 충돌체를 설정하는 다른 방법을 사용할 수 있습니다. "가장자리 블록"인 블록을 미리 계산 한 다음 풀링 가능한 충돌체로 해당 블록을 할당합니다. ( jgallant.com/images/uranus/chunk.png - 이미지는 오래되고 완벽하지,하지만이 기술을 보여줍니다) 당신이 게임 엔진 / 플랫폼에 사용하는 무엇을?
jgallant

@Jon 저는 타일 충돌을 위해 BoxCollider2D 구성 요소와 함께 Unity를 게임 엔진으로 사용하고 있습니다. 이 문제에 대한 일반적인 답변을 얻기 위해 게임 개발자 스택 교환에 더 유용 할 수 있다고 생각한 특정 플랫폼에 대해서는 언급하지 않았습니다. "가장자리 블록"방법과 관련하여이 방법에 대한 알고리즘의 정확한 세부 사항으로 답변을 제출할 수 있습니까? 아니면 그러한 기술에 대한 리소스에 대한 링크가 있습니까?
Craig Innes

1
나는 이것을 위해 Unity 구현을 가지고 있는데, 실제로 자르지 않고 건조하지 않기 때문에 쓰기를하는 데 약간의 시간이 걸릴 것입니다. 나는 현재 직장에서 일하고 있으며 소스 코드는 집에 있습니다. 오늘 밤까지 답변을 기다릴 수 있다면. : 여기 모습입니다 jgallant.com/images/landgen.gif
jgallant

답변:


5

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

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


C # 버전이 있습니까? 문서 주석이 포함 된 버전이 있습니까? 이 알고리즘을 3D에 적용 할 수 있습니까?
Aaron Franke 17 년

3

파괴 가능한 지형을 만들려고한다면 Unity 에서이 작업을 수행 한 방식은 세상의 가장자리 블록에만 충돌자를 설정하는 것입니다. 예를 들어, 이것이 당신이 달성하고자하는 것입니다 :

녹색 블록은 충돌체가 포함 된 타일을 나타냅니다

모든 녹색 블록에는 충돌체가 포함되어 있으며 나머지 블록에는 충돌이 없습니다. 그것은 계산에 많은 비용을 절약합니다. 블록을 파괴하면 인접한 블록의 충돌체를 아주 쉽게 활성화 할 수 있습니다. 충돌체 활성화 / 비활성화는 비용이 많이 들고 드물게 수행해야합니다.

따라서 Tile 리소스는 다음과 같습니다.

Unity의 타일 리소스

표준 게임 오브젝트이지만 풀링 가능합니다. 또한 상자 충돌체는 기본적으로 비활성화되어 있습니다. 가장자리 타일 인 경우에만 활성화됩니다.

월드를 정적으로로드하는 경우 타일을 풀링 할 필요가 없습니다. 한 번에 모두로드하고 가장자리에서 거리를 계산하고 필요한 경우 충돌체를 적용 할 수 있습니다.

동적으로로드하는 경우 타일 풀을 사용하는 것이 가장 좋습니다. 여기 내 새로 고침 루프의 편집 된 예가 있습니다. 현재 카메라 뷰를 기반으로 타일을로드합니다.

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.