当前位置:首页>>攻略文章>>正文
魔兽科普:模拟器组成原理(二)
2013-12-30 18:24:47 作者:fhsvengetta 来源:NGA 浏览次数:0
摘要:如果你觉得看不懂……那真的很抱歉,我已经写得非常通俗了,甚至已经给每一句贴进来的SimC源代码配上了注释,我觉得拿来当作C++入门教材都快够用了!


伤害类型的区分

我们将“机械伤害”与普通伤害区分开。虽然Bling只会造成机械伤害,但万一Bling某一天升级了,学会了造成其他种类伤害的技能呢?

我们需要给模拟器列举Blingdas世界中存在的所有伤害类型。在C++中,我们使用enum(枚举)来实现这一工作。

我们新建一个伤害类型枚举,按照习俗约定,将其命名为“damage_type_e”。

Code (c):

enum damage_type_e{ 

//这里列举伤害类型。 

};  //不要忘了加上最后这个分号。

 

由于Blingdas世界中只存在机械伤害和普通伤害,所以我们的枚举只需要列举两项,即“普通伤害DAMAGE_NORMAL”和“机械伤害DAMAGE_MECHANICAL”。

普通伤害用0表示,机械伤害用1表示。

 

Code (c):

enum damage_type_e{ 

    DAMAGE_NORMAL=0, 

    DAMAGE_MECHANICAL=1, 

    DAMAGE_TYPE_TOTAL=2, 

};  //千万不要忘了加上最后这个分号。

  

将这个DAMAGE_TYPE_TOTAL顺序加在枚举的末尾,你会发现它的值正好等于枚举中拥有的项目数量。我们以此获取Blingdas世界中伤害类型的总数。

如果未来Bling学会了电击,可以用2来表示“电击伤害”,扩展非常简单。

 

Code (c):

enum damage_type_e{ 

    DAMAGE_NORMAL=0, 

    DAMAGE_MECHANICAL=1, 

    DAMAGE_ELECTRIC=2, 

    DAMAGE_TYPE_TOTAL=3, 

};

枚举了三种伤害类型,DAMAGE_TYPE_TOTAL所代表的值自然而然也随着增加到了3。   

怪兽Kaiju的实现 

 

我们首先来描述怪兽Kaiju。如果你看过模拟器组成原理第一部,你会明白什么叫做“类”。这里我们将每一只怪兽之间的所有共性抽出来,作为一个“怪兽类”。

按照习俗约定,我们将怪兽类命名为“kaiju_t”。

 

Code (c):

class kaiju_t{ 

//这里写代码,描述kaiju所拥有的属性和方法。 

};    //小心分号。

 

属性和方法,都有私有和公有之分。私有属性和方法只有kaiju自己可以看得见,而公有属性和方法可以被任何人看见。

在C++中,这样区分私有和公有:

 

Code (c):

class kaiju_t{ 

private: 

//这里是私有的属性和方法 

public: 

//这里则是公有的 

};    //切记不要忘了最后这个分号。

 

 

在这个模拟器中,kaiju需要拥有多少种属性?

唔,kaiju有最大血量、当前剩余血量和免伤系数三个属性。我们分别将它们命名为“health_max”、“health”和“damage_reduction_coeff”。

 

Code (c):

class kaiju_t{ 

private: 

    double health_max; 

    double health; 

    static const double damage_reduction_coeff[DAMAGE_TYPE_TOTAL]; 

public: 

 

};    //灰谷第一原则:如果你不知道在一段代码的末尾该不该加分号,你就把它加上。

 

 

“double”是一种内置数据类型“双精度浮点数”。你可以把它简单理解为“可以带有小数点的数”。呃?什么?小数?血量不是整数吗?其实,魔兽世界里血量可以是小数。在Blingdas中也一样。

