自主牌游戏设计的数据模型

『技巧 | 经验』 版权:禁止匿名转载;禁止商业使用。

2002 0 0 2019-04-16 举报

CG素材

未设置职业

本帖最后由 Brittany89 于 2017-10-28 17:26 编辑

自主牌游戏设计的数据模型
我们通过创建允许系统互相交互的架构来开始,并且仍然没有别的系统。更糟的是,我们没有系统模型来操作!我们退回去几步然后从头开始创建我们的游戏。

打开我们上次运行的项目或者从上周的这里下载完整的项目。我也为熟悉版本的同学准备了线上的资源。

设计
理论上,在开始项目前,你可能会看几种设计文档。有时,代码中乱涂乱画还是挺有趣的,但是当你真正做东西的时候会发现,你从文章上会理解更深且更快。尤其是当你完全进入一个错的方向并不得不在你前进之前做些无用功。

在我们的例子中,我用一个已经存在的游戏,Hearthstone,就如设计文档中提到的一样。我可以用维基帮助分类各个好的那些在玩的过程中不显眼的点,。因此具体我们做什么呢?我认为最高水平的总览在这里-维基的游戏页介绍。开始几段听起来有点像广告,但章节命名为“Matches”似乎真的为我们开始定义些事情。

在这一点上,我们在设计文档中寻找的是“名词”。这些是代码中原始数据很容易被表达的项目,就如类的结构。这里有些事情需要指出来:
比赛-两位敌人/玩家的集合,带有游戏的回轮制概念所以现在是哪个玩家
玩家-带有自己的数据集的比赛中的参与者和各种各样位置的卡片比如“手”或者“副”。可能被人或者电脑控制
牌-被玩家拥有或者操作的对象。有多种类型比如魔法,武器,小卒,设置英雄牌。每种牌都有自己的数据集。

注意这里的“名词”形成了单个数据层。如果我进入了一个比赛,我有权使用任何东西。如果我之后保存和修复比赛,我会放置必要的对应东西到对应地方来继续比赛。如果我们使用火力点来存储线上比赛,我们需要的数据会很方便聚集起来就像这样。

我们有个大概的想法对于怎么使它成为游戏-每个玩家轮流玩牌最终的目标是v减少他们的对手英雄的分数使之变为0.我们这个教程的目标会定义足够的模型,所有的都会在代码中体现出来。我们打磨并修复我们进行的阐述,但看起来像很好的很小的来开始创建我们的系统。

