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


机器人Bling的实现 

 

下面我们要来制作机器人Bling。很显然,由于Bling可以有各种不同的属性取值,我们处理方法与Kaiju一样,先将所有Bling的共性抽象成一个“机器人类”。

依据习俗约定,我们将机器人类命名为“bling_t”。

 

Code (c):

class bling_t{ 

};

 

 

有了刚才制作怪兽类的经验,我们很快就能写出bling_t的大体框架。

 

Code (c):

class bling_t{ 

private: 

    double crit;    //暴击属性 

    double strength;    //力量属性 

    double power;    //当前剩余能量 

    static const double power_max;    //能量最大值,是静态常数。 

 

public: 

    bling_t(double str, double c) : crit(c), strength(str), power(power_max){}  //输入属性(力量和暴击)来构造一只Bling。

 

}; 

 

const double bling_t::power_max = 100.0;    //给能量最大值赋初值。

 

 

公共冷却是与每一只Bling相关的,它也应该属于机器人类的一个属性。

我们如何表达公共冷却?记录公共冷却的结束时刻就可以了。我们对公共冷却何时起始不感兴趣,我们只关心它何时结束以便使用下一个技能。

时间类型time_t在C语言头文件“time.h”中有定义,它们将time_t定义为了内置类型long。我们不妨使用它。

 

Code (c):

#include <time.h>  //从time.h头文件中获取time_t类型的定义。 

class bling_t{ 

private: 

    double crit; 

    double strength; 

    double power; 

    static const double power_max; 

    time_t gcd_expire;    //使用time_t类型记录公共冷却的结束时刻,我们以毫秒为单位。 

public: 

    bling_t(double str, double c) : crit(c), strength(str), power(power_max), gcd_expire(0) {} //别忘了将它也加入成员初值列一起初始化。

 

}; 

 

const double bling_t::power_max = 100.0;

 

 

我们记录了公共冷却的结束时间,那么如何表示当前时间?

同样,你也可以使用Singleton设计模式,建立一个函数,每次调用返回一个指向时间的引用。

这里time_t的定义并不复杂,它只是一个整数而已,所以如果你直接使用一个全局变量来表示它,也是非常不错的。

我们还是使用Singleton来表示,因为这种手法可扩展,有保障,在time_t稍复杂一些的模拟器中还应该使用它。

使用Singleton还有一个好处是,即使是对于基本类型,它也能保证被正确赋予初值。被声明为static的时间,无需显式赋初值,在程序启动时会自动初始化为0。

如果这里你使用非static的变量来表示,不要忘了给它赋初值。

 

Code (c):

#include <time.h> 

 

time_t& now(){  //一个Singleton函数, 

    static time_t t;   //包含了一个静态的时间, 

    return t;   //每次调用now(),就返回指向它的引用。 

 

class bling_t{ 

private: 

    double crit; 

    double strength; 

    double power; 

    static const double power_max; 

    time_t gcd_expire; 

public: 

    bool is_gcd_expired() const{    //一个成员函数,判断公共冷却是否已经结束。 

        return gcd_expire<=now();  //就这样判断。 

    } 

    void trigger_gcd(time_t duration){  //另一个成员函数,触发长度为duration的公共冷却。 

        gcd_expire=now()+duration;   //记录公共冷却的结束时刻,同样使用到了now()。 

    } 

 

    bling_t(double str, double c) : crit(c), strength(str), power(power_max), gcd_expire(0){}

 }; 

 

const double bling_t::power_max = 100.0;

 

  


另外,我们需要添加一系列的成员方法,供后面设计技能使用。包括返回各项属性值、消耗/恢复能量等等。

 

Code (c):

class bling_t{ 

private: 

    double crit; 

    double strength; 

    double power; 

    static const double power_max; 

    time_t gcd_expire; 

public: 

    double get_crit() const{  //返回暴击 

        return crit; 

    } 

    double get_str() const{  //返回力量 

        return strength; 

    } 

    bool has_such_power(double p) const{  //是否有足够的能量 

        return power>=p; 

    } 

    bool is_gcd_expired() const{  //公冷是否结束 

        return gcd_expire<=now(); 

    } 

    void consume_power(double p){  //消耗能量 

        power-=p; 

        if (power<0) power=0;  //避免出现负能量。 

    } 

    void resume_power(double p){  //恢复能量 

        power+=p; 

        if (power>power_max) power=power_max;  //不能超过最大值。 

    } 

    void trigger_gcd(time_t duration){  //触发公冷 

        gcd_expire=now()+duration; 

    } 

 

    bling_t(double str, double c) : crit(c), strength(str), power(power_max), gcd_expire(0){}

 };

  

机器人类大体就这样了。

现在我们试试描述Bling的两个技能。

在第一部里我们已经详细描述了使用“继承”来描绘技能的方式,这里我们就使用它。

首先着手新建一个技能类“bling_spell_t”。技能类需要描绘出一个最平凡的技能是如何工作的。

 

Code (c):

class bling_spell_t{  //布林技能类 

protected:  //不同于“公有”和“私有”,这里使用一种特殊的类型,称为“保护”。 

    bling_t* bling_ptr;  //一个指针,通过它访问技能的持有者Bling。 

private:    //私有属性 

    time_t cd;    //冷却长度 

    time_t gcd;   //公共冷却长度 

    time_t cd_expire;    //记录这个技能的冷却结束时间。 

    double power_consume;    //消耗的能量 

    double power_resume;    //恢复的能量 

public: 

    bling_spell_t(bling_t* ptr, time_t _cd, time_t _gcd, double _power_consume, double _power_resume) :  //构造函数

         bling_ptr(ptr),    //要想构造一个布林的技能,你必须提供所有这些属性的值。 

        cd(_cd),    //包括:一个指向布林的指针“bling_ptr”,技能的冷却长度“cd”,公共冷却长度“gcd”, 

        gcd(_gcd),    //技能消耗和恢复的能量“power_consume”和“power_resume”。 

        cd_expire(0),   //另外,初始时刻技能处于冷却完成状态,将cd_expire初始化为0 

        power_consume(_power_consume), 

        power_resume(_power_resume) 

        {}; 

 

    virtual bool execute(){    //方法:执行 

        if (gcd>0&&!bling_ptr->is_gcd_expired()) return false;   //检查公共冷却 

        if (cd>0&&cd_expire>now()) return false;    //检查冷却 

        if (power_consume>0&&!bling_ptr->has_such_power(power_consume)) return false;    //检查能量

         if (gcd>0) bling_ptr->trigger_gcd(gcd);   //如果都过关,继续执行。触发公共冷却。 

        if (cd>0) cd_expire=now()+cd;    //触发冷却。 

        if (power_consume>0) bling_ptr->consume_power(power_consume);    //消耗能量 

        if (power_resume>0) bling_ptr->resume_power(power_resume);    //恢复能量 

        return true;  //返回执行成功。 

    }; 

};

 

 

我们见到了一个新鲜名词“protected”。对,在C++中,除了私有和公有之外,还有一种成员类型叫做“保护”。保护是介于公有和私有之间的一种类型,它可以被自身所处的类所看见,可以被它的派生类所看见,但不能被派生类以外的位置看见。一个表格可以说明得更清楚:
\

 

由于我们的派生类也需要访问这个成员“bling_ptr”,通过它才能找到使用此技能的布林是哪一只。private将权限收得太紧,public又将权限放得太松,所以我们选择protected。

 

将一个方法声明为virtual,意为“虚函数”,它可以被派生类继承,也可以被派生类所覆写。

这里execute()方法就是一个虚函数。如果某个技能继承自“bling_spell_t”,那么这个技能会自动获得一份execute方法的默认实现,也就是我们上面所写的那些“检查公冷、检查冷却、……”;如果该技能决定自己写一份实现,那么它可以覆写这段代码,程序会转而采用它所写的代码。

 

我们采用的是第一部中提到的“继承”设计法。由于这个基类“bling_spell_t”需要被各个技能类“继承”,各个技能类对同一种方法可以有不同的实现,我们说“bling_spell_t”是多态的。

什么叫多态?举个例子。猫头鹰是一种鸟类,企鹅也是一种鸟类,我们把猫头鹰和企鹅都叫做鸟类的派生类,把鸟类叫做它们的基类。

如果我提到“一只鸟”,那么你就明白,我可能说的是一只猫头鹰,也有可能说的是一只企鹅。

猫头鹰会孵蛋,企鹅也会孵蛋。但是猫头鹰由雌性孵蛋,企鹅是由雄性孵蛋的。“一只鸟”有至少两种可能的孵蛋方法,鸟类所具有的这种性质叫做多态。

这里,“打脸.EXE类”将会是“布林技能类”的派生类,“蓝屏类”也将会是“布林技能类”的派生类。它们的执行方法各有不同,所以我们说“布林技能类”是多态的。

简单的多态判定,就是看一个基类中是否有虚函数成员。

为什么要突然插入这么一段话来讲解什么叫多态?因为这里涉及到一项原则:如果一个基类是多态的,你必须声明一个virtual析构函数。

 

Code (c):

class bling_spell_t{ 

protected: 

    bling_t* bling_ptr; 

private: 

    time_t cd; 

    time_t gcd; 

    time_t cd_expire; 

    double power_consume; 

    double power_resume; 

public: 

    bling_spell_t(bling_t* ptr, time_t _cd, time_t _gcd, double _power_consume, double _power_resume) :

         bling_ptr(ptr), 

        cd(_cd), 

        gcd(_gcd), 

        cd_expire(0), 

        power_consume(_power_consume), 

        power_resume(_power_resume) 

        {}; 

 

    virtual bool execute(){  //有虚函数成员,所以是多态的。 

        if (gcd>0&&!bling_ptr->is_gcd_expired()) return false; 

        if (cd>0&&cd_expire>now()) return false; 

        if (power_consume>0&&!bling_ptr->has_such_power(power_consume)) return false;

         if (gcd>0) bling_ptr->trigger_gcd(gcd); 

        if (cd>0) cd_expire=now()+cd; 

        if (power_consume>0) bling_ptr->consume_power(power_consume); 

        if (power_resume>0) bling_ptr->resume_power(power_resume); 

        return true; 

    }; 

 

    virtual ~bling_spell_t(){};  //这就叫virtual析构函数。没什么必须delete掉的成员所以具体实现是空的,但必须声明一下让它是virtual才行。

 };

 

 

 

基类写好,我们开始写派生类。我们先挑软柿子捏,蓝屏这个技能看起来简单一些,我们把它实现。

 

Code (c):

class bsod_t : public bling_spell_t{  //后边这个public是什么意思不用管,只要知道这个冒号加public是“继承自”的意思就行了。

 public: 

    bsod_t(bling_t* ptr) : bling_spell_t(ptr, 6000, 1500, 0, 50) {}  //构造函数,接受一个指向布林的指针,然后把属性设置好。

 };  //呃,就完了。

 

 

真的就结束了……恢复能量已经在基类中实现,我们只要提供恢复的具体数值“50”就可以了。

另一个技能是打脸。

 

Code (c):

class smackthat_dot_exe_t : public bling_spell_t{ 

public: 

    smackthat_dot_exe_t(bling_t* ptr) : bling_spell_t(ptr, 0, 1500, 30, 0) {}  //和蓝屏一样的构造函数,就是改改数字。

     virtual bool execute(){  //覆写虚函数“execute” 

        if (!bling_spell_t::execute()) return false;   //条件检查都在基类的“execute”中,这里调用一下

         //执行基类提供的“execute”之后,还不够, 

        double d = 236.0;    //计算伤害,基础伤害236 

        d += bling_ptr->get_str();    //每点力量增加1点伤害 

        double c = 0.0;    //还有可能会暴击,基础暴击率0% 

        c += bling_ptr->get_crit();    //再加上暴击属性 

        if ( static_cast<double>(rand())/RAND_MAX < c )    //进行一次Roll点,如果Roll出暴击来,

             d *= 2.0;    //伤害就翻倍。 

        enemy().take_damage(d,DAMAGE_MECHANICAL);    //对敌人造成这么多的机械伤害。 

        return true; //返回执行成功。 

    }; 

};

 

 

“static_cast<double>(rand())/RAND_MAX”在上一部讲到随机数发生器的时候提到了。rand产生的是随机整数,我们这样把它转换成小数。

 

调整代码顺序

要使用rand(),你需要将C函数库头文件“stdlib.h”包含进来,rand()在其中有定义。

在代码的顶端写上:

 

Code (c):

#include <stdlib.h>

 

 

呃,刚才是不是包含过一个“time.h”?

把它也挪到顶端去,两个头文件写在一起比较好看。

 

Code (c):

#include <stdlib.h> 

#include <time.h> 

 

//后边是代码

 

 

在C++中,你在使用一个东西之前,必须能够在其上文找到这个东西的声明式或者定义式。

 

Code (c):

//这是一个例子。 

class some_class_t;  //这是一个类的声明式,但只声明了类本身,没有声明其中的任何成员。 

some_class_t some_object;  //这是一个对象的定义式,其中使用了some_class_t,而且前面已经有了some_class_t的声明,所以没问题。

some_object.some_method();  //尝试使用对象some_object的方法some_method(),前者能够找到定义式,没问题; 

                                      //但是后者既找不到定义式也找不到声明式,不行。

 

 

其他代码都好办,我们依次顺序写下来的。但是在布林这里出现了一些问题。

施放技能需要能够获知布林的属性,而布林本身又需要能够使用技能。这是一个环状依赖,无论把技能和布林两者中的哪一个放在前面,都无法通过编译。

这时,我们采用“内部类”的办法来解决。我们将“技能”的所有代码放置在“布林”类的内部。形如:

 

Code (c):

 

class bling_t{  //外层是布林。 

private: 

    double power;  //布林的私有量 

public: 

    class bling_spell_t{  //内层是技能。 

 

    }; 

 

    bling_spell_t smackthat;  //布林和技能的关系:“布林有技能”。 

    bling_spell_t bsod; 

    bling_spell_t some_other_abilities;     

};

 

 

内部类并不比其他普普通通的类具有更多的访问权限,所以内层的bling_spell_t仍然无法访问布林的私有量“power”(即使在某些编译器中能访问,也请千万不要尝试使用它)。

只是现在我们解决了循环依赖的问题。

 

布林和技能之间的关系叫做“布林有技能”,我们使用复合来描绘这种关系。

所谓复合,就是在一个类中,包含了另一个类的实例作为其成员。例如这里,布林类包含了技能类的三个实例作为成员:smackthat、bsod、some_other_abilities。

调整后的bling_t应该是这样:

 

Code (c):

class bling_t{ 

private: 

    double crit; 

    double strength; 

    double power; 

    static const double power_max; 

    time_t gcd_expire; 

public: 

    double get_crit() const{  //各种方法在先,因为技能需要使用它们。 

        return crit; 

    } 

    double get_str() const{ 

        return strength; 

    } 

    bool has_such_power(double p) const{ 

        return power>=p; 

    } 

    bool is_gcd_expired() const{ 

        return gcd_expire<=now(); 

    } 

    void consume_power(double p){ 

        power-=p; 

        if (power<0) power=0; 

    } 

    void resume_power(double p){ 

        power+=p; 

        if (power>power_max) power=power_max; 

    } 

    void trigger_gcd(time_t duration){ 

        gcd_expire=now()+duration; 

    } 

 

    class bling_spell_t{   //然后定义技能类 

    protected: 

        bling_t* bling_ptr; 

    private: 

        time_t cd; 

        time_t gcd; 

        time_t cd_expire; 

        double power_consume; 

        double power_resume; 

    public: 

        bling_spell_t(bling_t* ptr, time_t _cd, time_t _gcd, double _power_consume, double _power_resume) :

             bling_ptr(ptr), 

            cd(_cd), 

            gcd(_gcd), 

            cd_expire(0), 

            power_consume(_power_consume), 

            power_resume(_power_resume) 

            {}; 

        virtual ~bling_spell_t(){}; 

        virtual bool execute(){ 

            if (gcd>0&&!bling_ptr->is_gcd_expired()) return false; 

            if (cd>0&&cd_expire>now()) return false; 

            if (power_consume>0&&!bling_ptr->has_such_power(power_consume)) return false;

             if (gcd>0) bling_ptr->trigger_gcd(gcd); 

            if (cd>0) cd_expire=now()+cd; 

            if (power_consume>0) bling_ptr->consume_power(power_consume); 

            if (power_resume>0) bling_ptr->resume_power(power_resume); 

            return true; 

        }; 

    }; 

 

    class bsod_t : public bling_spell_t{  //技能类的派生类 

    public: 

        bsod_t(bling_t* ptr) : bling_spell_t(ptr, 6000, 1500, 0, 50) {} 

    }; 

 

    class smackthat_dot_exe_t : public bling_spell_t{  //派生类 

    public: 

        smackthat_dot_exe_t(bling_t* ptr) : bling_spell_t(ptr, 0, 1500, 30, 0) {}

         virtual bool execute(){ 

            if (!bling_spell_t::execute()) return false; 

 

            double d = 236.0; 

            d += bling_ptr->get_str(); 

            double c = 0.0; 

            c += bling_ptr->get_crit(); 

            if ( static_cast<double>(rand())/RAND_MAX < c ) 

                d *= 2.0; 

            enemy().take_damage(d,DAMAGE_MECHANICAL); 

            return true; 

        }; 

    }; 

 

    bling_spell_t* smackthat_dot_exe;  //布林有两个技能, 

    bling_spell_t* bsod;                       //我们使用指针类型,是为了方便在不销毁布林的情况下将技能状态重置。

 

    bling_t(double str, double c) : 

        crit(c), 

        strength(str), 

        power(power_max), 

        gcd_expire(0){ 

            smackthat_dot_exe = new smackthat_dot_exe_t(this);  //在构造布林时,将两个技能也构造好。

             bsod = new bsod_t(this); 

        } 

    ~bling_t(){ 

        delete smackthat_dot_exe;  //在销毁布林时,将两个技能也销毁掉。 

        delete bsod; 

    } 

}; 

 

我们使用new来构造一个实例,但千万记住一个原则:每次使用new,都必须有一个delete与之对应。

所有对象都有生命周期,有生,就有死;有构造,就有销毁;有new,就有delete。

如果你只负责不断地new、new、new,从来也不管delete,就会有许多本应被销毁掉的对象未被正确销毁,而积压在内存里。你会发现程序的内存占用一直在上涨、上涨、上涨……

这种现象称为“内存泄漏(memory leak)”,对一个模拟器来说,少量的内存泄漏或许影响并不大,但对一个需要长时间保持运行的系统而言,稍有内存泄漏,最终结果就将是内存被积压至满而崩溃。泄漏量大小都一样,只要泄漏了,剩下的就是崩溃时间早晚的问题。

为什么系统使用时间长了性能会下降,需要重启一下才能恢复,其实就是这个道理。你们如果在用大脚插件,有兴趣可以在游戏里观察一下大脚基本库“BigFoot”的内存占用,这就属于典型的内存泄漏,而且泄漏量还不小。在WoW中输入这个命令:

 

Code (c):

/run collectgarbage("collect");

 

 

这是lua语言的显示内存回收命令。使用它,lua可以找到大部分本应被销毁、但未被正确销毁的僵尸对象,然后将它们从内存中释放掉。你会发现每次大脚的内存占用升高了,使用这个命令,它的内存占用就会突然下降一截。大脚会在某些情况下自动调用它来清理自己泄漏掉的内存。

 

与怪兽一样,我们给布林也增加一个重置状态的方法respawn。

 

Code (c):

void respawn(){  //无参数的respawn 

    delete smackthat_dot_exe;  //销毁旧技能 

    delete bsod; 

    power=power_max;   //重设能量为满 

    gcd_expire=0;   //重设公冷完成时间为0 

    smackthat_dot_exe = new smackthat_dot_exe_t(this);   //重新构建新技能 

    bsod = new bsod_t(this); 

void respawn(double str, double c){  //有参数的respawn重载 

    respawn();  //除了执行无参数respawn的工作以外, 

    strength=str;  //将力量和暴击设置为新值。 

    crit=c; 

}

 

 


制作一个布林作为模拟主人公 

 

与怪兽一样,布林也需要实例化成为一个可用的对象。

我们这里仍然使用Singleton函数完成实例化。

 

Code (c):

bling_t& bling(){ 

    static bling_t blington3k(500.0, 0.3);//布林顿3000,默认带有500力量和30%暴击。 

    return blington3k; 

}





相关报道:

[关闭] [返回顶部]


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

机器人国度