用Java从头开始创建游戏(第2部分)

如果您尚未阅读第1部分,请先将其签出。

创建任何大型软件都始于良好的体系结构。 做出架构决策完全是为了平衡:您在细节的复杂性,实现的复杂性,实现所需的时间长度,所需的代码大小,团队规模和知识等因素之间取得平衡。

我将使用接下来的几篇文章详细说明各种游戏组件的体系结构决策。 部分目的是通过我的思想过程来进行工作,并帮助我更深入地进行自我反思,但实际上,我希望以下文章对将来对软件工程领域不熟悉的工程师有用,并且可以利用一些见解来对它们进行分析。决策或如何思考决策过程。

因此,我们将从游戏最重要的方面之一开始:纸牌。

卡架构

出乎意料的是,纸牌游戏是用纸牌玩的。

谁能猜到?

快速阅读规则会告诉我们需要实现几种卡:

  • 用作居民,建筑和奇迹卡锚点的地块 ,卡片。 在每个玩家回合开始时,根据其类型: 海岸,草原,山脉,丘陵,森林河流 ,他们将为其控制的每个地块获得2种资源
  • 人口 ,卡片,是其附属可以提供的地块的一种附加资源。 在一个地块上拥有3个或更多的人口,也可以使该地块拥有第二座建筑物或奇迹。
  • 技术是游戏的制胜法宝-它在采用最后一种技术时就结束了。 技术卡是由玩家购买的,除了胜利点以外,几乎都会提供效果。
  • 建筑物 ,提供一些效果并附加到单个图块上。
  • 奇观 ,提供了一些复杂且通常强大的效果,并附加到单个图上。
  • 单位 ,用于窃取人口,摧毁建筑物并防御其他单位的卡。 请注意,有一张特殊的单位卡,即弹射器(Catapult) ,只有在玩家购买了特定技术后才能防御,并且只能进攻以摧毁Walls建筑。
  • 资源 ,用于获取卡定义类型的X资源的卡,或者如果卡为Wild则获取任何资源。

除了上面列出的那些卡外,还有一些用于玩家的幸福感文化程度最高的特殊占位符卡,仅当玩家达到该属性的2个或更多时才提供。 还有一些资源占位符卡可以用作跟踪资源池值的一种方式-认为现金垄断,比实际的卡组件更能代表数字。 这些实际上不会在我们的计算机游戏中表示为卡,因为它们只是具有指定属性而赋予的其他效果。 因此,我们的第一个架构原则是:

了解您的需求并构建您所需的东西,仅此而已。

为了强调这种简单性,我们也将每个地块的人口表示为一个数字,而不是一张单独的卡片,因为没有理由将它们视为其他任何东西。

现在我们如何设计实际的卡?

许多程序员认为对象继承与职责重叠之间存在明显的关系,并认为多态性。 我就是那些程序员之一。 因此,我们当然要拥有Card基础类。 我不认为任何OOP程序员都会不同意。

此实现中的Card本质上是一个Model(我们将在以后的文章中进一步讨论MVC / MVP以及如何使用它)。 Card基类将封装CardInfo对象,该对象只是一个完整描述CardHashMap 。 检索卡信息(例如名称,效果文本等)的方法将CardInfo提取或计算数据。

剩下的代码如下:

 公开课卡{ 

私人最终CardInfo cardInfo;

公共卡(CardInfo cardInfo){
this.cardInfo = cardInfo;
}
}

但是,关于Card类的子代到底应该做什么,还有很多决定。 您可能会争辩说,上述每种类型的卡都应该是一个单独的类,这是一个合理的论点。 但它需要编写许多单独的类和样板代码。 您也可以说我们应该有一个单一的Card对象,该对象封装了Card可能具有的所有行为。 再说一次,这是可行的,但对于一个单一的班级来说似乎担负着巨大的责任,而且听起来像是那种设计使我们只剩下一个3000行的班级,很难继续前进。

那使我们处于中间位置。 我们将要区分卡类型不共享的职责,同时将它们确实共享的职责封装到模块化的功能块中。

经过一番思考,这是我想出的子类列表。 随时不同意:

  • 情节卡
  • 技术卡
  • 可玩卡

因此,我将7种不同的卡类型列表分为3个基本类。 这是我对此的思考过程。

PlotCard类将只是一个Card ,其CardInfo中有一个特殊对象RESOURCE_YIELD ,并且是一个使用该对象返回绘图产量的方法。 例如, Coast将产生[ RES_COMMERCERES_COMMERCE ]数组,或者我们决定称之为的数组。 希尔将产生[ RES_PRODRES_FOOD ]。 很简单。

TechCard是一种足够特殊的情况,因为技术会发挥作用,需要玩家购买。 我们将在以后的文章中介绍效果架构,但是现在足以说我们构建的内容将是模块化的。

最终的卡片类型PlayableCard封装了玩家手中所有物品。 如果阅读了游戏规则,您会记住有3个牌组使用-一个积木组,其中随时有3个积木可供使用; 技术平台,其作用类似于情节平台,但适用于技术;还有一个供玩家借鉴的大型平台。

剩下下面的类图:

您会注意到, PlayableCard实例是奇观,建筑物,资源卡和单位。 将很多内容构建到一个类中! 幸运的是,这种设计有一些明显的好处,可以让我们做一些花哨的技巧。 一方面, PlayableCards都是可玩的 。 如果我们确实决定进一步细分类层次结构,则无论我们做出什么决定,我们都希望有一个PlayableCards子类,因为它们都至少共享该功能。 这也使我们能够维护游戏的结构:每种纸牌类型都有一种类型的副牌,而每种纸牌类型恰好具有一种牌型。 这使我们人类可以更轻松地针对诸如AI之类的未来组件,甚至针对诸如游戏状态和玩家架构之类的下一个组件,来概念化游戏状态。

另外,我将把卡功能分为许多接口以反映其职责。 PlotCard将实现Plot接口, TechCard将实现Tech接口,而PlayableCard将实现其子类型将要承担的每个职责的接口。

这为我们提供了很多用于将来更改的灵活性,并使初始实施变得非常灵活和模块化。 例如,假设将来我们会将奇迹分解成自己的套牌。 我们需要做的就是从PlayableCard删除Wonder接口,将它们转换为自己的卡类型,其余代码实际上会自行编写。

这就为Card及其子类提供了以下架构。

最后,我们可以构建CardFactory类来获取单个Card实例。

由于具有Java泛型,因此看起来有点尴尬,但它仍然足够简单,一旦我们将卡放在JSON文件中,例如下面的plot.json

  { 
“海岸”:{
“收益”:[“商业”,“商业”]
},
“河”:{
“产量”:[“食品”,“商业”]
},
“山”:{
“产量”:[“食物”,“生产”]
},
“山”:{
“产量”:[“生产”,“生产”]
},
“森林”: {
“收益”:[“生产”,“商业”]
},
“草原”:{
“产量”:[“食物”,“食物”]
}
}

从JSON文件生成一组卡的代码很简单。 我会将其留给读者练习—或者,只要在https://github.com/brooks42/JavaCivCardGame上查看项目的github回购,就可以了。

如何将它们全部集成到其余代码库中? 在下一篇文章中查找!