Kết hợp nhiều máy va chạm nhỏ thành những người lớn hơn


13

Tôi đang tạo ra một trò chơi bằng cách sử dụng một bản đồ ô vuông được tạo thành từ hàng ngàn ô vuông. Hiện tại, mỗi hình vuông có một máy va chạm vuông trên đó để kiểm tra va chạm.

nhập mô tả hình ảnh ở đây

Tuy nhiên, với hàng ngàn khối nhỏ, việc kiểm tra tất cả các va chạm là không hiệu quả. Nếu tôi biết rằng tilemap sẽ trông như thế này trước, tôi có thể đã sử dụng 3 hoặc 4 máy va chạm lớn thay vì hàng ngàn cái nhỏ:

nhập mô tả hình ảnh ở đây

Có một số loại thuật toán tiêu chuẩn để kết hợp nhiều gạch liền kề nhỏ thành các khối lớn nhất? Nếu vậy, ai đó có thể mô tả nó ở đây, hoặc chỉ vào tài liệu về các thuật toán như vậy?

Ngoài ra, có thể xử lý trước các trình tạo gạch theo cách này là hoàn toàn sai. Nếu vậy, đâu là chính xác để đối phó với hiệu quả của một số lượng lớn máy va chạm?


Bạn có kế hoạch để có địa hình phá hủy?
jgallant

@Jon. Tôi đã không xem xét điều này. Tôi tưởng tượng việc cho phép phá hủy sẽ khiến vấn đề trở nên khó khăn hơn đáng kể (vì một trong những máy va chạm nhỏ có thể bị phá hủy, có nghĩa là các máy va chạm lớn kết hợp sẽ cần phải được tính lại, phải không?)
Craig Innes

Đúng. Đây là lý do tại sao tôi đã hỏi. Thông thường, bạn sẽ kết hợp tất cả các địa hình của bạn thành một lưới. Nếu bạn có kế hoạch cho phép địa hình của bạn có thể bị phá hủy, có một phương pháp thay thế bạn có thể sử dụng, đó là đặt máy va chạm vào chỉ các khối bên ngoài. Bạn sẽ xác định trước các khối là "khối cạnh" và sau đó gán các khối đó bằng máy va chạm có thể gộp được. ( jgallant.com/images/uranus/chunk.png - Hình ảnh cũ và không hoàn hảo, nhưng thể hiện kỹ thuật) Bạn đang sử dụng gì cho một công cụ / nền tảng trò chơi?
jgallant

@Jon Tôi đang sử dụng Unity làm công cụ trò chơi của mình, với các thành phần BoxCollider2D cho các va chạm gạch. Tôi đã không đề cập đến nền tảng cụ thể của mình vì tôi nghĩ rằng nó có thể được sử dụng nhiều hơn cho trao đổi ngăn xếp trò chơi để có câu trả lời chung hơn cho vấn đề này. Liên quan đến phương pháp "khối cạnh" của bạn, bạn có thể gửi câu trả lời với các chi tiết chính xác của thuật toán cho phương pháp này không? Hay bạn có một liên kết đến các tài nguyên về các kỹ thuật như vậy?
Craig Innes

1
Tôi có một triển khai Unity cho việc này, tôi sẽ mất một chút thời gian để viết lên, vì nó không thực sự bị cắt và khô. Tôi đang làm việc tại thời điểm này, và mã nguồn ở nhà. Nếu bạn có thể đợi đến tối nay để có câu trả lời. Đây là những gì nó trông giống như: jgallant.com/images/landgen.gif
jgallant

Câu trả lời:


5

Tôi tìm thấy hữu ích algoritm này cho công cụ love2d ( ngôn ngữ 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

Ở đây làm theo ví dụ love2d trong dự án hiện tại của tôi. Trong màu đỏ, bạn có thể thấy máy va chạm tường của tôi.

nhập mô tả hình ảnh ở đây


Có phiên bản C # không? Có một phiên bản với ý kiến ​​tài liệu? Thuật toán này có thể được điều chỉnh cho 3D không?
Aaron Franke

3

Nếu bạn đang tìm cách tạo địa hình có thể phá hủy, cách tôi đã làm trong Unity, là chỉ đặt máy va chạm trên các khối cạnh của thế giới của bạn. Vì vậy, ví dụ, đây là những gì bạn muốn thực hiện:

Các khối màu xanh lá cây cho biết các ô có chứa máy va chạm

Tất cả những khối màu xanh lá cây này đều có máy va chạm và phần còn lại thì không. Điều đó tiết kiệm một tấn trên các tính toán. Nếu bạn phá hủy một khối, bạn có thể kích hoạt các máy va chạm trên các khối liền kề khá dễ dàng. Hãy nhớ rằng kích hoạt / hủy kích hoạt máy va chạm là tốn kém và nên được thực hiện một cách tiết kiệm.

Vì vậy, tài nguyên Ngói trông như thế này:

Tài nguyên ngói trong Unity

Nó là một gameobject tiêu chuẩn, nhưng nó cũng có thể sử dụng được. Cũng lưu ý rằng trình tạo hộp được đặt thành bị tắt theo mặc định. Chúng tôi sẽ chỉ kích hoạt nếu nó là một gạch cạnh.

Nếu bạn đang tải tĩnh thế giới của mình, không cần phải gộp các ô của bạn. Bạn chỉ có thể tải tất cả chúng trong một lần bắn, tính khoảng cách của chúng từ cạnh và áp dụng máy va chạm nếu cần.

Nếu bạn đang tải động, tốt nhất là sử dụng một nhóm gạch. Đây là một ví dụ chỉnh sửa của vòng lặp làm mới của tôi. Nó tải gạch dựa trên chế độ xem camera hiện tại:

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

Lý tưởng nhất là tôi sẽ viết một bài chi tiết hơn nhiều, vì có khá nhiều điều đang diễn ra đằng sau hậu trường. Tuy nhiên, điều này có thể giúp bạn ra ngoài. Nếu có câu hỏi hãy hỏi hoặc liên hệ với tôi.


Đã chấp nhận câu trả lời của dnkdrone vì nó trả lời trực tiếp hơn câu hỏi ban đầu được đặt ra. Tuy nhiên, đã đưa ra câu trả lời này vì nó đưa ra hướng có giá trị đối với một giải pháp thay thế hiệu quả
Craig Innes

@CraigInnes Không có vấn đề gì. Chỉ muốn giúp đỡ ra. Điểm không thành vấn đề :)
jgallant
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.