Search code examples
c#arraysioxnamonogame

C# Monogame - Load level from file


I am building a 2D platform game. But the guy making the videos just showed how to create a map and tile class and loaded the level from an array he made. I heard that's a bad practice and people should load their levels from files... I know how to load files with the stream reader but since that's not my code, I was following the tutorial and I have difficulties in implementing it.

So here's the code I have for the level:

private Map map;

// This is in my LoadContent() method
map.Generate(new int[,]
            {
                {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                {2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
                {2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 2, 2},
                {2, 2, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 0, 2, 2},
                {2, 2, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 2, 2},
                {2, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2},
                {2, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2},
                {2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2},
                {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2},
                {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2},
            }, 64);

So I'm loading tiles like this:

// Tiles.cs class
public CollisionTiles(int i, Rectangle newRectangle)
        {
            texture = Content.Load<Texture2D>("Graphics/Tiles/tile_" + i);
            this.Rectangle = newRectangle;
        }

And each one of the above integers are added to the string of the filename and diretly loaded and drawn on the screen (like this: map.Draw(spriteBatch);). The 64 int is the size the actual tiles are drawn into. That's what I want to avoid, having to write these arrays for each level, instead I need a way of loading levels from files. When loading the tiles of the level, the tile size shouldn't be forgotten in the .txt file. Maybe have it stay alone on the first or the last line of the level file? That way each level can have different size of tiles if needed.

Here are my Map.cs and Tiles.cs classes. I didn't paste them on SO directly it would make the question too long...

So how would you approach this? What kind of solution is best? I think loading levels similar to tiles like this: "Levels/level_" + i is also I nice way and just have the game increment the int i every time the player finishes a level (I should be able to do this myself), but I'm just asking how you would read a file. I will make any suggested modifications to my code and also I think Splitting the contents of the file by , is opinion based, could also use spaces , but since that's an array in the class I had to use ,s. In the text file I should also remove the { } braces. Anyway I thank you for any feedback and code examples with an explanation would be great!

EDIT: So should I add the tiles to collisionTiles like this?

public int[,] LoadLevelData(string filename)
        {
            using (var streamReader = new StreamReader(filename))
            {
                var serializer = new JsonSerializer();
                Generate((int[,])serializer.Deserialize(streamReader, typeof(int[,])), 64);
                return (int[,])serializer.Deserialize(streamReader, typeof(int[,]));
            }
        }

Solution

  • So how would you approach this? What kind of solution is best?

    The best solution to this problem really depends on how you plan to edit your levels.

    With your current approach (storing level data in code) you can actually edit the level manually by hand. This is not spectacular, but it is manageable for small games.

    The next approach is to store levels in a text file. I answered a similar question about this the other day. Many games have used this approach in the past with great success.

    If you want to keep things really simple, you could take the data you've got now and use JSON.NET to deserialize it. The cool thing is, the data represented in JSON looks almost exactly as it appears in code and can be edited by your favorite text editor.

    [
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
        [2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 2, 2],
        [2, 2, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 0, 2, 2],
        [2, 2, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 2, 2],
        [2, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2],
        [2, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
        [2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
        [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
        [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
    ]
    

    The load method is really simple too.

        public int[,] LoadLevelData(string filename)
        {
            using (var streamReader = new StreamReader(filename))
            {
                var serializer = new JsonSerializer();
                return (int[,])serializer.Deserialize(streamReader, typeof(int[,]));
            }
        }
    

    To use the method, you can pass the result into your Generate method:

        // This is in my LoadContent() method
        var levelData = LoadLevelData(filename);
        map.Generate(levelData, 64);
    

    That'll work just fine if you're always using size 64 blocks, although, you could store the block size in the JSON file as well if you like. It's a bit more complicated though, and probably too much for this answer. The JSON.NET library has excellent documentation.

    Since you're using MonoGame you probably want to use TitleContainer.OpenStream instead of a StreamReader to keep things working on all platforms. Alternately, you could write a content reader for the MonoGame Pipeline tool but that's beyond the scope of this question.

    The last, but not least, thing you might want to consider is using 3rd party level editor software like Tiled. There's a great Tiled map loader in my MonoGame.Extended library or a number of other existing map loaders to choose from.

    Whatever approach you choose, my suggestion is to keep things simple. Each approach has pros and cons, and simple solutions are usually the easiest to get started. Once you've got the basics working you can upgrade to the the complex approaches for added benefits.