程序员谈炉石传说:如何实现卡牌机制
来源:18183 作者:小k 时间:2016-05-16
大伙每天玩炉石,偶尔会遇到游戏bug,偶尔会看到意想不到的特效,那么问题来了:炉石传说是如何通过程序运行实现这些机制的呢?
炉石中的单位特效和规则较多,各种触发条件也不同,加上单位和法术数量庞大,随着不停地推出新的扩展包和冒险模式,底层代码肯定会非常复杂,如果规划不好很容易导致灾难性的后果。
那么现在,我想从一个程序猿的角度,来说一说我所看到的炉石传说是怎样的。本人不是暴雪员工,以下代码和逻辑纯属猜测,如有雷同,求暴雪爸爸发offer~

我们希望得到的系统,每张卡的逻辑写在自己的代码里,不要写到别的卡的代码里; 改代码的时候,改一张卡不影响其他卡和整个系统;整个系统运行效率高;新加入卡不用改旧代码;新加入机制不用改旧代码。
从最简单的模型开始,没有任何特效,战吼亡语全没有,随从有费、攻、血 三个属性,最初级的炉石随从牌,就是小精灵,0费11身材,没有特效和属性。台面上都是1攻1血随从互殴,简单明了但是太单调。
那么我们来加一个随从:食尸鬼,每当一个随从死去,就给他加1攻。

接下来的问题是这个机制要怎么实现呢?
首先我们来想象一个超级裁判,所有的逻辑包括战吼、亡语、受伤、治疗等都由他来处理,那么势必所有的逻辑都要写在这个超级裁判里,每加一个新卡,我们就去改代码,让这个超级裁判学会如何处理这张卡。很快的,卡牌超过20张的时候这个裁判的代码已经美如画了,不现实。
那么,按照一般的思路,我们模拟着实现一下“事件发生在哪里,相关的代码就写在哪里”的策略,看看是否真的可以实现炉石的游戏机制。
可以在“小精灵”这个类里面加入如下代码:
(说明:继承:随从 表示这个类是随从的一种,我们会把所有随从的共性写在一个叫随从的类里面,然后每个不同的随从再单独实现,这是一种实现层级关系的方式。暂时理解为“小精灵”是“一种”随从 即可。)

class 小精灵 继承:随从
{
function 死亡()
{
检查所有盘面上的单位,如果它是食尸鬼,给他+1攻击力
}
}
这样,我们就实现了,每当死掉一个小精灵,就给场上的食尸鬼加1攻击力。
但是我们是不是忘了,另一个食尸鬼死掉的时候,也得给其他食尸鬼加攻击?那么我们就再把类似的代码加进食尸鬼的实现代码中。
class 食尸鬼 继承:随从
{
function 死亡()
{
查找所有盘面上的单位,如果它是食尸鬼,给他+1攻击力
}
}
想必大家也已经看出来问题在哪里了,这玩意重复了啊对不对?同样的代码写了2遍,一个随从就占了几千行代码,而且以后食尸鬼如果改了,我们得改几百遍?明显不可行。

那么我们想到了,既然都一样,是不是可以写在“超类”里呢?所有随从都继承于“随从”这个超类,我们把食尸鬼这个逻辑写到随从类的死亡事件里面去不就得了,反正都是随从,随从死了就给场上所有食尸鬼+1攻击力。
到此为止,我们加了一个食尸鬼,现在我们有2种随从了,一切都看起来不错。

然后我们再加一个法术吧,严正警戒。当回合每死一个随从,-1费。那么
class 随从
{
function 死亡( )
{
查找所有盘面上的单位,如果它是食尸鬼,给他+1攻击力;//这是食尸鬼的
如果有玩家手牌里有严正警戒,给它-1费(最少是0);//这是严正警戒的
}
}
OK看起来不错,那么看起来这是一个可行的方案。但是这个方案存在一个问题,就是各种各样具有特殊效果和需要结算的随从太多了,而且还要考虑法术和其他影响因素等复杂性,这么干的话,这个函数是不是得上5000行,甚至上万行?同时改版的时候,改一张卡就得到这类来改,工作量巨大,依然不实际。而且除了“死亡”事件,炉石里面还有很多“受伤”、“减费”等等其他事件和影响因素,每一个都会产生一个类似“死亡”的上万行的巨大函数。
如果从不合适的类别出发来写函数,工作量会非常大,运行效率低下,无法投入实际运行。
至此我们能得出结论:不能采用“事件发生在哪里,相关的代码就写在哪里”的策略,该策略会导致超类的代码无限膨胀,每次修改的时候还得改那个几万行代码的逻辑,一旦出错就是“调皮的小精灵”。

