รวม Colliders ขนาดเล็กจำนวนมากเข้ากับคนที่ใหญ่


13

ฉันกำลังสร้างเกมโดยใช้แผนที่ย่อยที่ทำจากสี่เหลี่ยมหลายพันตาราง ในขณะนี้แต่ละสแควร์จะมี collider สแควร์สำหรับตรวจสอบการชนกัน

ป้อนคำอธิบายรูปภาพที่นี่

อย่างไรก็ตามด้วยบล็อกขนาดเล็กหลายพันบล็อกการตรวจสอบพวกเขาทั้งหมดสำหรับการชนนั้นไม่มีประสิทธิภาพ หากฉันรู้ว่า tilemap จะมีลักษณะเช่นนี้ล่วงหน้าฉันสามารถใช้ colliders ขนาดใหญ่ 3 หรือ 4 ตัวแทนตัวเล็ก ๆ หลายพันตัว:

ป้อนคำอธิบายรูปภาพที่นี่

มีอัลกอริธึมมาตรฐานบางประเภทสำหรับการรวมไทล์เล็ก ๆ ที่อยู่ติดกันให้ใหญ่ที่สุด? ถ้ามีใครช่วยอธิบายได้ที่นี่หรือชี้ไปที่วรรณกรรมเกี่ยวกับอัลกอริธึมดังกล่าว

อีกทางหนึ่งบางทีการประมวลผลล่วงหน้าตัวสร้างไทล์ด้วยวิธีนี้อาจเป็นวิธีที่ผิด ถ้าเป็นเช่นนั้นสิ่งที่ถูกต้องที่จะจัดการกับประสิทธิภาพของ colliders จำนวนมากคืออะไร?


คุณวางแผนที่จะทำลายภูมิประเทศหรือไม่?
jgallant

@ จอน ฉันไม่ได้พิจารณาเรื่องนี้ ฉันคิดว่าการอนุญาตให้ทำลายได้จะทำให้ปัญหายากขึ้นอย่างมีนัยสำคัญ (เพราะหนึ่งในคอลเล็กชั่นอาจถูกทำลายซึ่งหมายความว่าคอลลิเปอร์ขนาดใหญ่จะต้องคำนวณใหม่อีกครั้งใช่มั้ย)
Craig Innes

ใช่. นี่คือเหตุผลที่ฉันถูกถาม โดยปกติแล้วคุณจะรวมภูมิประเทศทั้งหมดของคุณเป็นตาข่าย หากคุณวางแผนที่จะอนุญาตให้ภูมิประเทศของคุณทำลายล้างได้มีวิธีอื่นที่คุณสามารถใช้ได้ซึ่งกำหนด colliders บนบล็อกด้านนอกเท่านั้น คุณต้องคำนวณล่วงหน้าว่าบล็อกใดเป็น "edge block" จากนั้นกำหนด block เหล่านั้นด้วย collider ที่รวมกลุ่มได้ ( jgallant.com/images/uranus/chunk.png - รูปภาพเก่าและไม่สมบูรณ์แบบ แต่แสดงให้เห็นถึงเทคนิค) คุณใช้อะไรกับเกมเอ็นจิ้น / แพลตฟอร์ม?
jgallant

@ จอนฉันใช้ Unity เป็นเอ็นจิ้นเกมของฉันพร้อมกับส่วนประกอบ BoxCollider2D สำหรับการชนกันของไพ่ ฉันไม่ได้พูดถึงแพลตฟอร์มเฉพาะของฉันเพราะฉันคิดว่ามันอาจจะมีประโยชน์มากขึ้นสำหรับการแลกเปลี่ยนเกม dev stack เพื่อรับคำตอบทั่วไปเกี่ยวกับปัญหานี้ เกี่ยวกับวิธี "บล็อกขอบ" ของคุณคุณสามารถส่งคำตอบพร้อมรายละเอียดที่แม่นยำของอัลกอริทึมสำหรับวิธีนี้ได้หรือไม่? หรือคุณมีลิงค์ไปยังแหล่งข้อมูลเกี่ยวกับเทคนิคดังกล่าวหรือไม่?
Craig Innes

1
ฉันมีการดำเนินการแบบเอกภาพสำหรับเรื่องนี้มันต้องใช้เวลาพอสมควรในการเขียนเพราะมันไม่ได้ตัดและแห้ง ฉันทำงานอยู่ในขณะนี้และซอร์สโค้ดก็อยู่ที่บ้าน หากคุณสามารถรอจนถึงคืนนี้เพื่อหาคำตอบ นี่คือสิ่งที่ดูเหมือน: jgallant.com/images/landgen.gif
jgallant

คำตอบ:


5

ฉันพบว่ามีประโยชน์algoritm นี้สำหรับเครื่องยนต์ 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 ได้หรือไม่?
Aaron Franke

3

หากคุณกำลังมองหาการสร้างภูมิประเทศที่ถูกทำลายวิธีที่ฉันทำใน Unity คือการตั้งค่า colliders บนบล็อคขอบของโลกของคุณ ตัวอย่างเช่นนี่คือสิ่งที่คุณต้องการทำ:

บล็อกสีเขียวบ่งบอกถึงไพ่ที่มี collider

บล็อคสีเขียวทั้งหมดนั้นมี collider และที่เหลือก็ไม่มี ที่ช่วยลดการคำนวณ หากคุณทำลายบล็อกคุณสามารถเปิดใช้งาน colliders บนบล็อกที่อยู่ติดกันได้อย่างง่ายดาย โปรดทราบว่าการเปิดใช้งาน / ปิดใช้งาน Collider นั้นมีค่าใช้จ่ายสูงและควรดำเนินการอย่าง จำกัด

ดังนั้นทรัพยากรไทล์จะมีลักษณะเช่นนี้:

ทรัพยากรกระเบื้องในความสามัคคี

มันเป็นเกมมาตรฐานของเกม แต่มันก็สามารถรวมได้ นอกจากนี้สังเกตว่ากล่อง collider ถูกตั้งค่าเป็นปิดใช้งานโดยค่าเริ่มต้น เราจะเปิดใช้งานก็ต่อเมื่อมันเป็นกระเบื้องขอบ

หากคุณกำลังโหลดโลกของคุณแบบคงที่ไม่จำเป็นต้องรวมกระเบื้องของคุณ คุณสามารถโหลดพวกมันทั้งหมดในช็อตเดียวคำนวณระยะห่างจากขอบและใช้คอลเดอร์ถ้าจำเป็น

หากคุณกำลังโหลดแบบไดนามิกวิธีที่ดีที่สุดคือใช้ไทล์พูล นี่คือตัวอย่างการแก้ไขของลูปการรีเฟรชของฉัน มันโหลดไทล์จากมุมมองกล้องปัจจุบัน:

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.