XNA'da, büyük bir 2D dünya haritasının parçalarını dinamik olarak nasıl yüklerim?


17

Çok büyük bir dünya haritası geliştirmek istiyorum; en az 8000 × 6000 piksel boyutunda. 800 × 600 piksel PNG görüntülerinden oluşan 10 × 10 ızgaraya böldüm. Her şeyi belleğe yüklemekten kaçınmak için, görüntüler oynatıcının ızgaradaki konumuna bağlı olarak yüklenmeli ve boşaltılmalıdır.

Örneğin, şu pozisyondaki oyuncu (3,3):

Oyuncu (3,3)

Sağa doğru hareket ederken (4,3), en soldaki üç görüntü yerleştirilirken, sağdaki üç görüntü tahsis edilir:

Oyuncu şu hareket eder: (4,3)

Muhtemelen ızgaranın her hücresinin içinde yükleme ve boşaltma işlemini tetikleyen bir eşik olmalıdır. Yükleme muhtemelen ayrı bir iş parçacığında gerçekleşmelidir.

Böyle bir sistemi nasıl tasarlarım?


1
Hızlı öneri: Burada iki sorum var, "bir oyunda karoları dinamik olarak nasıl yükleyebilirim" ve "XBox 360 için XNA'da iş parçacığı oluşturmayı nasıl yaparım". OS'ye özgü segmentleri bu yazıdan kaldırın - döşeme yükleme kodu XB360, Linux ve Nintendo Wii'de aynı olacak - ve XBox360'ta işlem yapmak için yeni bir yazı oluşturun.
ZorbaTHut

Asıl probleminizle ilgili bir soruya gelince, bu düzgün kaydırmalı bir harita mı yoksa bir dizi ayrı kaydırılmamış ekran mı?
ZorbaTHut

"Düzgün kaydırmalı harita" anladıysam, evet. Soruyu ayırmaya gelince, düşündüm ama sonuç olarak soru sormaktan çekiniyordum. Ama şimdi yapacağım.
pek

Yanıtlar:


10

Burada çözülmesi gereken birkaç sorun var. Birincisi, karoların nasıl yükleneceği ve boşaltılacağıdır. ContentManager varsayılan olarak belirli içerik parçalarını kaldırmanıza izin vermez. Bununla birlikte, bunun özel bir uygulaması:

public class ExclusiveContentManager : ContentManager
{
    public ExclusiveContentManager(IServiceProvider serviceProvider,
        string RootDirectory) : base(serviceProvider, RootDirectory)
    {
    }

    public T LoadContentExclusive<T>(string AssetName)
    {
        return ReadAsset<T>(AssetName, null);
    }

    public void Unload(IDisposable ContentItem)
    {
        ContentItem.Dispose();
    }
}

İkinci konu, hangi döşemelerin yüklenip boşaltılacağına nasıl karar verileceğidir. Aşağıdakiler bu sorunu çözecektir:

public class Tile
{
    public int X;
    public int Y;
    public Texture2D Texture;
}

int playerTileX;
int playerTileY;
int width;
int height;

ExclusiveContentManager contentEx;
Tile[,] tiles;

void updateLoadedTiles()
{
    List<Tile> loaded = new List<Tile>();

    // Determine which tiles need to be loaded
    for (int x = playerTileX - 1; x <= playerTileX + 1; x++)
        for (int y = playerTileY - 1; y <= playerTileY; y++)
        {
            if (x < 0 || x > width - 1) continue;
            if (y < 0 || y > height - 1) continue;

            loaded.Add(tiles[x, y]);
        }

    // Load and unload as necessary
    for (int x = 0; x < width; x++)
        for (int y = 0; y < height; y++)
        {
            if (loaded.Contains(tiles[x, y]))
            {
                if (tiles[x, y].Texture == null)
                    loadTile(x, y);
            }
            else
            {
                if (tiles[x, y].Texture != null)
                    contentEx.Unload(tiles[x, y].Texture);
            }
        }
}

void loadTile(int x, int y)
{
    Texture2D tex = contentEx.LoadEx<Texture2D>("tile_" + x + "_" + y);
    tiles[x, y].Texture = tex;
}

Son konu, ne zaman yükleneceğine ve boşaltılacağına nasıl karar verileceğidir. Bu muhtemelen en kolay kısımdır. Bu, oynatıcının ekran konumu belirlendikten sonra Update () yönteminde yapılabilir:

int playerScreenX;
int playerScreenY;
int tileWidth;
int tileHeight;

protected override void Update(GameTime gameTime)
{
    // Code to update player position, etc...

    // Load/unload
    int newPlayerTileY = (int)((float)playerScreenX / (float)tileWidth);
    int newPlayerTileX = (int)((float)playerScreenY / (float)tileHeight);

    if (newPlayerTileX != playerTileX || newPlayerTileY != playerTileY)
        updateLoadedTiles();

    base.Update(gameTime);
}