明确上述的问题,在此探讨一下可行的方案:
如果有一个机制,每当有随从死掉的时候,都能及时让食尸鬼知道,然后由食尸鬼去决定它应当如何,是不是会好很多呢?比较合理的是设计一个“事件-通知”的机制,把发生的各种事件,通知到各个单位,让他们自行决定是否应当处理。
“通知表”是炉石的事件触发机制核心,也是炉石在代码层面可以水平扩充卡池、增添各种特效的基础。每个单位产生时,会根据自己的特效,把自己注册到一些表中去。这里说的产生可能不是随从上场,而是游戏开始的时候,系统对卡牌进行初始化的时候。
比如刚刚我们说的,“死亡”这个特效的事件通知表。食尸鬼对随从的死亡感兴趣,希望得到通知,所以它上场之后,就把自己注册到这个通知表里面。

举例说明:
这个列表里有一个食尸鬼,一个诅咒教派领袖,还有一个奥秘“救赎”,那么我们依次通知他们,食尸鬼立刻就知道了有一个单位死了,按照既定的触发
顺序:
食尸鬼给自己+1攻;诅咒教派领袖会检查这个单位是不是己方的,是己方的,就抽张牌,不是的话,不产生动作;奥秘救赎会检查更多的东西,是否是对方的回合而且是己方的单位,如果是,触发救赎。死亡这个事件通知表中并没有其他单位了,这个死亡事件结束。
我们来看一下这个机制:食尸鬼的代码写在食尸鬼这里,诅咒的代码写在诅咒这里。如果我们把食尸鬼改为“己方随从死亡+2攻”呢?很简单,在食尸鬼这里直接改就好。如果删除了食尸鬼这张牌呢?直接删就好了。如果我们要增加一个类似机制的卡牌呢?也很简单,加一个,上场的时候把自己加到通知列表里,就能得到通知,并且在得到通知之后做相应的事情就好了。
每个单卡需要实现自己内部逻辑,然后就是要知道自己在初始化的时候要注册哪几个通知表即可。只要能获得及时正确的通知,就能正确地响应和实现特效。

我们大概会需要抽牌、回合开始、回合结束、使用法术、使用一张牌、弃一张牌、随从产生、随从死亡、一个单位受到伤害、一个单位受到治疗、一个单位被法术命中、奥秘被触发(这和法术被使用完全不同)、疲劳、触发战吼、触发亡语等类型的通知表。随着游戏不断推陈出新,可能还有很多其他的通知表和新事件。要注意每个事件可能会引起其他的事件,比如小蜘蛛死了之后给食尸鬼+1攻,然后出来2个小蜘蛛,飞刀飞2刀,飞中了铸甲师,加了2点甲,又给暴乱加了2攻。

有了这样的机制,我们清楚地看到了一个事件被通知到了相关单位,触发了特效,又触发了其他的事件,事件的通知又触发了其他单位的特效处理。简单来说就是某个随从对某个事件感兴趣,然后按顺序各自完成了自己的特效。这个模型中,我们可以添加更多的特效,更多的卡牌,只要依次实现他们自己的逻辑即可,加入新卡、新机制,也不用修改旧代码。
以上就是我认为的可行的炉石的基本实现机制。
这种“通知列表-注册-通知”的机制,在软件工程学里叫做“观察者模式”。

后续还会从程序猿的视角继续讨论一些其他方面的内容,欢迎感兴趣的朋友继续关注,也希望得到各位的意见和反馈。
感谢各位的支持。
免责声明:文中图文均来自网络,如有侵权请联系删除,18183手游网发布此文仅为传递信息,不代表18183认同其观点或证实其描述。
相关内容
-
现在居然还能靠玩传奇打米?赶快叫上兄弟们一起来打米吧
为传奇玩家推荐真的能打米的传奇游戏、传奇页游和传奇端游,想要什么样的传奇游戏这里全都有,赶快叫上自己的好友们开启新一轮的热血传奇吧!