将设计模式运用于游戏设计:工厂方法
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定义,但还是在子类实例化,所以结构图将组合的方式放在了子类。
问题描述:
在一般的角色扮演游戏中都有很多不同角色让玩家选择,这些不同角色对属性的依赖以及攻击力的计算公式各有不同。例如刺客主要属性是敏捷,法师主要依赖属性是智力。这就会产生一个问题,每当在游戏系统中新加入一个角色,那么就会有一套新的属性以及攻击力的计算公式产生。注意:这种添加是平行的,新加一个角色就会有新的攻击计算公式产生。
那么如何才能做到更新内容而不改变原有逻辑呢?
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定义,但还是在子类实例化,所以结构图将组合的方式放在了子类。
相关文章推荐
- 设计模式在游戏中的运用(工厂模式)
- 设计模式在游戏中的应用--工厂方法(五)
- 设计模式之工厂方法模式
- 设计模式之工厂方法
- Java设计模式(2) -- 工厂方法
- 设计模式示例一 Factory Method(工厂方法)
- php设计模式之工厂方法
- 设计模式—工厂方法
- Java设计模式之三:工厂方法模式
- PHP设计模式之工厂方法设计模式实例分析
- 研磨设计模式之工厂方法模式-4
- (转)设计模式(3):工厂方法模式
- Java设计模式之工厂方法模式
- 可复用面向对象软件基础——设计模式(二)之工厂方法模式
- 学习设计模式-方法工厂设计模式
- Java设计模式之简单工厂、工厂方法和抽象工厂
- Java设计模式——模板方法设计模式——抽象类的运用
- java设计模式_工厂方法
- 设计模式(六)——JDK中的那些工厂方法
- 设计模式 学习之工厂方法模式