当前位置:首页>>攻略文章>>正文
魔兽科普:模拟器组成原理(一)
2013-12-27 23:50:41 作者:fhsvengetta 来源: 浏览次数:0
摘要:我会把我所知道的重点尽可能地写出来。由于这个系统非常庞杂,不可能事无巨细全都写出来,但在你读懂这篇内容之后,加上少许编程功底,应该就能够达到自制DPS模拟器的水平。
导航:

 蒙特卡洛法重识SimC反向工程时间驱动和事件驱动表达式解析面向对象的模拟器设计机器不掷骰子|


面向对象的模拟器设计
SimC主要是使用C++程序设计语言所编写的。
C++是一门非常适合编写模拟器的语言。作为现在最常用的程序设计语言之一,C++最早被发明时的初衷便是用来编写模拟程序,C++之父Bjarne Stroustrup博士在其著作《The C++ Programming Language》里坦诚到:“初始发明这个语言(指C with Classes,C++的前身),是因为我想去写某些事件驱动的模拟程序”。当时语法上最适合编写模拟程序的语言是Simula67,从这种语言的名字上就可见一斑,它是上世纪60年代专门设计用来编写模拟器的语言,也是世界上第一个面向对象程序设计(Object-Oriented Programming, OOP)语言,同属于ALGOL家族。Bjarne Stroustrup博士将Simula67中的一些思想融合到C语言中,形成了最初的C with Classes。
风靡一时的OOP,其最早的概念“类”、“继承”,如今这些所有程序员都耳熟能详的术语,就是由制作模拟器的需求所催生出来的。

所以,很显然,模拟器的核心,就是对象。
什么是对象?
这里简单地对OOP中的一些概念进行阐述,方便没有编程基础的人往下阅读。
阅读这样几个句子,体会它们的含义:
 

  • 你知道什么叫“动物”对吧。
    • 动物会呼吸。
    • 动物都有身长和体重这两种指标。
  • 鸟类是一种动物。
    • 鸟类会飞。 这里隐含着一点小错误,但是你暂时不用管它。
    • 鸟类会下蛋。
  • 猫头鹰是一种鸟类。
  • 我有一只猫头鹰,名叫Minerva。
    • Minerva身长21公分。
    • Minerva重467克。

这里,“动物”“鸟类”“猫头鹰”都叫做类。
而“Minerva”叫做对象。Minerva是一只猫头鹰,我们说Minerva是猫头鹰类的一个实例。

  • 让Minerva飞。 没问题!Minerva会飞。
  • 让Minerva呼吸。 没问题!Minerva会呼吸。
  • 让Minerva去打瓶酱油回来。 错了,Minerva不会打酱油。
  • 让鸟类飞。 错了,鸟类不是一个对象,它是一个类。

我们管“飞”“呼吸”这些动作称为方法。Minerva支持“飞”这个方法,所以我们可以让Minerva飞。
Minerva不支持“打酱油”这个方法,所以我们没办法让Minerva去打酱油。
而“让鸟类飞”显然是非常滑稽的一种说法,不需要我多解释你们都能明白为什么。

  • 告诉我Minerva的体重? 没问题!Minerva重467克。
  • 告诉我鸟类的体重? 错了,鸟类不是一个对象,它是一个类。

“体重”“身长”这些称为属性。

“动物”“鸟类”“猫头鹰”三个类之间的关系称为继承。猫头鹰继承自鸟类,鸟类继承自动物。
鸟类具有动物的所有属性和方法。猫头鹰具有鸟类的所有属性和方法。
我们管鸟类叫做猫头鹰的基类,管猫头鹰叫做鸟类的派生类。

以上是一些简单的概念介绍。
思考题
"鸟类会飞"这里究竟隐含了什么错误?如何改正?
(提示:企鹅是鸟类。)
为什么要用对象?
回到魔兽世界里来。
如果我们没有面向对象的这些概念,进行模拟会是很痛苦的一件事情。
例如我们现在要描述一些待模拟的职业:

职业 盗贼 战士 小德
属性 急速
精通
方法 攻击
爆菊    
冲锋    
卖萌    

如果我们使用不带有OOP性质的程序设计语言,描述上述问题,就需要这么写:
盗贼的急速。
盗贼的精通。
盗贼的攻击。
盗贼的爆菊。
战士的急速。
战士的精通。
战士的攻击。
战士的冲锋。
小德的急速。
小德的精通。
小德的攻击。
小德的卖萌。
这样写的问题,不仅是代码量很大,写着麻烦。重复的代码也太多了,好像一直在做无用功一样,三个职业的急速有什么区别?
突然有一天,暴雪宣布,急速这个属性的计算方法改变啦!那么我们需要修改三处蓝色的代码,维护起来很吃力。
如果我们使用了OOP,程序会变成这样:

