C# Monogame - 从文件加载关卡

C# Monogame - Load level from file

我正在构建一个 2D 平台游戏。但是制作视频的人只是展示了如何创建地图和图块 class 并从他制作的数组中加载关卡。我听说这是一种不好的做法,人们应该从文件中加载他们的关卡...我知道如何使用流 reader 加载文件,但由于那不是我的代码,我正在按照教程进行操作,但在实现它时遇到了困难.

这是我的关卡代码:

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

所以我正在像这样加载图块:

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

并将上述每一个整数添加到文件名的字符串中,并直接加载并绘制在屏幕上(如下所示:map.Draw(spriteBatch);)。 64 int 是绘制实际图块的大小。这就是我想要避免的,必须为每个级别编写这些数组,而不是我需要一种从文件加载级别的方法。加载关卡的图块时,不应忘记 .txt 文件中的图块大小。也许让它单独留在关卡文件的第一行或最后一行?这样每个级别都可以根据需要使用不同大小的图块。

这是我的 Map.cs and Tiles.cs class。我没有将它们直接粘贴到 SO 上,这会使问题太长...

那么你会如何处理这个问题?什么样的解决方案最好?我认为加载关卡类似于这样的图块:"Levels/level_" + i 也是我的好方法,每次玩家完成关卡时游戏都会增加 int i(我自己应该可以做到这一点) ,但我只是问你如何读取文件。我将对我的代码进行任何建议的修改,而且我认为 Split 通过 , 调整文件内容是基于意见的,也可以使用空格 ,但由于这是一个数组class 我不得不使用 ,s。在文本文件中,我还应该删除 { } 大括号。无论如何,我感谢您的任何反馈和带有解释的代码示例会很棒!

编辑: 那么我应该像这样将图块添加到 collisionTiles 吗?

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[,]));
            }
        }

加载关卡和二进制数据,我喜欢用BinaryReaderBinaryWriter。对于数组,我先写大小,再写数据。像这样:

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

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

这个问题的最佳解决方案实际上取决于您打算如何编辑您的关卡。

使用您当前的方法(在代码中存储关卡数据),您可以实际手动编辑关卡。这并不壮观,但对于小游戏来说是可以管理的。

下一个方法是将级别存储在文本文件中。前几天我 answered a similar question 关于这个。过去许多游戏都使用这种方法取得了巨大成功。

如果你想让事情变得非常简单,你可以获取你现在获得的数据并使用 JSON.NET 对其进行反序列化。很酷的是,JSON 中表示的数据看起来几乎与它在代码中出现的完全一样,并且可以用您最喜欢的文本编辑器进行编辑。

[
    [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]
]

加载方法也很简单

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

要使用该方法,您可以将结果传递给您的 Generate 方法:

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

如果您始终使用大小为 64 的块,那将很好地工作,但是,如果您愿意,您也可以将块大小存储在 JSON 文件中。虽然它有点复杂,但对于这个答案来说可能太多了。 JSON.NET 图书馆有 excellent documentation.

由于您使用的是 MonoGame,因此您可能想使用 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 但这超出了这个问题的范围。

最后但并非最不重要的一点是,您可能要考虑使用 Tiled. There's a great Tiled map loader in my MonoGame.Extended library or a number of other existing map loaders 等第 3 方级别的编辑器软件进行选择。

无论您选择哪种方法,我的建议都是让事情简单。每种方法都有利有弊,简单的解决方案通常最容易上手。一旦掌握了基础知识,您就可以升级到复杂的方法以获得更多好处。