在 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 草例項在我們的儲存庫中不存在,所以它被建立了,但是下次我們需要草時它已經存在,所以我們只返回它。