“static”意思是静态的,“const”意思是常量。在Blingdas中,每只怪兽对各种类型的伤害减免系数都一样,所以我们使用“static”;怪兽对各种类型的伤害减免系数不会变化,所以我们使用“const”。

如果你将某个量声明为static,编译器会在编译期为它划分好内存空间。这意味着无论你创造kaiju_t类的多少个实例,它占用的内存空间也总是只有一份,所有的kaiju_t实例都将共享同一个值:一个kaiju将它的值改变了,其他所有的kaiju都会受到影响。

如果你将某个量声明为const,任何尝试改变其值的行为都会被阻止。也就是说,带有const属性的量,其取值在整个运行期间永远不会改变(除非你刻意想办法钻空子去改变它)。

在一个量的名字后面加上中括号“[]”,可以将其声明为数组。中括号里的值,就是数组内包含的成员的数目。

这里DAMAGE_TYPE_TOTAL正好就是Blingdas世界中伤害类型的数目,所以每一种伤害类型都可以对应damage_reduction_coeff中的一个值。

由于damage_reduction_coeff是一个常量,所以我们必须为它赋初值。

 

Code (c):

class kaiju_t{ 

private: 

    double health_max; 

    double health; 

    static const double damage_reduction_coeff[DAMAGE_TYPE_TOTAL]; 

public: 

 

}; 

 

//对单个量赋初值,形式类似: 

//    const double some_constant = 0.7; 

//对数组赋初值,只需用大括号括起来,然后依次列出初值,用逗号分隔。 

//在类中已经定义为static,在这里就不需要static了。 

const double kaiju_t::damage_reduction_coeff[] = { 

    1.0,     //DAMAGE_NORMAL 

    0.7,     //DAMAGE_MECHANICAL 

    1.3,     //DAMAGE_ELECTRIC 

};    //这也需要分号。

 

 

由于某些编译器不允许在类的声明中给常量赋初值,所以我们将它移到了类声明的下方。

在类声明之外,需要使用“类名::”缀在名字之前,以表示这是来自类中的成员。例如这里使用kaiju_t::damage_reduction_coeff,表示这是来自“kaiju_t”类中的成员“damage_reduction_coeff”。

 

 

 

可能会有人有疑问,血量怎么会是私有属性呢?难道血量只有Kaiju自己才能看得见,不许Bling看吗?

这里,将血量声明为私有,并不是为了防止Bling看见它,而是要防止Bling修改它。

如果我们将血量声明为公有,Bling不仅可以看见它,还能随意修改它。想象一下你在玩游戏的时候可以随意修改敌人的现有血量……那可真是一件糟糕的事情。

 

Code (c):

//这是一段可怕的代码,不要把它加入你的模拟器中! 

void bling_t::cheat() {  //Bling要作弊了! 

    if (enemy.health>0)    //如果敌人还活着…… 

        enemy.health=0;    //直接将其秒杀!哈哈哈! 

}

 

 

为了阻止上面这段代码真的生效,我们必须将血量设置为私有,别无选择。

要让Bling能够获知敌人剩余血量,我们通过一个方法来实现。

 

Code (c):

class kaiju_t{ 

private: 

    double health_max; 

    double health; 

    static const double damage_reduction_coeff[DAMAGE_TYPE_TOTAL]; 

public: 

    double current_health() const {  //公有方法,所以Bling可以调用它。 

        return health;  //告诉调用者这只Kaiju现在还剩多少血量。 

    } 

};

 

 

current_health方法被声明为const,所以它承诺不会对Kaiju的任何属性造成任何改动。

现在任由Bling如何调用这个方法,Bling都无法随意修改Kaiju的剩余血量了。

 

Code (c):

//这是一段可怕的代码,但是它已经被阻止了。 

void bling_t::cheat() {  //Bling要作弊了! 

    if (enemy.current_health()>0)    //如果敌人还活着…… 

        enemy.current_health()=0;    //直接将其秒杀…… 

}

 

 