Tabii ki karoları da çizmeniz gerekir, ancak tileWidth, tileHeight ve Tiles [] dizisi verildiğinde, bu önemsiz olmalıdır.


Teşekkür ederim. Çok güzel dedi. Ben bir öneri yapmak istiyorum, eğer bilirseniz: fayans yükleme bir iş parçacığında nasıl olacağını açıklamak. Bir kerede 3 800x600 karo yüklemenin oyunu biraz duraklatacağını hayal ediyorum.
pek

Grafik kartı bir Texture2D nesnesi oluştururken dahil edilmelidir, bu yüzden bildiğim kadarıyla, ikinci bir iş parçacığına yükleme yapılsa bile yine de atlamaya neden olur.
Sean James

0

Yapmak istediğiniz şey oldukça yaygın. Bu ve diğer yaygın teknikler hakkında güzel bir eğitim için bu karo motoru serisine göz atın .

Daha önce böyle bir şey yapmadıysanız diziyi izlemenizi tavsiye ederim. Ancak, isterseniz son öğretici kodunu alabilirsiniz. Daha sonra yaparsanız, çizim yöntemine bakın.

Özetle, oyuncunun çevresinde minimum ve maksimum X / Y puanlarınızı bulmanız gerekir. Bir kez sen her biri döngü ve o kiremit çizmek.

public override void Draw(GameTime gameTime)
{
    Point min = currentMap.WorldPointToTileIndex(camera.Position);
    Point max = currentMap.WorldPointToTileIndex( camera.Position +
        new Vector2(
            ScreenManager.Viewport.Width + currentMap.TileWidth,
            ScreenManager.Viewport.Height + currentMap.TileHeight));


    min.X = (int)Math.Max(min.X, 0);
    min.Y = (int)Math.Max(min.Y, 0);
    max.X = (int)Math.Min(max.X, currentMap.Width);
    max.Y = (int)Math.Min(max.Y, currentMap.Height + 100);

    ScreenManager.SpriteBatch.Begin(SpriteBlendMode.AlphaBlend
                                    , SpriteSortMode.Immediate
                                    , SaveStateMode.None
                                    , camera.TransformMatrix);
    for (int x = min.X; x < max.X; x++)
    {
        for (int y = min.Y; y < max.Y; y++)
        {

            Tile tile = Tiles[x, y];
            if (tile == null)
                continue;

            Rectangle currentPos = new Rectangle(x * currentMap.TileWidth, y * currentMap.TileHeight, currentMap.TileWidth, currentMap.TileHeight);
            ScreenManager.SpriteBatch.Draw(tile.Texture, currentPos, tile.Source, Color.White);
        }
    }

    ScreenManager.SpriteBatch.End();
    base.Draw(gameTime);
}

public Point WorldPointToTileIndex(Vector2 worldPoint)
{
    worldPoint.X = MathHelper.Clamp(worldPoint.X, 0, Width * TileWidth);
    worldPoint.Y = MathHelper.Clamp(worldPoint.Y, 0, Height * TileHeight);


    Point p = new Point();

    // simple conversion to tile indices
    p.X = (int)Math.Floor(worldPoint.X / TileWidth);
    p.Y = (int)Math.Floor(worldPoint.Y / TileWidth);

    return p;
}

Gördüğünüz gibi çok şey oluyor. Matrisinizi oluşturacak bir kameraya ihtiyacınız var, mevcut piksel konumunuzu bir karo dizinine dönüştürmeniz gerekiyor, min / maks puanlarınızı buluyorsunuz (MAX'a biraz ekliyorum, böylece görünür ekranın biraz ötesine geçiyor. ), ve sonra çizebilirsiniz.

Eğitim dizisini izlemenizi öneririm. Şu anki probleminizi, bir karo editörünün nasıl oluşturulacağını, oynatıcıyı sınırların içinde tuttuğunu, sprite animasyonunu, AI etkileşimini vb.

Bir yan not TiledLib yerleşik vardır. Onların kodlarını da inceleyebilirsiniz.


Bu çok yararlı olsa da, yalnızca karo haritasının oluşturulmasını kapsar. İlgili fayansların yüklenmesi ve boşaltılması ne olacak? 100 800x600 kutucuğu belleğe yükleyemiyorum. Video eğiticilerine bir göz atacağım; Teşekkürler.
pek

ahhh .. Sorunu yanlış anladım. Geleneksel bir tilemap mı kullanıyorsunuz yoksa seviyeniz için parçalara ayırdığınız büyük bir görüntünüz var mı?

Sadece bir düşünce .... fayanslarınızın isimlerini bir dizide saklayabilir ve ne yükleyeceğinizi ve çizeceğinizi bilmek için aynı tekniği kullanabilirsiniz. Herhangi bir nesneyi yeniden kullanmayı denemeyi unutmayın veya çok fazla çöp oluşturabilirsiniz

İkincisi: harita 8000x6000, 800x600 piksellik 10x10 karolara bölünmüştür. Yeniden kullanıma gelince, zaten bundan sorumlu bir içerik yöneticim var.
pek

0

Mevcut projem için çok benzer bir şey üzerinde çalışıyorum. Bu, nasıl yaptığımı hızlı bir şekilde bitiriyor, bazı şeyleri kendiniz üzerinde nasıl daha kolay hale getireceğinize dair bazı yan notlar.

Benim için ilk sorun, dünyayı anında yüklemeye ve boşaltmaya uygun daha küçük parçalara ayırmaktı. Döşeme tabanlı bir harita kullandığınız için, bu adım önemli ölçüde daha kolay hale gelir. Seviyedeki her 3B nesnenin pozisyonlarını göz önünde bulundurmak yerine, seviyeniz karolara güzelce ayrılmıştır. Bu, dünyayı Y karo parçalarıyla X'e ayırmanızı ve bunları yüklemenizi sağlar.

Bunu elle yapmak yerine otomatik olarak yapmak isteyeceksiniz. XNA kullandığınız için, içerik kanalınızı düzey içeriğiniz için özel bir dışa aktarıcıyla kullanma seçeneğiniz vardır. İhracat işlemini yeniden derlemeden çalıştırmanın bir yolunu bilmiyorsanız, buna karşı dürüstçe tavsiye ederim. C #, C ++ 'ın derlemesi kadar yavaş olmasa da, haritada her küçük değişiklik yaptığınızda Visual Studio'yu yüklemek ve oyununuzu yeniden derlemek zorunda kalmazsınız.

Buradaki bir başka önemli şey, seviyenizin parçalarını içeren dosyalar için iyi bir adlandırma kuralı kullandığınızdan emin olmaktır. Öbek C'yi yüklemek veya kaldırmak istediğinizi bilmek ve ardından çalışma zamanında bunu yapmak için yüklemeniz gereken dosya adını oluşturmak istiyorsunuz. Son olarak, yolda size yardımcı olabilecek küçük şeyleri düşünün. Bir parçanın ne kadar büyük olduğunu değiştirmek, yeniden dışa aktarmak ve daha sonra bunun performans üzerindeki etkilerini hemen görmek gerçekten güzel.

Çalışma zamanında, hala oldukça basit. Parçaları eşzamansız olarak yüklemek ve boşaltmak için bir yol bulmanız gerekir, ancak bu oyununuzun veya motorunuzun çalışma şekline bağlıdır. İkinci resminiz tam olarak doğrudur - hangi parçaların yükleneceğini veya boşaltılacağını belirlemeniz ve zaten değilse durumu yapmak için uygun istekleri yapmanız gerekir. Bir seferde kaç tane parça yüklediğinize bağlı olarak, bunu bir oyuncu bir parçadan diğerine sınır aştığında yapabilirsiniz. Sonuçta, her iki durumda da, en kötü (makul) yükleme süresinde bile, oynatıcıyı görmeden önce yığının yüklendiğinden emin olmak istediğinizden emin olmak istersiniz. Performans ve bellek tüketimi arasında iyi bir denge elde edene kadar muhtemelen bu sayı ile oynamak isteyeceksiniz.

Gerçek mimari gittikçe, verileri gerçekte yükleme ve boşaltma işlemini, neyin yüklenmesi / boşaltılması gerektiğini belirleme sürecinden soyutlamak isteyeceksiniz. İlk yinelemeniz için, yükleme / boşaltma performansı hakkında endişelenmiyorum ve sadece çalışabilecek en basit şeyi alıyorum ve uygun zamanlarda uygun istekleri oluşturduğunuzdan emin olabilirim. Bundan sonra, çöpü en aza indirmek için nasıl yüklediğinizi optimize etmeye bakabilirsiniz.

Kullandığım motor nedeniyle çok fazla karmaşıklık yaşadım, ancak bu uygulamaya özel. Yaptığım şey hakkında herhangi bir sorunuz varsa, lütfen yorum yapın, yardımcı olmak için elimden geleni yapacağım.


1
Visual Studio dışında içerik kanalını çalıştırmak için msbuild kullanabilirsiniz. İkinci WinForms örneği sizi ele aldı: creators.xna.com/tr-TR/sample/winforms_series2
Andrew Russell

@Andrew !!!!! Çok teşekkür ederim!!! Tam da bu nedenle içerik kanalını tamamen terk etmek üzereydim!
pek
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.