定义类“玩家”{
  • 玩家的急速。
  • 玩家的精通
  • 玩家的攻击。
}
定义类“盗贼”:继承自“玩家”{
  • 盗贼的爆菊。
}
定义类“战士”:继承自“玩家”{
  • 战士的冲锋。
}
定义类“小德”:继承自“玩家”{
  • 小德的卖萌。
}

这样代码量明显减少了,“急速”“精通”“攻击”这些三职业共性都只有一份代码。没有重复代码。
暴雪说修改急速计算方法,我们对应需要修改的蓝色代码也只有一处。维护方便。

假设现在我们要定义战士的三个输出技能:致死打击、压制、猛击。
如果不使用OOP,我们要这么写:

致死打击{
  • 如果不是武器战,退出。
  • 如果没有武器,退出。
  • 如果致死打击的冷却时间未结束,退出。
  • 如果公共冷却未结束,退出。
  • 计算基础伤害、加成、暴击加成和护甲减免。
  • 计算暴击、躲闪、未命中的概率。
  • Roll点。
  • 是否未命中?
      • 什么也不做。
      • 是否躲闪?
          • 触发一层血之气息。
          • 产生10怒气。
          • 施加重伤。
          • 施加致死之伤。
          • 触发两层血之气息。
          • 是否暴击?
              • 计算暴击伤害并施加。
              • 计划在0.5秒后触发激怒
              • 计算非暴击伤害并施加。
          • 尝试触发横扫和精通。
  • 致死打击进入冷却。
  • 触发公共冷却。
}
压制{
  • 如果不是武器战,退出。
  • 如果没有武器,退出。
  • 如果怒气不足,退出。
  • 如果没有血之气息,退出。
  • 如果公共冷却未结束,退出。
  • 消耗10怒气。
  • 消耗一层血之气息。
  • 计算基础伤害、加成、暴击加成和护甲减免。
  • 计算暴击、未命中的概率。
  • 暴击率增加60%。
  • Roll点。
  • 是否未命中?
      • 返还80%的怒气消耗。
      • 是否暴击?
          • 计算暴击伤害并施加。
          • 计算非暴击伤害并施加。
      • 尝试触发横扫和精通。
  • 触发公共冷却。
}
猛击{
  • 如果不是武器战,退出。
  • 如果没有武器,退出。
  • 如果怒气不足,退出。
  • 如果公共冷却未结束,退出。
  • 消耗25怒气。
  • 计算基础伤害、加成、暴击加成和护甲减免。
  • 计算暴击、躲闪、未命中的概率。
  • Roll点。
  • 是否未命中?
      • 返还80%的怒气消耗。
      • 是否躲闪?
          • 触发一层血之气息。
          • 返还80%的怒气消耗。
          • 是否暴击?
              • 计算暴击伤害并施加。
              • 计算非暴击伤害并施加。
          • 尝试触发横扫和精通
          • 是否开启了横扫?
              • 触发2码AOE效果。
              • 什么也不做
  • 触发公共冷却。
}

你妹啊,这只不过刚写了三个技能,而且还只描述了技能的主要效果,这代码就已经长到令人兴致索然。
一个职业需要模拟的技能恐怕要达到20个吧!
所以我们需要将它们的共性抽取出来,使用继承来解决代码冗长的问题。