这个Bling的cheat()方法仍然可以执行,但是不会造成任何实际效果。

 

请千万小心!将方法声明为const并不代表着一切都安全了,因为那只是Kaiju自己承诺不去做任何改动,而Bling没有对此事做出任何承诺。

假如我们将current_health()方法的返回类型由“double”改为“double&”:

 

Code (c):

double& kaiju_t::current_health() const {  //现在它返回的是一个“引用”而不是值。 

    return health;  //告诉调用者这只Kaiju现在还剩多少血量。 

void bling_t::cheat() {  //Bling要作弊了! 

    if (enemy.current_health()>0)    //如果敌人还活着…… 

        enemy.current_health()=0;    //直接将其秒杀!哈哈哈! 

}

 

 

“引用”类型的返回,将本应是Kaiju才能看见的私有属性health,引用给了Bling,导致了私有属性发生对外泄露。

现在Bling意外获得了对私有属性health的控制权。这个cheat()方法又可以将怪兽秒杀掉了。

如果因为某些原因,你必须使用引用类型的返回,那么在返回类型前面也加上“const”,以明确阻止Bling的犯规行为。

 

Code (c):

const double& kaiju_t::current_health() const {  //现在它返回的是一个不可改动的引用。 

    return health;  //告诉调用者这只Kaiju现在还剩多少血量。 

}

 

 

 

Kaiju除了会告诉别人它所剩的血量外,还会承受伤害。

所以“承受伤害”也是Kaiju的方法之一,虽然听起来感觉很苦逼。

 

Code (c):

class kaiju_t{ 

private: 

    double health_max; 

    double health; 

    static const double damage_reduction_coeff[DAMAGE_TYPE_TOTAL]; 

public: 

    double current_health() const { 

        return health; 

    } 

    void take_damage(double damage, damage_type_e type){  //承受伤害,需要告知伤害的大小和类型。 

        health -= damage * damage_reduction_coeff[type];  //根据类型不同伤害减免也不同,所以乘上不同的减免系数,再从当前生命值中扣除。

     } 

};

 

 

“-=”符号就是“扣除”的意思啦。

 

这写起来很简单对吧。

我们最后需要一个构造函数,一个析构函数。

构造函数是在每只Kaiju被实例化(你可以理解为被“创造”出来)时自动调用的函数。而析构函数则是每只Kaiju被销毁(你可以理解为“戏份结束谢幕”)时自动调用的函数。

如果你不写,编译器就会替你写一个标准形式的。

这里析构函数我们可以让编译器代劳,但是我们需要自己来写构造函数。

 

Code (c):

class kaiju_t{ 

private: 

    double health_max; 

    double health; 

    static const double damage_reduction_coeff[DAMAGE_TYPE_TOTAL]; 

public: 

    double current_health() const { 

        return health; 

    } 

    void take_damage(double damage, damage_type_e type){ 

        health -= damage * damage_reduction_coeff[type]; 

    } 

    kaiju_t(double h) :  //构造函数的名字就是类名,没有返回。 

        health_max(h),   //冒号后面花括号前面的内容称为“成员初值列”, 

        health(h)            //你可以在此设定任意属性的值。 

        {}                        //这里构造函数只需要用到成员初值列,函数本体没有任何内容。 

};

 

 

另外,C++有一个特性称为“函数重载”。我们可以将同名函数定义多个不同的形态,他们可以共存。

比如我们刚刚定义了一个带有参数“h”的构造函数,我们接下来还可以再定义一个不带任何输入参数的构造函数,以备懒得提供参数时使用。

 

Code (c):

static const double health_max_default = 100000.0; //定义一个静态常量“默认最大生命值”。 

 

class kaiju_t{ 

private: 

    double health_max; 

    double health; 

    static const double damage_reduction_coeff[DAMAGE_TYPE_TOTAL]; 

public: 

    double current_health() const { 

        return health; 

    } 

    void take_damage(double damage, damage_type_e type){ 

        health -= damage * damage_reduction_coeff[type]; 

    } 

    kaiju_t(double h) : health_max(h), health(h) {} //刚才定义的构造函数。 

    kaiju_t() : health_max(health_max_default), health(health_max_default) {} //新的重载,不提供参数则使用默认值。

 };

 

 

同样,对于普通函数,也可以进行重载。

例如我们写一个respawn方法,在不销毁不重新构建的情况下将其恢复到初始状态。

 

Code (c):

static const double health_max_default = 100000.0; 

 

class kaiju_t{ 

private: 

    double health_max; 

    double health; 

    static const double damage_reduction_coeff[DAMAGE_TYPE_TOTAL]; 

public: 

    double current_health() const { 

        return health; 

    } 

    void take_damage(double damage, damage_type_e type){ 

        health -= damage * damage_reduction_coeff[type]; 

    } 

    kaiju_t(double h) : health_max(h), health(h) {} 

    kaiju_t() : health_max(health_max_default), health(health_max_default) {} 

    void respawn(){  //不带参数的respawn 

        health_max=health_max_default; 

        health=health_max_default; 

    } 

    void respawn(double h){  //带参数的respawn 

        health_max=h; 

        health=h; 

    } 

};

 

 

这样我们就已经实现了Kaiju怪兽类所需的全部内容。 


制造一个怪兽作为敌人 

 

现在我们有了怪兽类,却没有怪兽实例。要使用这个怪兽类,我们需要将它实例化。

 

Code (c):

kaiju_t& enemy(){   //通过这个函数获取你的敌人。 

    static kaiju_t knifehead;    //一个静态的、使用无参数构造函数构造的怪兽对象:Knifehead。 

    return knifehead;  //将指向Knifehead的引用返回给调用者。 

}

 

 

这个函数内部有一个被声明为static的怪兽对象Knifehead。

static确保了这个函数无论何时被调用,返回的都是指向同一只怪兽的引用。整个程序运行开始到结束,这只Knifehead既不会被销毁也不会被重复构造。

这样我们就制造了一只怪兽实例,并且可以在程序中随时随地调用它获取指向这只怪兽的引用,通过引用对它进行任意的操作。

这种手法在设计模式中被称为“Singleton模式”或者“单例模式”。如果你需要构造一个永不销毁、唯一、随时随地可以调用的对象,使用这种手法会非常方便。

 


小结 

 

如果你一直跟着我在做,现在你手里的代码应该像这样:

 

Code (c):

 

enum damage_type_e{ 

    DAMAGE_NORMAL=0, 

    DAMAGE_MECHANICAL=1, 

    DAMAGE_ELECTRIC=2, 

    DAMAGE_TYPE_TOTAL=3, 

}; 

 

static const double health_max_default = 100000.0; 

 

class kaiju_t{ 

private: 

    double health_max; 

    double health; 

    static const double damage_reduction_coeff[DAMAGE_TYPE_TOTAL]; 

public: 

    double current_health() const { 

        return health; 

    } 

    void take_damage(double damage, damage_type_e type){ 

        health -= damage * damage_reduction_coeff[type]; 

    } 

    kaiju_t(double h) : health_max(h), health(h) {} 

    kaiju_t() : health_max(health_max_default), health(health_max_default) {} 

    void respawn(){ 

        health_max=health_max_default; 

        health=health_max_default; 

    } 

    void respawn(double h){ 

        health_max=h; 

        health=h; 

    } 

}; 

 

const double kaiju_t::damage_reduction_coeff[] = { 

    1.0,     //DAMAGE_NORMAL 

    0.7,     //DAMAGE_MECHANICAL 

    1.3,     //DAMAGE_ELECTRIC 

}; 

 

kaiju_t& enemy(){ 

    static kaiju_t knifehead; 

    return knifehead; 

}

 



相关报道:

[关闭] [返回顶部]


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

机器人国度