在 RPG 游戏中实现地图

Flyweight 是结构设计模式之一。它用于通过与类似对象共享尽可能多的数据来减少已用内存量。本文档将教你如何正确使用 Flyweight DP。

让我通过一个简单的例子向你解释它的想法。想象一下,你正在开发 RPG 游戏,你需要加载包含一些角色的大文件。例如:

  • # 是草。你可以走在上面。
  • $ 是起点
  • @ 是摇滚乐。你不能走路。
  • %是宝箱

地图样本:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

@############@@@@@######@#$@@@

@#############@@@######@###@@@

@#######%######@###########@@@

@############################@

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

由于这些对象具有类似的特征,因此你无需为每个地图字段创建单独的对象。我将告诉你如何使用 flyweight。

让我们定义一个我们的字段将实现的接口:

public interface IField
{
    string Name { get; }
    char Mark { get; }
    bool CanWalk { get; }
    FieldType Type { get; }
}

现在我们可以创建代表我们字段的类。我们还必须以某种方式识别它们(我使用了枚举):

public enum FieldType
{
    GRASS,
    ROCK,
    START,
    CHEST
}
public class Grass : IField
{
    public string Name { get { return "Grass"; } }
    public char Mark { get { return '#'; } }
    public bool CanWalk { get { return true; } }
    public FieldType Type { get { return FieldType.GRASS; } }
}
public class StartingPoint : IField
{
    public string Name { get { return "Starting Point"; } }
    public char Mark { get { return '$'; } }
    public bool CanWalk { get { return true; } }
    public FieldType Type { get { return FieldType.START; } }
}
public class Rock : IField
{
    public string Name { get { return "Rock"; } }
    public char Mark { get { return '@'; } }
    public bool CanWalk { get { return false; } }
    public FieldType Type { get { return FieldType.ROCK; } }
}
public class TreasureChest : IField
{
    public string Name { get { return "Treasure Chest"; } }
    public char Mark { get { return '%'; } }
    public bool CanWalk { get { return true; } } // you can approach it
    public FieldType Type { get { return FieldType.CHEST; } }
}

就像我说的,我们不需要为每个字段创建单独的实例。我们必须创建一个字段存储库。Flyweight DP 的本质是我们只在需要它时动态创建一个对象,它在我们的仓库中不存在,或者如果它已经存在则返回它。让我们编写一个简单的类来为我们处理这个:

public class FieldRepository
{
    private List<IField> lstFields = new List<IField>();

    private IField AddField(FieldType type)
    {
        IField f;
        switch(type)
        {
            case FieldType.GRASS: f = new Grass(); break;
            case FieldType.ROCK: f = new Rock(); break;
            case FieldType.START: f = new StartingPoint(); break;
            case FieldType.CHEST:
            default: f = new TreasureChest(); break;
        }
        lstFields.Add(f); //add it to repository
        Console.WriteLine("Created new instance of {0}", f.Name);
        return f;
    }
    public IField GetField(FieldType type)
    {
        IField f = lstFields.Find(x => x.Type == type);
        if (f != null) return f;
        else return AddField(type);
    }
}

大! 现在我们可以测试我们的代码:

public class Program
{
    public static void Main(string[] args)
    {
        FieldRepository f = new FieldRepository();
        IField grass = f.GetField(FieldType.GRASS);
        grass = f.GetField(FieldType.ROCK);
        grass = f.GetField(FieldType.GRASS);       
    }
}

控制台中的结果应该是:

创建了一个新的 Grass 实例

创建了一个新的 Rock 实例

但是,如果我们想要两次吃草,为什么草只出现一次呢?那是因为我们第一次调用 GetField 草实例在我们的存储库中不存在,所以它被创建了,但是下次我们需要草时它已经存在,所以我们只返回它。