比赛
我们定义比赛作为两个玩家的集合,有能力跟踪该轮到谁了。正如下面定义的小小的简单的模型一样:
[C#] 纯文本查看 复制代码
public class Match { public const int PlayerCount = 2; public List players = newList (PlayerCount); public int currentPlayerIndex; public Player CurrentPlayer { get { return players [currentPlayerIndex]; } } public Player OpponentPlayer { get { return players [1 - currentPlayerIndex]; } } public Match () { for (int i = 0; i < PlayerCount; ++i) { players.Add (new Player (i)); } }}

我为PlayerCount添加一个变量,因为程序员对出现在代码中的“神奇的数字”该叫什么有点挑剔。我们可以在许多地方用这个变量并用它在正确的容量中创建玩家列表(List of Players)。

我用一个简单的整型变量来表示顺序是什么。“currentPlayerIndex”是玩家列表中的玩家序号。我也创建了方便的属性使你能够轻易地知道currentPlayer是谁以及OpponentPlayer是谁,不管currentPlayerIndex被设置为什么。

你也注意到为了抓住对手玩家,变量1-currentPlayerIndex. 这会起作用因为我们有基于0的仅有两个玩家的列表。当现在的玩家序号是‘0’,对手的序号就是‘1-0=1’.如果现在的玩家序号是‘1’,然后对手是‘1-1=0’,很简单的数学

我为比赛对象添加构造函数,填充玩家列表,实例化一个玩家。这必须完成因为创建一定容量的列表与有内容填充的列表不一样

法力值
我们创建玩家模型前,我想抄近道为玩家的法力值创建另一个模型。技术上而言,他们被阐述为玩家的游戏统计状态,但我选择相关组织的分离以及更多的简明的名称。比如,“Player”领域中的可能是“spentMana”尽管相同的区域在“Mana”
类可能是简单的“spent”.
[C#] 纯文本查看 复制代码
publicclass Mana { public const int MaxSlots = 10; public int spent; public int permanent; public int overloaded; public int pendingOverloaded; public int temporary; public int Unlocked { get { return Mathf.Min (permanent + temporary,MaxSlots); } } public int Available { get { return Mathf.Min (permanent + temporary -spent, MaxSlots) - overloaded; } }}

我已经定义了另一个常量-“MaxSlots”是10,是另一个游戏设计的必要条件。另一个领域是:
花费-法力值的数量集用在目前的顺序中。在新一轮的轮换中会重置为‘0’
永久-获得的法力值机会的数量。每轮一个玩家赢得以此达到最大的机会数量
重载-由特定的压倒性牌引起的锁定的法力值机会数量
延迟重载-法力槽的数量在下一轮比赛中会被锁定
短暂的-暂时可用的法力槽的数量被魔法加持

我也创建了两个便利的属性。不锁定会对UI界面中渲染合适数量的法力水晶有帮助。可用性对于相同的目的也是很有帮助的,但更多。因此会帮助简化检查牌的花费是否是我们想要的

控制方式
玩家的一个属性将决定什么是在控制之中的。我想支持的三个操作会包括一个本地人类玩家,一个电脑,AI 玩家或者一个在线的敌人(远程玩家)。我们创建一个分离的列举文件来定义操作的类型:
[C#] 纯文本查看 复制代码
public enum ControlModes { Computer, Local, Remote}

区域
尽管一个玩家手握很多牌,他们被叫做“区域”的东西所组织,这些是不同的位置,即牌可以放在游戏中。看下下面的图,屏幕上半部分和下半部分被不同的玩家所控制。




  • 英雄出现在很高和很低的中心
  • 顶部英雄的左面是武器,但下面英雄没有武器装备
  • 每个玩家的牌组被聚集在屏幕右边
  • 左上及屏幕右下角落是角色
  • 战区代表的是每个英雄前面传唤小卒的地方
  • 圈内标志的秘密是可见的-顶部英雄在游戏中有两个秘密 墓地没有可见的指示,但他扮演者很重要的角色


在下面枚举中每个都被定义好了

[C#] 纯文本查看 复制代码
public enum Zones { Hero, Weapon, Deck, Hand, Battlefield, Secrets, Graveyard}



以后有太多理由与玩家的特殊区域的牌共战。比如,我们知道将有特殊能力的牌。不同的牌可能在不同的区域中应用。一些牌只能在你手中用,但其他人可能只注意桌上在玩的牌。确实没有一个很好的方式来序列化一个参考的实例化的区域,当尝试做我们牌的的数据库时。换句话说,我没有卡牌能力的入口包括比如“target”:”player.hand”因为“player”根本不在场景中。我可以使用一个字符串能被拆解成一个枚举,允许一个系统来决定比赛玩家和他战斗的区域。

玩家

一个玩家比被战区组织起来的牌组多-每个玩家会有一定的别的身份。比如,玩家跟踪魔法池,决定了多少牌会发生变化(因为一张牌有一种魔法会消耗掉)。如果一个玩家消耗了他们开始发生的疲劳伤害的牌-伤害数量每轮都会增长并被每个玩家所具体化。我们也需要阐述玩家被控制的不同的方式。我们可以如下定义:

[C#] 纯文本查看 复制代码
public class Player { public const int maxDeck = 30; public const int maxHand = 10; public const int maxBattlefield = 7; public const int maxSecrets = 5; public readonly int index; public ControlModes mode; public Mana mana = new Mana (); public int fatigue; public List hero = new List (1); public List weapon = new List (1); public List deck = new List (maxDeck); public List hand = new List (maxHand); public List battlefield = new List (maxBattlefield); public List secrets = new List (maxSecrets); public List graveyard = new List (maxDeck); public List this[Zones z] { get { switch (z) { case Zones.Hero: return hero; case Zones.Weapon: return weapon; case Zones.Deck: return deck; case Zones.Hand: return hand; case Zones.Battlefield: return battlefield; case Zones.Secrets: return secrets; case Zones.Graveyard: return graveyard; default: return null; } } } public Player (int index) { this.index = index; }}


在这个例子中注意,一些常量确实是在大小上有限制,但是其他只是初始容量。比如,尽管所有的玩家牌组从30张开始,一个特殊的能力可以引起一张牌被克隆并被重复添加到牌组,理论上可以引起牌组的数量超过它的原始数量。其他常量比如手中握着的牌的数量被限制,如果你尽力去在限制的条件下画一张牌,这张牌会被替换移动到废弃的那一堆中。这个规则不得不被作为系统的一部分进行强加。

你可能在想为什么英雄和武器战区作为集合在代码中建模,因为看起来每次都只有一张牌。一个原因是设计中的很大的灵活性。比如,当战斗在特殊的技能单个玩家的任务比如反击Professor Putricide,可能对手英雄有几个阶段,每个可能由一个分离的英雄牌代表在集合中。


另一个把英雄和武器战区作为一个集合的理由是,代码会简单点。如果我们处理单个行为的每个位置。比如,我们可以使用“indexer”区域,甚至在类中去简化集合的范围基于战区。然后当我们创建牌类,我们添加战区以致于很容易决定哪个集合是包括之内的。



牌的设计已经有许多种子类,但是我们可以把牌类当做相同的基类型(称作多态)。如果有任何状态在子类型中是相同的,我们在基类型中定义它们。我们看下每个的设计:





英雄-代表玩家
小卒-在战场上被召唤的人
魔法-可以做任何事
武器-被英雄装备
当我被炉石维基浏览时,我被link难住了,这个链接有所有的炉石卡牌数据。当然,我确实不确定炉石游戏直接用了这个,因为有人可以通过另外的方法来产生它。另一种方式,你可以用Json示例来装束并用实际的变量来运行。我不想用这么多数据来干扰,因此我有自由来把它们打乱成各种集合。你可以在这里看到。我确实不在主要项目中包括它。因为有版权问题如果有必要我会移除这里的链接。我们看下核心集合的一小段,每个主牌的四张,但要注意相同的牌包括可能变化的每个入口:
这里是一个英雄:
[C#] 纯文本查看 复制代码
{ "rarity" : "FREE", "name" : "Rexxar", "health" : 30, "id" : "HERO_05", "set" : "CORE", "collectible" : true, "playerClass" :"HUNTER", "dbfId" : 31, "cardClass" : "HUNTER", "type" : "HERO"}

这是小卒:
[C#] 纯文本查看 复制代码
{ "name" : "OasisSnapjaw", "health" : 7, "attack" : 2, "flavor" : "His dreams offlying and breathing fire like his idol will never be realized.", "cardClass" :"NEUTRAL", "type" : "MINION", "rarity" : "FREE", "howToEarnGolden" :"Unlocked at Druid Level 51.", "artist" : "Ittoku", "cost" : 4, "id" : "CS2_119", "collectible" : true, "set" : "CORE", "playerClass" :"NEUTRAL", "dbfId" : 1370, "race" : "BEAST"}

魔法:
[C#] 纯文本查看 复制代码
{ "name" : "Shadow Word:Death", "flavor" : "If you miss, itleaves a lightning-bolt-shaped scar on your target.", "playRequirements" : { "REQ_TARGET_TO_PLAY" : 0, "REQ_MINION_TARGET" : 0, "REQ_TARGET_MIN_ATTACK" : 5 }, "cardClass" : "PRIEST", "type" : "SPELL", "rarity" : "FREE", "howToEarnGolden" :"Unlocked at Level 43.", "artist" : "RaymondSwanland", "howToEarn" : "Unlocked atLevel 8.", "id" : "EX1_622", "collectible" : true, "set" : "CORE", "text" : "Destroy a minionwith 5Â or more Attack.", "playerClass" :"PRIEST", "dbfId" : 1363, "cost" : 3}

武器:
[C#] 纯文本查看 复制代码
{ "name" : "Fiery WarAxe", "attack" : 3, "durability" : 2, "flavor" : "During times oftranquility and harmony, this weapon was called by its less popular name,Chilly Peace Axe.", "cardClass" :"WARRIOR", "type" : "WEAPON", "rarity" : "FREE", "howToEarnGolden" :"Unlocked at Level 49.", "artist" : "LucasGraciano", "howToEarn" : "Unlocked atLevel 1.", "id" : "CS2_106", "collectible" : true, "set" : "CORE", "cost" : 2, "playerClass" :"WARRIOR", "dbfId" : 401}

这些小片段显示了很多特性,可能没有那么明显的阐述,比如牌的“golden”版本的事实,或者一些牌不被锁定而且会被赢得。也有些牌不被收集-也许是王牌。很多关键因素出现在每四个小段,其他可能出现在其他种类的牌,看起来确实有很多共性基于界面种类的。

尽管我喜欢项目的潜在特性集合的主意,但我确实没有这些领域的模型。我不知道我确实做什么来阐述,我喜欢简单的事物。我计划重新设计数据。目前为止我认为牌的重要的共享特性看起来像这样的:
[C#] 纯文本查看 复制代码
public class Card { public string id; public string name; public string text; public int cost; public int orderOfPlay = int.MaxValue; public int ownerIndex; public Zones zone = Zones.Deck;}

这个领域:id,name,text和cost被json小段所挑选因为我想马上包括的事物被很好的运用到各类型牌。Id是唯一的以至于任何时候我们能取回牌数据通过数据库的值来创建实例。Name是牌的名称出现在牌上。Text描述了牌所带的特殊技能,比如魔法对目标的破坏力。Cost是魔法值的数量值。

我也添加了一些我自己的字段。orderOfPlay动态填充因为实际玩牌的用户。这个字段的值在事件分析中很重要,因为牌第一次被玩也有他们需要解决的事件。

ownerIndex和zone使其对任何系统都很容易知道谁拥有牌及在什么区域收集牌。比如,想象魔法有这样的能力“Destroys”另一个目标牌。相反所有的玩家的所有的链接循环直到你找到目标盘的容器,你可能会直接用“ownerIndex”在“比赛”对象来获得正确的玩家,“zone”能被用在玩家的索引。因此你知道卡牌列表包括目标牌。现在你可以从区域中移除目标牌。把它放在灰色区域。

子牌类型
最普通的方式我过去解释多态是通过类的继承。现在也一样-牌的子类型的四种(英雄,小卒,魔法和武器)会作为牌的子类型来解释。但是,在子类中会有很多共性,在基类中不是足够相同的。

比如,一些类型的牌有些生命值的概念(小卒和英雄)-我想要一个攻击系统,任何能力都可以引起伤害并能够应用于几种牌。没有必要为每个写具体的阐述。但我也不想这些类型中的一个从其他人那边继承-英雄不是”特殊”的小卒。

这个问题的一个解决方法是全部子类类型阐述一个界面。你可以在你分享的代码中使用界面类型来处理牌的所有类型来阐述界面。这儿有些我们可以尝试的:
[C#] 纯文本查看 复制代码
public interface IArmored { int armor { get; set; }}public interface ICombatant { int attack { get; set; } int remainingAttacks { get; set; } int allowedAttacks { get; set; }}public interface IDestructable { int hitPoints { get; set; } int maxHitPoints { get; set; }}

我们提供一些我们牌的子类型的阐述当看到我们怎么通过这些界面阐述些共享的功能:
[AppleScript] 纯文本查看 复制代码
public class Hero : Card, IArmored, ICombatant, IDestructable { // IArmored public int armor { get; set; } // ICombatant public int attack { get; set; } public int remainingAttacks { get; set; } public int allowedAttacks { get; set; } // IDesructable public int hitPoints { get; set; } public int maxHitPoints { get; set; }}public class Minion : Card, ICombatant, IDestructable { // ICombatant public int attack { get; set; } public int remainingAttacks { get; set; } public int allowedAttacks { get; set; } // IDestructable public int hitPoints { get; set; } public int maxHitPoints { get; set; } // Other public List mechanics; public string race;}public class Spell : Card {}public class Weapon : Card { public int attack; public int durability;}

这些类可能没有“完成”-比如,英雄有些英雄力量的概念,最终我可能添加特征尤其是基类比如能力-但我们能够增长他们因为我们写系统来支持它们。

总结
单独课程我们已经管理“模型”一个好的自主牌游戏设计会是什么样子。从比赛来看,对于玩家参与的,他们拥有且玩的是牌-每件事囊括在一个单独的数据层,在以后非常容易坚持。我们有足够的空间来开始创建多样的新系统并决定游戏的玩家-只是眼看着英雄的生命值会降到0

你可以通过这里的课程很好的理解项目,完整课程完整版。如果你对版本控制非常熟悉,你可以从我的知识库获取项目的复印件。

如果你在我的博客中找到这些值,你可以通过成为我的资助者来继续支持。这里可以查看我的主页,多谢!

原文链接:http://theliquidfire.com/2017/09/04/make-a-ccg-data-modeling/
翻译:Brittany

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

让资源更有价值

  • Archiver|
  • 手机版|
  • 小黑屋|
  • CG素材网
  • 蜀ICP备18003526号-3
  • Powered by Discuz! X3.4
  • © 2001-2017 Comsenz Inc.
  • GMT+8, 2024-3-29 13:44 , Processed in 0.311066 second(s), 34 queries .

 关注CG资源素材

快速回复 返回顶部 返回列表