战士技能类{

  • 属性1:伤害。
  • 属性2:怒气消耗。
  • 属性3:怒气获取。
  • 属性4:冷却。
  • 属性5:公共冷却。
  • 属性6:要求天赋。
  • 属性7:要求武器。
  • 方法1:计算基础伤害。
  • 方法2:计算暴击率。
  • 方法3:计算躲闪率。
  • 方法4:计算未命中率。
  • 方法5:计算暴击加成。
  • 方法6:计算伤害加成。
  • 方法7:击中触发。
  • 方法8:暴击触发。
  • 方法9:执行{
    • 检查公冷
    • 检查怒气
    • 检查冷却
    • 检查天赋
    • 检查武器
    • 消耗怒气
    • 触发公冷
    • 触发技能冷却
    • 计算未命中率
    • 计算躲闪率
    • 计算暴击率
    • Roll点。
    • 是否未命中?
        • 返还80%的怒气消耗。
        • 是否躲闪?
            • 触发一层血之气息。
            • 返还80%的怒气消耗。
            • 获取怒气。
            • 是否暴击?
                • 计算暴击伤害并施加。
                • 执行方法8:“暴击触发”
                • 计算非暴击伤害并施加。
            • 执行方法7:“击中触发”
            • 尝试触发横扫和精通。
    }

}
致死打击类:继承自“战士技能类”{

  • 构造方法:把自身属性设置好,耗怒0,获取怒气10,冷却6,公共冷却1.5,要求武器天赋和近战武器。
  • 覆写方法7:击中触发。触发致死之伤、重伤、血之气息。
  • 覆写方法8:暴击触发。计划在0.5秒后触发激怒。

}
压制类:继承自“战士技能类”{

  • 构造方法:把自身属性设置好,耗怒10,获取怒气0,冷却0,公共冷却1,要求武器天赋和近战武器。
  • 覆写方法2:计算暴击率{
    • 调用基类“战士技能类”的方法2:“计算暴击率”。
    • 暴击率额外增加60%。
    }
  • 覆写方法3:计算躲闪几率。躲闪几率恒为0。
  • 覆写方法9:执行{
    • 检查血之气息。
    • 调用基类“战士技能类”的方法9:“执行”。
    • 如果成功执行,消耗一层血之气息。
    }

}
猛击类:继承自“战士技能类”{

  • 构造方法:把自身属性设置好,耗怒25,获取怒气0,冷却0,公共冷却1.5,要求武器天赋和近战武器。
  • 覆写方法7:击中触发。如果开启了横扫,触发2码AOE效果。

}

这样每添加一个技能所需的代码量就只有区区几行!添加一个技能变得轻松加愉快!

它们从基类“战士技能类”继承来了一整段完善的技能攻击过程,只需覆写其中的某个或某几个部分来表现每个技能的个性,例如压制永不会被躲闪,“压制类”覆写了计算躲闪几率的方法,使其返回结果永远都为零。

我们现在只有三个技能,代码长度看起来区别不是很明显。当你写满20多个技能的时候,差距就显现出来了。

思考题
请你尝试模仿我的写法,在上面的代码中添加第四个技能“英勇打击”。
英勇打击消耗30怒气,不获取怒气,冷却1.5秒,没有公共冷却,要求近战武器,不限天赋。
如果目前战士身上有“最后通牒”效果,英勇打击吞噬这个效果,变得必暴且不消耗怒气。
如果战士双持单手武器,英勇打击造成1.4倍的伤害。
(提示:写完后请自行检查,如果战士身上有“最后通牒”效果而现有怒气不足30,应该可以顺利施放英勇打击,而且这个英勇打击暴击率为100%。)

答案:

英勇打击类:继承自“战士技能类”{
  • 构造方法:把自身属性设置好,耗怒30,获取怒气0,冷却1.5,公共冷却0,要求近战武器。
  • 覆写方法2:计算暴击率{
    • 检查是否有“最后通牒”效果。
        • 返回100%
      • 没有
        • 调用基类“战士技能类”的方法2:“计算暴击率”。
    }
  • 覆写方法6:计算伤害加成{
    • 调用基类“战士技能类”的方法6:“计算伤害加成”。
    • 检查是否双持单手武器。
        • 伤害加成再乘以1.4
        • 什么都不做
    }
  • 覆写方法9:执行{
    • 检查是否有“最后通牒”效果。
        • 设置属性2:怒气消耗为0。
      • 没有
        • 什么都不做
    • 调用基类“战士技能类”的方法9:“执行”。
    • 设置属性2:怒气消耗为30。
    • 消耗“最后通牒”效果。
    }
}
实际上,我们最后所描述的这种基于OOP的技能设计方法,与SimC目前所采用的方法几乎相同。
如果你想要修改SimC职业模块的代码,在其中添加或修改技能,而苦于看不懂代码的意思,这一块叙述应该对你非常有帮助。

 



相关报道:

[关闭] [返回顶部]


  返回首页 | 最新资讯 | 资源下载 | 魔兽图片 | 单机文档 | 技术攻略 | 玩家视频
备案号:蜀ICP备2024062380号-1
免责声明:本网站为热爱怀旧WOW的玩家们建立的魔兽世界资料网站,仅供交流和学习使用,非盈利和商用.如有侵权之处,请联系我们,我们会在24小时内确认删除侵权内容,谢谢合作。
Copyright © 2024 - 2024 WOWAII.COM Corporation, All Rights Reserved

机器人国度