卡牌游戏,卡牌架构

3

我目前是从Python背景转入学习C#的,还在逐渐掌握其内部机制。

现在,假如我要开发一个游戏,以Magic: the Gathering或其他流行的收集卡牌游戏为基础进行简单说明。在这样的游戏中,有多张可以使用的卡牌,这些卡牌都有不同的类型和效果。例如,可以有生物卡牌或法术卡牌等。

这些不同类型的卡牌具有不同的属性。例如,生物卡牌会有攻击值(attack)和生命值(health)两种属性,而法术卡牌可能只有一个效果属性(effect)。然而,所有这些卡牌仍然都是卡牌,在某些方面还是有共性的,比如说花费属性(cost)。这些不同的卡牌可能由管理者类(Manager)来管理。下面是这款游戏的代码样例:

public class Manager
{
    public CreatureCard card1 = new CreatureCard();
    public SpellCard card2 = new SpellCard();
}

public class Card
{
    public int cost;
}

public class CreatureCard : Card
{
    public int attack;
    public int health;
}

public class SpellCard : Card
{
    public SpellEffect effect;
}

我想做的基本上是创建卡片并将它们存储在管理类中,而不预先知道它们是法术还是生物。管理类可能如下所示:

public class Manager
{
    public Card card1;
    public Card card2;
}

这些卡牌将从其他地方分配。

现在,这并不真正有效。例如,没有办法访问卡的health属性。这是相当合理的,因为卡片甚至可能没有health属性。 这就是为什么我正在寻找一种可以运行的方法,同时保持结构大致相同。所以会有一个Card类,其中包含了很多东西,并从中派生出其他类(CreatureCard, SpellCard)。

是否有一种方法使其工作?我来自Python,所以这种方法在C#中可能完全错误。如果是这样,请告诉我我可以使用哪种方法来代替我现在试图使其正常工作的方法。


你能举个例子说明如何通过管理器对象理想地使用卡片吗?换句话说,你将如何知道是访问卡片的 health 还是 effect 字段? - rae1
我基本上会使用臭名昭著的鸭子类型。例如:这张卡牌已经被攻击了(所以它的生命值必须被修改),因此它一定是一个生物。当然,代码中有防止玩家攻击法术卡牌的措施。 - Lokkij
这里只是一个想法。对我来说,这听起来像是使用策略设计模式的好机会。您可以在接口中定义一个“战斗”方法,并注入不同的战斗行为类。 - Crudler
1个回答

4

您可以通过适当地转换Card实例来完成:

public class Manager
{
    public Card card1;
    public Card card2;
}

...
var manager = new Manager 
{
    card1 = new CreatureCard();
    card2 = new SpellCard();
}
if (manager.card1 is CreatureCard)
{
    var health = ((CreatureCard)manager.card1).Health;
    Console.WriteLine(health);
}
if (manager.card2 is SpellCard)
{
    var effect = ((SpellCard)manager.card2).effect;
    Console.WriteLine(effect);
}

或者像这样:
var creatureCard = manager.card1 as CreatureCard;
var spellCard = manager.card2 as SpellCard;
if (creatureCard != null)
{
    Console.WriteLine(creatureCard.health);
}
if (spellCard != null)
{
    Console.WriteLine(spellCard.effect);
}

为了进一步说明这一点:
  • To test if a card is of a specified type, use the is operator:

    bool isCardType = myCard is CardType;
    
  • Use (CardType) to cast / convert the card to the specified type:

    CardType specificCardType = (CardType)myCard;
    
  • To safely cast a card to a specific type or return null if it's not of that type, use the as operator:

    CardType specificCardType = myCard as CardType;
    if (specificCardType != null) {
        ...
    }
    

1
我还要提到的是,如果可能有多个类别的卡片具有您关心的特定属性,您可能希望以这种方式进行提取,您需要为这些属性(或相关属性集)创建接口,使您的类实现这些接口,然后使用与上面相同的逻辑,但检查对象是否为接口,如果是,则直接将其转换为接口,而不是检查单个类。如果每个属性只有一个类,那么这并不是必要的。 - neminem
谢谢,这基本上就是我要找的。不过看起来并不太美观,有没有更好的编写方式?@neminem 我已经在这样做了,只是不想让示例变得更复杂。不过还是谢谢你的建议 :) - Lokkij
@Lokkij C#通常是静态类型的,因此您需要向编译器提供有关如何使用对象的一些提示。理论上,您可以使用dynamic,这会导致所有成员访问在运行时解析,但这既低效又不良实践。您具体想要做什么? - p.s.w.g
@p.s.w.g 我的意思只是说 ((SpellCard)manager.card2).Effect 而不是 manager.card2.effect 会让代码看起来更加复杂,难以阅读。我总是喜欢保持我的代码尽可能干净和易读。 - Lokkij
@Lokkij 使用 as 是一个常见的技巧,因为它是安全的并且看起来更好一些。请参考我的更新答案中的示例。当然,你必须检查是否为 null - p.s.w.g

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接