Trong XNA, làm cách nào để tôi tự động tải các phần của bản đồ thế giới 2D lớn?


17

Tôi muốn phát triển một bản đồ thế giới rộng lớn; kích thước tối thiểu 8000 × 6000 pixel. Tôi đã chia nó thành một lưới 10 × 10 hình ảnh PNG 800 × 600 pixel. Để tránh tải mọi thứ vào bộ nhớ, hình ảnh nên được tải và tải tùy thuộc vào vị trí của người chơi trong lưới.

Ví dụ, đây là người chơi ở vị trí (3,3):

Người chơi tại (3,3)

Khi anh di chuyển sang phải (4,3), ba hình ảnh ở bên trái được sắp xếp lại trong khi ba hình ảnh bên phải được phân bổ:

Người chơi chuyển đến (4,3)

Có lẽ nên có một ngưỡng bên trong mỗi ô của lưới kích hoạt tải và dỡ tải. Việc tải có lẽ nên xảy ra trong một luồng riêng biệt.

Làm thế nào để tôi thiết kế một hệ thống như vậy?


1
Gợi ý nhanh: Bạn đã có hai câu hỏi ở đây, "làm cách nào để tôi tự động tải các ô trong trò chơi" và "làm cách nào để tạo luồng trên XNA cho XBox 360". Xóa các phân đoạn dành riêng cho hệ điều hành khỏi bài đăng này - mã tải xuống sẽ giống hệt trên XB360, Linux và Nintendo Wii - và tạo một bài đăng mới để xâu chuỗi trên XBox360.
ZorbaTHut

Đối với một câu hỏi về vấn đề thực tế của bạn, đây là một bản đồ cuộn trơn tru, hoặc một loạt các màn hình không cuộn riêng lẻ?
ZorbaTHut

Nếu tôi hiểu "bản đồ cuộn trơn tru", vâng, đúng vậy. Đối với việc tách câu hỏi, tôi đã nghĩ về nó nhưng hơi ngần ngại khi đặt câu hỏi. Nhưng, tôi sẽ làm ngay bây giờ.
pek

Câu trả lời:


10

Có một vài vấn đề cần giải quyết ở đây. Đầu tiên là làm thế nào để tải và dỡ gạch. Trình quản lý nội dung theo mặc định sẽ không cho phép bạn tải các phần nội dung cụ thể. Tuy nhiên, việc thực hiện tùy chỉnh này sẽ:

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

Vấn đề thứ hai là làm thế nào để quyết định gạch nào sẽ tải và dỡ. Sau đây sẽ giải quyết vấn đề này:

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

Vấn đề cuối cùng là làm thế nào để quyết định khi nào tải và dỡ hàng. Đây có lẽ là phần dễ nhất. Điều này chỉ có thể được thực hiện trong phương thức Update () sau khi vị trí màn hình của người chơi được xác định:

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

Tất nhiên, bạn cũng sẽ cần phải vẽ các ô, nhưng được cung cấp cho gạchWidth, brickHeight và mảng Gạch [], điều này sẽ không quan trọng.


Cảm ơn bạn. Rất độc đáo nói. Tôi muốn đưa ra một gợi ý, nếu bạn biết: bạn có thể mô tả cách tải gạch sẽ được thực hiện trong một luồng không. Tôi tưởng tượng rằng việc tải 3 gạch 800x600 cùng một lúc sẽ tạm dừng trò chơi.
pek

Card đồ họa cần phải được tham gia khi tạo một đối tượng Texture2D, theo như tôi biết, nó vẫn sẽ gây ra sự bỏ qua ngay cả khi việc tải được thực hiện trên một luồng thứ hai.
Sean James

0

Những gì bạn muốn làm là khá phổ biến. Để có một hướng dẫn tốt về điều này và các kỹ thuật phổ biến khác, hãy xem loạt động cơ gạch này .

Nếu bạn chưa từng làm bất cứ điều gì như thế này trước đây, tôi khuyên bạn nên xem loạt phim này. Tuy nhiên, nếu bạn muốn bạn có thể lấy mã hướng dẫn cuối cùng. Nếu bạn làm sau, hãy kiểm tra phương pháp vẽ.

Tóm lại, bạn cần tìm điểm X / Y tối thiểu và tối đa của mình xung quanh người chơi. Khi bạn đã có nó, bạn chỉ cần lặp qua từng cái và vẽ hình xếp đó.

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

Như bạn có thể thấy có rất nhiều điều đang diễn ra. Bạn cần một máy ảnh sẽ tạo ma trận của bạn, bạn cần chuyển đổi vị trí pixel hiện tại của mình thành chỉ mục ô vuông, bạn tìm thấy điểm tối thiểu / tối đa của mình (Tôi thêm một chút vào MAX của mình để nó vượt ra ngoài màn hình hiển thị một chút ), và sau đó bạn có thể vẽ nó.

Tôi thực sự đề nghị xem loạt bài hướng dẫn. Anh ấy đề cập đến vấn đề hiện tại của bạn, cách tạo trình chỉnh sửa ô, giữ trình phát trong giới hạn, hoạt hình sprite, tương tác AI, v.v ...

Bên cạnh đó, TiledLib đã tích hợp sẵn. Bạn cũng có thể nghiên cứu mã của họ.


Mặc dù điều này rất hữu ích, nhưng nó chỉ bao gồm kết xuất bản đồ ô vuông. Những gì về tải và xếp gạch có liên quan? Tôi không thể tải 100 gạch 800x600 trong bộ nhớ. Tôi sẽ xem các video hướng dẫn; cảm ơn.
pek

