2017-08-04 66 views
0

我試圖實現一個高性能的遊戲庫存系統。我有這個抽象基類來存儲不同類型的物品的庫存中,例如,硬幣,手電筒,刀等。創建遊戲庫存系統,無需轉換爲派生

public abstract class ObtainableItem 
{ 
    public string Name { get; private set; } 

    public ObtainableItem(string name) 
    { 
     Name = name; 
    } 
} 

例如,我有打開的門的DoorKey。 DoorKey具有用於打開門的屬性KeyCode。

public class DoorKey : ObtainableItem 
{ 
    public int KeyCode { get; private set; } 

    public DoorKey() : base("key") 
    { 
     KeyCode = 1234; 
    } 
} 

所有ObtainableItem都存儲在庫存

public class Inventory 
{ 
    const int slotCount = 2; 
    ObtainableItem[] slots = new ObtainableItem[slotCount]; 

    public Inventory() 
    { 
     slots[0] = new DoorKey(); 
    } 
} 

現在想象一下,用戶從他的庫存拖累DoorKey在門上,並觸發Open方法

public class Door 
{ 
    public void Open(ObtainableItem key) 
    { 
     if (key is DoorKey) 
     { 
      DoorKey doorKey = (DoorKey)key; 
      if (doorKey.KeyCode == 1234) 
      { 
       // Open door 
      } 
     } 
     else 
     { 
      // "can't use this item on a door" 
     } 
    } 
} 

如何避免投從ObtainableItem到DoorKey?我讀過使用鑄造是不好的做法,它指向了錯誤的代碼設計。理想情況下,一門類應該看起來像這樣。我的庫存系統有什麼樣的模式?

public class Door 
{ 
    public void Open(DoorKey key) 
    { 
     if (key.KeyCode == 1234) 
     { 
      // Open door 
     } 
    } 
} 
+2

你在哪裏讀過*鑄造是不好的做法*,我從來沒有讀過任何這樣的東西。 –

+0

@ErikPhilips可以指出拳擊/開箱的開銷 –

+1

@RandRandom是的,然後它* boxing/unboxing可能會導致性能問題*這是顯着不同於*糟糕的做法*。 –

回答

1

爲便於實施和可讀性,總會有例外。你所描述的是很常見的,如果不是典型的話。

另一種方法是在類中調用Door.Open的「控制」邏輯。這可能與觸摸反射的可以容易地實現:

public abstract class ObtainableItem 
{ 
    public string Name { get; private set; } 

    public ObtainableItem(string name) 
    { 
     Name = name; 
    } 
} 

public abstract class WorldItem 
{ 
} 

public interface IActsOn<in TWorldItem> 
    where TWorldItem : WorldItem 
{ 
    void ApplyTo(TWorldItem worldItem); 
} 

public class World 
{ 
    // If profiling shows that this is a performance issue, a cache keyed by tWorldItem, tInvItem 
    // should fix it. No expiry or invalidation should be needed. 
    private Action<ObtainableItem, WorldItem> GetApplyTo(Type tWorldItem, Type tInvItem) 
    { 
     var tActOn = typeof(IActsOn<>).MakeGenericType(tWorldItem); 
     if (!tActOn.IsAssignableFrom(tInvItem)) 
     { 
      return null; 
     } 
     var methodInfo = tActOn.GetMethod(nameof(IActsOn<WorldItem>.ApplyTo)); 

     return new Action<ObtainableItem, WorldItem>((invItem, worldItem) => 
     { 
      methodInfo.Invoke(invItem, new object[] { worldItem }); 
     }); 
    } 

    public bool IsDropTarget(WorldItem worldItem, ObtainableItem item) 
     => GetApplyTo(worldItem.GetType(), item.GetType()) != null; 

    public void ActOn(WorldItem worldItem, ObtainableItem item) 
    { 
     var actOn = GetApplyTo(worldItem.GetType(), item.GetType()); 
     if (actOn == null) 
     { 
      throw new InvalidOperationException(); 
     } 

     actOn(item, worldItem); 
    } 
} 

雖然本的World實施略微複雜,它簡化了各種對象的實現:

class Door : WorldItem 
{ 
    public void Unlock(string bitting) 
    { 
     if (bitting == "1234") 
     { 
      Console.WriteLine("Door Opened"); 
     } 
     else 
     { 
      Console.WriteLine("Door could not unlock"); 
     } 
    } 
} 

class DoorKey : ObtainableItem, IActsOn<Door> 
{ 
    private readonly string Bitting; 

    public DoorKey(string bitting) 
     : base("Key") 
    { 
     this.Bitting = bitting; 
    } 

    public void ApplyTo(Door worldItem) 
    { 
     worldItem.Unlock(this.Bitting); 
    } 
} 

class RubberChicken : ObtainableItem 
{ 
    public RubberChicken() 
     : base("Rubber chicken") 
    { 
    } 
} 

實例:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var key1 = new DoorKey("1234"); 
     var key2 = new DoorKey("4321"); 
     var rubberChicken = new RubberChicken(); 

     var door = new Door(); 

     var world = new World(); 
     Debug.Assert(!world.IsDropTarget(door, rubberChicken)); 
     Debug.Assert(world.IsDropTarget(door, key1)); 

     world.ActOn(door, key2); 
     world.ActOn(door, key1); 

     Console.ReadLine(); 
    } 
}