我的体系结构的多重继承的替代方案(实时策略游戏中的NPC)?


11

编码实际上并不难。困难的部分是编写有意义,可读和可理解的代码。因此,我想找一个更好的开发人员并创建一些可靠的体系结构。

因此,我想为电子游戏中的NPC创建一个体系结构。这是一个实时战略游戏,例如《星际争霸》,《帝国时代》,《命令与征服》等。因此,我将拥有不同种类的NPC。一个NPC可以有一对多的能力,这些(方法): Build()Farm()Attack()

例子:
工人可以Build()Farm()
战士可以Attack()
公民可以Build()Farm()Attack()
渔民可以Farm()Attack()

我希望到目前为止一切都清楚。

所以现在我有了我的NPC类型及其能力。但是让我们来谈谈技术/程序方面。
对于我不同种类的NPC,什么样的程序设计架构会很好?

好吧,我可以有一个基础课。实际上,我认为这是坚持DRY原则的好方法。因此,我可以WalkTo(x,y)在基类中使用类似的方法,因为每个NPC都可以移动。但是现在让我们来解决真正的问题。我在哪里实现我的能力?(记住:Build()Farm()Attack()

由于这些能力将由相同的逻辑组成,因此为每个NPC(工人,战士,..)实施它们将是令人讨厌的/打破DRY原则。

好吧,我可以在基类中实现这些功能。这将需要某种逻辑来验证NPC是否可以使用X IsBuilder,... CanBuild,…… 能力。我想我想表达的很清楚。
但是我对这个想法不太满意。这听起来像一个功能过多的。肿基类。

我确实使用C#作为编程语言。因此,这里不能选择多重继承。意思是:拥有额外的基础类是Fisherman : Farmer, Attacker行不通的。


3
我正在看这个例子,看来这是一个解决方案:en.wikipedia.org/wiki/Composition_over_inheritance
Shawn Mclean 2014年

4
这个问题会适合更好的gamedev.stackexchange.com
菲利普

Answers:


14

组合和接口继承是经典多重继承的常用替代方法。

上面描述的所有以“ can”开头的功能都是可以用接口表示的功能,如ICanBuild或中ICanFarm。您可以根据需要继承任意多个接口。

public class Worker: ICanBuild, ICanFarm
{
    #region ICanBuild Implementation
    // Build
    #endregion

    #region ICanFarm Implementation
    // Farm
    #endregion
}

您可以通过类的构造函数传递“通用”建筑和耕作对象。那就是合成的来源。


只是大声思考:“关系”(内部)将是has而不是is(Worker有Builder,而Worker是Builder)。您所解释的示例/模式很有意义,听起来像是解决我的问题的一种很好的分离方式。但是我很难建立这种关系。
Brettetete 2014年

3
这与Robert的解决方案是正交的,但是当您大量使用组合物以避免产生复杂的副作用网络时,您应该支持不变性(甚至比平时更多)。理想情况下,您的构建/农场/攻击实施无需删除任何其他内容即可将数据导入和吐出数据。
2014年

1
由于您是从继承的,所以Worker仍然是一个构建器ICanBuild。在对象层次结构中,您还拥有用于构建的工具是第二要考虑的问题。
罗伯特·哈维

说得通。我将试一试。感谢您的友好和描述性答复。我真的很感激。我会让这个问题开放一段时间:)
Brettetete 2014年

4
@Brettetete将角色的Build,Farm,Attack,Hunt等实现视为技能可能会有所帮助。BuildSkill,FarmSkill,AttackSkill等。然后构图变得更加有意义。
埃里克·金

4

正如其他张贴者所提到的,组件体系结构可以解决此问题。

class component // Some generic base component structure
{
  void update() {} // With an empty update function
} 

在这种情况下,扩展更新功能以实现NPC应该具有的任何功能。

class build : component
{
  override void update()
  { 
    // Check if the right object is selected, resources, etc
  }
}

迭代组件容器并更新每个组件可以应用每个组件的特定功能。

class npc
{
  void update()
  {
    foreach(component c in components)
      c.update();
  }

  list<component> components;
}

由于需要每种类型的对象,因此可以使用某种形式的工厂模式来构造所需的单个类型。

npc worker;
worker.add_component<build>();
worker.add_component<walk>();
worker.add_component<attack>();

随着组件变得越来越复杂,我一直遇到问题。降低组件的复杂度(一项职责)可以帮助减轻该问题。


3

如果您定义了类似的接口,ICanBuild那么您的游戏系统就必须按类型检查每个NPC,这通常在OOP中会被忽略。例如:if (npc is ICanBuild)

您最好选择:

interface INPC
{
    bool CanBuild { get; }
    void Build(...);
    bool CanFarm { get; }
    void Farm(...);
    ... etc.
}

然后:

class Worker : INPC
{
    private readonly IBuildStrategy buildStrategy;
    public Worker(IBuildStrategy buildStrategy)
    {
        this.buildStrategy = buildStrategy;
    }

    public bool CanBuild { get { return true; } }
    public void Build(...)
    {
        this.buildStrategy.Build(...);
    }
}

...或者当然,您可以使用多种不同的体系结构,例如以流利的界面形式使用的特定于领域的语言,用于定义不同类型的NPC等,在其中您可以通过组合不同的行为来构建NPC类型:

var workerType = NPC.Builder
    .Builds(new WorkerBuildBehavior())
    .Farms(new WorkerFarmBehavior())
    .Build();

var workerFactory = new NPCFactory(workerType);

var worker = workerFactory.Build();

NPC生成器可以实现一个DefaultWalkBehavior可以在NPC飞行但不能行走等情况下被覆盖的功能。


好吧,只是想一想:if(npc is ICanBuild)和之间实际上没有多大区别if(npc.CanBuild)。即使当我npc.CanBuild从目前的态度中选择出路时。
Brettetete 2014年

3
@Brettetete-您可以在这个问题中看到为什么某些程序员真的不喜欢检查类型。
Scott Whitlock

0

我将所有能力放在从Ability继承的单独的类中。然后,在单元类型类中具有能力和其他内容的列表,如攻击,防御,最大生命值等。还具有具有当前健康状况,位置等并将单元类型作为成员变量的单元类。

在配置文件或数据库中定义所有单元类型,而不是对其进行硬编码。

然后,关卡设计人员可以轻松调整单位类型的能力和状态,而无需重新编译游戏,人们也可以为游戏编写mod。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.