ahhh .. tôi hiểu nhầm câu hỏi của bạn. Vì vậy, bạn đang sử dụng một tilemap truyền thống, hoặc bạn có một hình ảnh lớn cho cấp độ của bạn mà bạn đã chia thành từng mảnh?

Chỉ cần một suy nghĩ .... bạn có thể lưu trữ tên của gạch trong một mảng và sử dụng cùng một kỹ thuật để biết những gì cần tải và vẽ. Hãy ghi nhớ để cố gắng sử dụng lại bất kỳ đối tượng nào hoặc bạn có thể tạo ra rất nhiều rác

Thứ hai: bản đồ là 8000x6000 được chia thành các ô 10x10 800x600 px. Đối với việc sử dụng lại, tôi đã có một người quản lý nội dung chịu trách nhiệm về điều đó.
pek

0

Tôi đã làm việc trên một cái gì đó rất giống với dự án hiện tại của tôi. Đây là một cách nhanh chóng về cách tôi đang thực hiện nó, với một số lưu ý phụ về cách làm cho mọi thứ dễ dàng hơn với chính bạn.

Đối với tôi, vấn đề đầu tiên là phá vỡ thế giới thành những phần nhỏ hơn phù hợp để tải và dỡ hàng khi đang bay. Vì bạn đang sử dụng bản đồ dựa trên ô xếp, bước đó trở nên dễ dàng hơn đáng kể. Thay vì xem xét vị trí của từng đối tượng 3D trong cấp độ, bạn đã có cấp độ của mình được phân chia độc đáo thành các ô. Điều này cho phép bạn chỉ cần chia thế giới thành các khối gạch X bằng Y và tải chúng.

Bạn sẽ muốn làm điều đó tự động, chứ không phải bằng tay. Vì bạn đang sử dụng XNA, bạn có tùy chọn sử dụng Đường ống nội dung với nhà xuất khẩu tùy chỉnh cho nội dung cấp độ của bạn. Trừ khi bạn biết một số cách để chạy quy trình xuất mà không cần biên dịch lại, tôi thành thật khuyên bạn nên chống lại nó. Mặc dù C # không thể biên dịch chậm như C ++, nhưng bạn vẫn không muốn tải Visual Studio và biên dịch lại trò chơi của mình mỗi khi bạn thực hiện một thay đổi nhỏ trên bản đồ.

Một điều quan trọng khác ở đây là đảm bảo bạn sử dụng quy ước đặt tên tốt cho các tệp chứa các khối của cấp độ của bạn. Bạn muốn có thể biết rằng bạn muốn tải hoặc giải nén chunk C, sau đó tạo tên tệp bạn cần tải để thực hiện điều đó trong thời gian chạy. Cuối cùng, nghĩ về bất kỳ điều nhỏ nhặt nào có thể giúp bạn xuống đường. Thật tuyệt khi có thể thay đổi một khối lớn như thế nào, tái xuất, và sau đó thấy tác động của điều đó lên hiệu suất ngay lập tức.

Trong thời gian chạy, nó vẫn khá đơn giản. Bạn sẽ cần một số cách để tải và dỡ tải không đồng bộ các khối, nhưng điều này phụ thuộc nhiều vào cách trò chơi hoặc công cụ của bạn hoạt động. Hình ảnh thứ hai của bạn là chính xác - bạn cần xác định những khối nào sẽ được tải hoặc không tải và thực hiện các yêu cầu thích hợp để thực hiện trường hợp đó nếu chưa có. Tùy thuộc vào số lượng khối bạn đã tải cùng một lúc, bạn có thể thực hiện việc này bất cứ khi nào người chơi vượt qua một ranh giới từ một đoạn này sang đoạn kế tiếp. Dù sao đi nữa, bạn muốn chắc chắn rằng đã tải đủ, ngay cả trong thời gian tải (hợp lý) tồi tệ nhất, đoạn vẫn được tải trước khi người chơi có thể nhìn thấy nó. Có lẽ bạn sẽ muốn chơi với con số này rất nhiều cho đến khi bạn có được sự cân bằng tốt giữa hiệu suất và mức tiêu thụ bộ nhớ.

Theo như kiến ​​trúc thực tế, bạn sẽ muốn trừu tượng hóa quá trình thực sự tải và dỡ dữ liệu khỏi bộ nhớ từ quá trình xác định những gì nên được tải / không tải. Đối với lần lặp đầu tiên của bạn, tôi thậm chí sẽ không lo lắng về hiệu suất tải / dỡ tải và chỉ nhận được điều đơn giản nhất có thể hoạt động và đảm bảo rằng bạn đang tạo các yêu cầu phù hợp vào những thời điểm thích hợp. Sau đó, bạn có thể xem xét tối ưu hóa cách bạn tải để giảm thiểu rác.

Tôi đã gặp rất nhiều phức tạp do công cụ tôi đang sử dụng, nhưng đó là cách triển khai khá cụ thể. Nếu bạn có bất kỳ câu hỏi nào về những gì tôi đã làm, xin vui lòng bình luận và tôi sẽ làm những gì tôi có thể giúp đỡ.


1
Bạn có thể sử dụng msbuild để chạy đường ống nội dung bên ngoài Visual Studio. Mẫu WinForms thứ hai có bạn bảo hiểm: creators.xna.com/en-US/sample/winforms_series2
Andrew Russell

@Andrew !!!!! Cảm ơn bạn rất nhiều!!! Tôi đã hoàn toàn từ bỏ các đường ống nội dung vì lý do chính xác đó!
pek
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.