Birçok Küçük Çarpıştırıcıyı Daha Büyük Olanlara Birleştirmek


13

Binlerce ızgara kareden oluşan bir kiremit haritası kullanarak bir oyun yaratıyorum. Şu anda, her karenin çarpışmaları kontrol etmek için üzerinde bir kare çarpıştırıcısı var.

resim açıklamasını buraya girin

Ancak, binlerce küçük blok ile hepsini çarpışmalara karşı kontrol etmek verimsizdir. Tilemap'ın önceden böyle görüneceğini bilseydim, binlerce minik yerine 3 veya 4 büyük çarpıştırıcı kullanabilirdim:

resim açıklamasını buraya girin

Birçok küçük bitişik döşemeyi maksimum büyük olanlarla birleştirmek için bir çeşit standart algoritma var mı? Eğer öyleyse, birisi onu burada tarif edebilir ya da bu tür algoritmalar hakkında literatüre işaret edebilir mi?

Alternatif olarak, karo çarpıştırıcıların bu şekilde ön işlemden geçirilmesi tamamen yanlış bir yaklaşımdır. Eğer öyleyse, çok fazla sayıda çarpıştırıcının verimliliğiyle başa çıkmak için doğru olan nedir?


Arazinin yıkılabilir olmasını planlıyor musunuz?
jgallant

@Jon. Bunu düşünmemiştim. Yıkılabilirliğe izin vermenin sorunu önemli ölçüde zorlaştıracağını hayal ediyorum (çünkü küçük çarpıştırıcılardan biri yok edilebilir, yani büyük kombine çarpıştırıcıların yeniden hesaplanması gerekir, değil mi?)
Craig Innes

Evet. Bu yüzden soruyordum. Tipik olarak, tüm arazinizi bir kafes halinde birleştirirsiniz. Arazinizin yıkılabilir olmasına izin vermeyi planlıyorsanız, yalnızca dış bloklara çarpıştırıcılar ayarlayan alternatif bir yöntem vardır. Hangi blokların "kenar blokları" olduğunu önceden hesaplar ve daha sonra bu blokları taşınabilir bir çarpıştırıcı ile atarsınız. ( jgallant.com/images/uranus/chunk.png - Görüntü eski ve mükemmel değil, ancak tekniği gösteriyor) Bir oyun motoru / platformu için ne kullanıyorsunuz?
jgallant

@Jon Kiremit çarpışmaları için BoxCollider2D bileşenleri ile Unity'yi oyun motorum olarak kullanıyorum. Bu soruna daha genel bir cevap almak için oyun geliştirici yığın alışverişinde daha yararlı olabileceğini düşündüğüm için özel platformumdan bahsetmedim. "Kenar blokları" yönteminizle ilgili olarak, bu yöntem için algoritmanın kesin ayrıntılarını içeren bir yanıt gönderebilir misiniz? Yoksa bu tekniklerle ilgili kaynaklara bir bağınız var mı?
Craig Innes

1
Bunun için bir Birlik uygulamam var, gerçekten kesilmiş ve kuru olmadığından yazmak için biraz zaman alacak. Şu anda işteyim ve kaynak kodu evde. Cevap için bu gece kadar bekleyebilirsen. İşte neye benzediği: jgallant.com/images/landgen.gif
jgallant

Yanıtlar:


5

Love2d motoru için bu algoritmayı yararlı buldum ( lua dili )

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

İşte mevcut projemdeki love2d örneğini takip edin. Kırmızı renkte duvar çarpıştırıcılarımı görebilirsiniz.

resim açıklamasını buraya girin


Bir C # sürümü var mı? Doküman yorumlarının bulunduğu bir sürüm var mı? Bu algoritma 3D için uyarlanabilir mi?
Aaron Franke

3

Yıkılabilir bir arazi yaratmak istiyorsanız, bunu Unity'de yaptığım gibi, sadece dünyanızın kenar bloklarına çarpıştırıcılar koymaktır. Yani, örneğin, bunu başarmak istersiniz:

Yeşil bloklar, çarpıştırıcı içeren karoları gösterir

Tüm bu yeşil bloklar bir çarpıştırıcı içeriyor ve geri kalanı yok. Bu hesaplamalara bir ton kazandırır. Bir bloğu yok ederseniz, bitişik bloklardaki çarpıştırıcıları kolayca etkinleştirebilirsiniz. Bir çarpıştırıcıyı etkinleştirmenin / devre dışı bırakmanın maliyetli olduğunu ve çok az yapılması gerektiğini unutmayın.

Böylece, Döşeme kaynağı şöyle görünür:

Birlikteki Döşeme Kaynağı

Standart bir gameobject, ama aynı zamanda poolable. Ayrıca, kutu toplayıcının varsayılan olarak devre dışı olarak ayarlandığını unutmayın. Yalnızca bir kenar döşemesi olduğunda etkinleştiririz.

Dünyanızı statik olarak yüklüyorsanız, karolarınızı havuzlamanıza gerek yoktur. Hepsini tek seferde yükleyebilir, kenardan mesafelerini hesaplayabilir ve gerekirse bir çarpıştırıcı uygulayabilirsiniz.

Dinamik olarak yüklüyorsanız, bir döşeme havuzu kullanmak en iyisidir. İşte benim yenileme döngümün düzenlenmiş bir örneği. Geçerli kamera görünümüne göre döşemeleri yükler:

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

İdeal olarak, sahne arkasında biraz daha fazla şey olduğu için çok daha ayrıntılı bir yazı yazardım. Ancak, bu size yardımcı olabilir. Sorularınız varsa benimle iletişime geçmekten çekinmeyin.


Kabul edilen dnkdrone'nin cevabı, asıl sorulan soruya daha doğrudan cevap verdiğinden. Ancak, verimli bir alternatif için değerli bir yön verdiği için bu cevabı iptal ettiler
Craig Innes

@CraigInnes Sorun yok adam. Tıpkı yardım etmeyi seviyorum. Puan önemli değil :)
jgallant
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.