Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I want to develop a huge world map which is at least 8000x6000 pixels in size. I have broken it into a 10x10 grid of 800x600 pixels PNG images. To avoid loading everything into memory, the images should be loaded and unloaded depending on the player's position in the grid.

For example, here is the player position (3,3):

Player at (3,3)

As he moves to the right (4,3), the three images at the far left are deallocated while the three images at the right are allocated:

Player moves to (4,3)

I was thinking that there should be a threshold inside each cell of the grid that triggers the loading and unloading of the images. Also, this loading would probably be in a separate thread.

So, my main question is: How should I design this?

Thank you in advance.

share|improve this question
1  
Quick suggestion: You've got two questions here, "how do I dynamically load tiles in a game" and "how do I do threading on XNA for XBox 360". Remove the OS-specific segments from this post - the tileloading code will be identical on XB360, Linux, and the Nintendo Wii - and make a new post for threading on XBox360. –  ZorbaTHut Jul 17 '10 at 7:46
    
As for a question about your actual problem, is this a smooth-scrolling map, or a series of individual non-scrolling screens? –  ZorbaTHut Jul 17 '10 at 7:47
    
If I understood "smooth-scrolling map", yes, it is. As for separating the question, I thought about it but kinda hesitated to ask to questions consequentially. But, I'll do it now. –  pek Jul 17 '10 at 12:21

3 Answers 3

up vote 7 down vote accepted

There are a few problems to solve here. The first is how to load and unload tiles. The ContentManager by default will not let you unload specific pieces of content. A custom implementation of this, however, will:

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

The second issue is how to decide which tiles to load and unload. The following would solve this problem:

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

The final issue is how to decide when to load and unload. This is probably the easiest part. This could simply be done in the Update() method once the player's screen position was determined:

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

Of course you would also need to draw the tiles, but given tileWidth, tileHeight, and the Tiles[] array, this should be trivial.

share|improve this answer
    
Thank you. Very nicely said. I would like to make a suggestion, if you know: could you describe how would the loading of tiles be done in a thread. I imagine that loading 3 800x600 tiles at once would pause slightly the game. –  pek Jul 18 '10 at 17:15
    
The graphics card needs to be involved when creating a Texture2D object, so as far as I know it would still cause a skip even if loading were done on a second thread. –  Sean James Jul 18 '10 at 18:41

What you want to do is pretty common. For a nice tutorial on this and other common techniques, check out this tile engine series.

If you haven't done anything like this before, I recommend watching the series. However, if you wanted you could get the last tutorials code. If you do the later, check out the draw method.

In a nutshell, you need to find your min and max X/Y points around the player. Once you have that you just loop through each one and draw that tile.

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

As you can see there is a lot going on. You need a camera that is going to create your matrix, you need to convert your current pixel location to a tile index, you find your min/max points ( I add a little to my MAX so it draws beyond the visible screen a little bit), and then you can draw it.

I really suggest watching the tutorial series. He covers your current problem, how to create a tile editor, keeping the player within bounds, sprite animation, AI interaction, etc...

On a side note TiledLib has this built in. You could study their code as well.

share|improve this answer
    
While this is very helpful, it only covers the rendering of the tile map. What about loading and unloading relevant tiles? I cannot load 100 800x600 tiles in memory. I will take a look at the video tutorials; thanks. –  pek Jul 16 '10 at 14:35
    
ahhh.. I misunderstood your question. So are you using a traditional tilemap, or do you have a big image for your level that you broke up into pieces? –  Joe Jul 16 '10 at 14:56
    
Just a thought....you could store the names of your tiles in an array, and use the same technique to know what to load and draw. Keep in mind to try to re-use any objects or you could create a lot of garbage –  Joe Jul 16 '10 at 15:12
    
The second: the map is 8000x6000 broken into 10x10 tiles of 800x600 px. As for reuse, I already have a content manager that is responsible for that. –  pek Jul 17 '10 at 6:55

I've been working on something very similar for my current project. This is a quick run down of how I'm doing it, with some side notes on how to make things a bit easier on yourself.

For me, the first problem was breaking the world into smaller chunks that would be appropriate to load and unload on the fly. Since you're using a tile based map, that step becomes significantly easier. Rather than considering the positions of each 3D object in the level, you already have your level split nicely into tiles. This lets you simply break up the world into X by Y tile chunks, and load those.

You're going to want to do that automatically, rather than by hand. Since you're using XNA, you have an option of using the Content Pipeline with a custom exporter for your level content. Unless you know of some way to run the export process without recompiling, I'd honestly recommend against it. While C# isn't nearly as slow to compile as C++ tends to be, you still don't want to have to load Visual Studio and recompile your game every time you make a minor change to the map.

Another important thing here is to make sure you use a good naming convention for the files containing your level's chunks. You want to be able to know that you want to load or unload chunk C, and then generate the filename you need to load to do that at run time. Finally, think of any little things that might help you down the road. It's really nice to be able to change how large a chunk is, re-export, and then see the effects of that on performance immediately.

At run time, it's still pretty straightforward. You'll need some way to asynchronously load and unload chunks, but this is highly dependent on how your game or engine works. Your second image is exactly correct -- you need to determine what chunks should be loaded or unloaded, and make the appropriate requests to make that the case if it is not already. Depending on how many chunks you have loaded at one time, you could just do this whenever the player cross a boundary from one chunk to the next. After all, either way, you want to make sure that enough is loaded that even in the worst (reasonable) load time, the chunk is still loaded before the player can see it. You're probably going to want to play with this number a lot until you get a good balance between performance and memory consumption.

As far as actual architecture goes, you're going to want to abstract the process of actually loading and unloading the data from memory from the process of determining what should be loaded/unloaded. For your first iteration, I wouldn't even worry about performance of loading/unloading and just get the simplest thing that could possibly work, and ensure that you are generating the appropriate requests at the appropriate times. After that, you can look at optimizing how you load to minimize garbage.

I ran into a lot of additional complexity due to the engine I was using, but that's pretty implementation specific. If you've got any questions about what I did, please comment and I'll do what I can to help out.

share|improve this answer
1  
You can use msbuild to run the content pipeline outside of Visual Studio. The second WinForms sample has you covered: creators.xna.com/en-US/sample/winforms_series2 –  Andrew Russell Jul 18 '10 at 14:42
    
@Andrew !!!!! Thank you SO much!!! I was about to completely abandon the content pipeline for exactly that reason! –  pek Jul 18 '10 at 16:42

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.