您的位置:首页 > 其它

将设计模式运用于游戏设计:工厂方法

2011-11-17 16:22 253 查看
网络游戏——延长网络游戏生命周期的方法是,更新,不断地更新,不断的将新内容呈现于玩家面前。这就要求游戏程序的设计要有弹性,哪怕是游戏逻辑部分最好也能够重用,这样既方便扩展也可以缩短未来项目的开发周期。其中要遵循的一个设计原则就是open-close原则,对既定逻辑封闭对游戏内容的扩展开放。下面用一个具体的应用说明这一观点。

 

问题描述:

在一般的角色扮演游戏中都有很多不同角色让玩家选择,这些不同角色对属性的依赖以及攻击力的计算公式各有不同。例如刺客主要属性是敏捷,法师主要依赖属性是智力。这就会产生一个问题,每当在游戏系统中新加入一个角色,那么就会有一套新的属性以及攻击力的计算公式产生。注意:这种添加是平行的,新加一个角色就会有新的攻击计算公式产生。

 

那么如何才能做到更新内容而不改变原有逻辑呢?

1:把攻击力计算逻辑抽象成有着统一的接口类(CAttackBase),所有职业的攻击力全部继承自此类。

2:把所有职业也都抽象为一个类(CNature),所有的具体职业都有此类继承出来,此类不用理会攻击力如何计算,但是他有一个计算攻击力的接口(Cnature::DoNormalAttack,Cnature::
DoSkillAttack)。

3:让具体的职业去挑选适合自己的攻击力计算公式类型(Cnature::AttackFactoryMethod() = 0)。

 

下面贴出具体代码

#include <stdio.h>

#include <stdlib.h>

#include <windows.h>

#pragma comment(lib, "winmm.lib")

class CNature;

/*

攻击计算公式基类

*/

class CAttackBase

{

public:

    /*

    普通攻击

    */

    virtual int NormalAttack(int nWeaponMinPower, int nWeaponMaxPower, CNature* pNature)

    {

        if( nWeaponMinPower == nWeaponMaxPower )

            return nWeaponMaxPower;

        return rand() % (nWeaponMaxPower - nWeaponMinPower) + nWeaponMinPower;

    }

    /*

    技能攻击

    */

    virtual int SkillAttack(int nSkillMinPower, int nSkillMaxPower, CNature* pNature)

    {

        if( nSkillMinPower == nSkillMaxPower )

            return nSkillMaxPower;

        return rand() % (nSkillMaxPower - nSkillMinPower) + nSkillMinPower;

    }

protected:

    /*

    给定一个小于1等于大于等于0的浮点数, 计算本次调用是否在 [0, fHitRate]区间之内

    精确到百分位

    */

    static bool    HitTheTarget(float fHitRate)

    {

        if( fHitRate <= 0.0f )

            return false;

        else if( fHitRate >= 1.0f )

            return true;

        int nProbability = int( fHitRate * 1000 );

        int nRand = rand() % 1000 + 1;

        return nRand > nProbability ? false : true;

    }

};

/*

会进行攻击的任何物件

*/

class CNature

{

public:

    CNature():m_nForce(0),m_nAgility(0),m_nCrasis(0),m_nIntellect(0),m_pAttack(0) {}

    int        GetForce()        { return m_nForce; }

    int        GetAgility()    { return m_nAgility; }

    int        GetCrasis()        { return m_nCrasis; }

    int        GetIntellect()    { return m_nIntellect; }

    /*

    通过配置文件读取属性

    */

    virtual bool    LoadAttribFromFile() = 0;

    virtual bool    AttackFactoryMethod() = 0;

    int        DoNormalAttack(int nWeaponMinPower, int nWeaponMaxPower)

    {

        if( m_pAttack )

            return m_pAttack->NormalAttack(nWeaponMinPower, nWeaponMaxPower, this);

        return 0;

    }

    

    int        DoSkillAttack(int nSkillMinPower, int nSkillMaxPower)

    {

        if( m_pAttack )

            return m_pAttack->SkillAttack(nSkillMinPower, nSkillMaxPower, this);

        return 0;

    }

    /*

    其它方法略

    */

protected:

    int                m_nForce;

    int                m_nAgility;

    int                m_nCrasis;

    int                m_nIntellect;

    CAttackBase*    m_pAttack;

    /*

    其它属性略

    */

};

/*

刺客攻击力计算公式

*/

class CPinkerAttack : public CAttackBase

{

public:

    CPinkerAttack() {}

    /*

    普通攻击

    */

    virtual int NormalAttack(int nWeaponMinPower, int nWeaponMaxPower, CNature* pNature)

    {

        // 恩,刺客希望敏捷越大、爆击概率越大 攻击力也越大,10.0f,2.5f需要根据等级等因素计算出来

        float fTemp = pNature->GetAgility() / 20.0f;

        int    nTemp = CAttackBase::NormalAttack(nWeaponMinPower, nWeaponMaxPower, pNature) * pNature->GetAgility() / 3;

        // 计算爆击

        if( HitTheTarget(fTemp) )

            nTemp = 2*nTemp;

        printf( "pinker normal attack: %d ", nTemp );

        return nTemp;

    }

    /*

    技能攻击

    */

    virtual int SkillAttack(int nSkillMinPower, int nSkillMaxPower, CNature* pNature)

