3

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[,]));
            }
        }
peterh
  • 11,875
  • 18
  • 85
  • 108
Johny P.
  • 648
  • 1
  • 9
  • 31
  • There are lots of ways to read files, and lots of ways to store this data. I suggest trying to google some and read about them, thus being able to weigh the pros and cons yourself. – D. Ben Knoble Aug 22 '15 at 18:52
  • How do you plan to edit your levels? If you are going to edit them by hand you might as well leave them in code. The main reason to load them from files is so you can use some sort of level editor. Yes, there are other reasons it might be "bad practice" but they only matter if they matter to you or your team. Always start with the simplest solution and make it more complex as required. – craftworkgames Aug 22 '15 at 21:59

2 Answers2

4

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.

Community
  • 1
  • 1
craftworkgames
  • 9,437
  • 4
  • 41
  • 52
  • OK, after I load the file how would I call `map.Generate`? Since when the file is loaded I will have to add it to `CollisionTiles`. – Johny P. Aug 24 '15 at 12:27
  • I've edited my question with a way I found to work with your code, fairly simple I had to call `map.Generate` with the deserialized json converted to `int[,]` so that it can accept it as a parameter and `64` as a hard-coded size variable. – Johny P. Aug 24 '15 at 12:34
  • @JohnyP. I've added more info to the answer. – craftworkgames Aug 24 '15 at 12:52
  • Yeah now I can continue with the project. – Johny P. Aug 24 '15 at 14:33
3

Loading levels and binary data, I like to use the BinaryReader and BinaryWriter. For arrays, I write the size first, and then the data. Like this:

void Write(string fileName, int[,] data)
{
    using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(fileName)))
    {
        writer.Write(data.GetLength(0));
        writer.Write(data.GetLength(1));

        for (int x = 0; x < data.GetLength(0); x++)
        {
            for (int y = 0; y < data.GetLength(1); y++)
            {
                writer.Write(data[x, y]);
            }
        }
    }
}

int[,] Read(string fileName)
{
    using (BinaryReader reader = new BinaryReader(File.OpenRead(fileName)))
    {
        int width = reader.ReadInt32();
        int height = reader.ReadInt32();

        int[,] result = new int[width, height];

        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                result[x, y] = reader.ReadInt32();
            }
        }

        return result;
    }
}
Philippe Paré
  • 4,279
  • 5
  • 36
  • 56
  • So how would my modified classes look like? I assume I should put this in the `Map` class and also can you provide example usage of that code? Also what would the level file be like, I mean should I use `,`'s and `{ }` braces? – Johny P. Aug 22 '15 at 19:58
  • I simply showed how to save `int[,]` to a file and load it back. This is how I do it, now you'd have to implement it in your class :) – Philippe Paré Aug 22 '15 at 20:41
  • How would the OP create these files? If there's no simple way to edit the levels, this solution doesn't really solve the problem. – craftworkgames Aug 22 '15 at 22:03
  • Well the question was not "How can I create a map editor" – Philippe Paré Aug 22 '15 at 22:04
  • @PhilippeParé You've answered the "how you would read a file" part of the question perfectly. It's just that there's other parts to this question like "What kind of solution is best?" – craftworkgames Aug 23 '15 at 00:30