    {

        // 刺客希望技能攻击不加成,但是会有爆击

        float fTemp = pNature->GetAgility() / 20.0f;

        int    nTemp = CAttackBase::SkillAttack(nSkillMinPower, nSkillMaxPower, pNature);

        // 计算爆击

        if( HitTheTarget(fTemp) )

            nTemp = 2*nTemp;

        printf( "pinker skill attack: %d ", nTemp );

        return nTemp;

    }

};

/*

巫师攻击力计算公式

*/

class CSorcererAttack : public CAttackBase

{

public:

    CSorcererAttack() {}

    /*

    普通攻击

    */

    virtual int NormalAttack(int nWeaponMinPower, int nWeaponMaxPower, CNature* pNature)

    {

        // 巫师物力攻击的爆机很小所以除一个很大的数,并且攻击力不会受到属性影响

        float fTemp = pNature->GetAgility() / 100.0f;

        int    nTemp = CAttackBase::NormalAttack(nWeaponMinPower, nWeaponMaxPower, pNature);

        // 计算爆击

        if( HitTheTarget(fTemp) )

            nTemp = 2*nTemp;

        printf( "sorcerer normal attack: %d ", nTemp );

        return nTemp;

    }

    /*

    技能攻击

    */

    virtual int SkillAttack(int nSkillMinPower, int nSkillMaxPower, CNature* pNature)

    {

        // 巫师希望技能攻击随智力加成,爆击也会

        float fTemp = pNature->GetIntellect() / 80.0f;

        int    nTemp = CAttackBase::SkillAttack(nSkillMinPower, nSkillMaxPower, pNature) * pNature->GetIntellect() / 6;

        // 计算爆击

        if( HitTheTarget(fTemp) )

            nTemp = 2*nTemp;

        printf( "sorcerer skill attack: %d ", nTemp );

        return nTemp;

    }

};

class CPinker : public CNature

{

public:

    bool    LoadAttribFromFile()

    {

        // 从指定配置文件读取计算后得到属性,这里省略

        m_nForce = 10;

        m_nAgility = 15;

        m_nCrasis = 7;

        m_nIntellect = 3;

        return true;

    }

    bool    AttackFactoryMethod()

    {

        m_pAttack = new CPinkerAttack;

        return true;

    }

};

class CSorcerer : public CNature

{

public:

    bool    LoadAttribFromFile()

    {

        // 从指定配置文件读取计算后得到属性,这里省略

        m_nForce = 3;

        m_nAgility = 5;

        m_nCrasis = 6;

        m_nIntellect = 23;

        return true;

    }

    bool    AttackFactoryMethod()

    {

        m_pAttack = new CSorcererAttack;

        return true;

    }

};

int main()

{

    srand(timeGetTime());

    CPinker        Pinker;

    CSorcerer    Sorcerer;

    Pinker.LoadAttribFromFile();

    Pinker.AttackFactoryMethod();

    Sorcerer.LoadAttribFromFile();

    Sorcerer.AttackFactoryMethod();

    while(1)

    {

        Pinker.DoNormalAttack( 30,50 );

        Pinker.DoSkillAttack( 700, 800 );

        printf( " " );

        Sorcerer.DoNormalAttack( 10,30 );

        Sorcerer.DoSkillAttack( 110, 120 );

        printf( " " );

        Sleep( 2000 );

    }

    return 0;

}

 

让我们再来看看,哪部分是可扩展的,哪部分是不可变化的既定逻辑。先观察一下Cnature类,Cnature::DoNormalAttack,DoSkillAttack::DoSkillAttack,这两个方法中计算攻击力的逻辑已被固定,Cpinker、Csorcerer,无需重写计算逻辑,这点符合close。但是Cnature并不在自己内部创建具体的攻击力算法,而是移交给具体的职业自行创建,以后新加入不同角色时可以随时加入新的攻击力计算类(Cpinker::AttackFactoryMethod),很有弹性,
这点符合open。

 

其中关键点是在Cnature中有一个成员变量CAttackBase*     m_pAttack,对Cnature来说只知道有一个计算攻击力的方法,但是不需要知道这个方法具体怎么实现。这是怎么做到的呢?因为不同职业的攻击力计算类有一个共同的祖先CAttackBase。CAttackBase::NormalAttack,
CAttackBase::SkillAttack,这两个方法将被所有子类继承,也就是说子类将遗传父类的特性,他们都是一个模子里出来的。所以在Cnature中可以用CAttackBase*     m_pAttack去代表所有所有攻击力的计算方法。

 

而CNature::AttackFactoryMethod() = 0 ——我们今天的主题。工厂有很多类型,有静态工厂、抽象工厂、工厂方法等等。把它定义为工厂方法是因为?顾名思义:他是嵌入在类中的一个方法。它不是一个静态的函数,也不是一组可以继承的类。他就是存在于一个类中的函数,但是它必须有被可以继承的特性。恩,必须可以被继承。我们的游戏还想创建一个新角色“战士”,力量将成为首要属性,所以我们还得为战士编写攻击力计算类,如果AttackFactoryMethod不能被继承,我们将不能为战士创建攻击类,程序的弹性消失了……

 

 

类结构图说明: 虽然m_pAttack在Cnature定义,但还是在子类实例化,所以结构图将组合的方式放在了子类。



 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: