《Kingdom Rush》是非常有趣的一款塔防类游戏。最近我跟几个同学试着做一个原型出来。
(PS:小弟最近才开始看设计模式,看的也不是非常懂,理解上有偏差,所写的东西有错误,还望各路高手批评指正)
在分析设计这个游的时候遇到很多上的问题,比如同一个类型的防御塔升级到底是真的“升级”了,还是原有防御塔拆卸之后的新建;再比如如何根据输入建造不同类型的防御塔;战场中众多的防御塔,小怪,友方士兵如何管理,如何简明的根据各个对象的当前状态执行它的下一步操作等。
从宏观上讲,建造防御塔是塔防游戏的第一步,而选择不同类型又是建造防御塔的第一步。如何根据选择建造相应的防御塔是一种设计上的考虑。
《Kingdom Rush》里面有四种类型的防御塔,分别是弓箭塔,魔法塔,炮塔,士兵塔。每种防御塔又有不同的升级方式。以弓箭塔为例子:
最左边的是一级弓箭塔,第二个是二级弓箭塔,第三个是三级弓箭塔,第四个和第五个同属于四级弓箭塔,但是是三级弓箭塔的两种不同升级方向。
不同的弓箭塔有不同的贴图,这是最表象的区别。深入一点看,不同的弓箭塔的攻击力,攻击范围,攻击频率不同,最高级的两个弓箭塔还有不同的技能,甚至于有的攻击方式也不一样。介于这些不同,我考虑了有四种策略组织弓箭塔这个大类。
1、把各个弓箭塔看作不同的类,共同继承与弓箭塔基类,类图如下:
这种策略模式下,防御塔之间除了共同的基类之外没有什么联系。每次升级是创建一个新的防御塔,替换旧的防御塔,然后把旧的防御塔删除掉。
2、把各个防御塔联系起来,采用装饰模式的结构,每一次升级都是利用子类的一次扩展,类图如下:
这种测咯模式下,每次防御塔的升级都只是创建升级塔壳,然后用塔克去包装已有的防御塔,这一点是通过升级塔壳内有一个指向父类箭塔的指针实现的。这样一定程度上体现了“升级”这一概念,而不是简单的 新建+拆毁。
//ArrowTower.h
#include<iostream>
class ArrowTower
{
private: //私有变量,子类不可见也不能访问
int attack;
int interval;
int range;
public:
ArrowTower():attack(0), interval(20), range(0){}
virtual int getAttack(){return attack;}
virtual int getInterval(){return interval;}
virtual int getRange(){return range;}
void Attack()
{
std::cout<<"attack! "<<getAttack()<<std::endl;
}
void Detect()
{
std::cout<<"detect! detect range "<<getRange()<<std::endl;
}
void Interval()
{
std::cout<<"attack interval "<<getInterval()<<std::endl;
}
};
class ArrowUpgradeTower : public ArrowTower //默认是私有继承,此处声明为公有继承
{
protected: //保护类成员变量,对外封装,对子类可见,但是不能通过父类访问,只能通过子类本身访问
int additionAttack;
int additionInterval;
int additionRange;
ArrowTower& preTower; //引用,必须初始化,所以没有用默认构造函数
public:
ArrowUpgradeTower(ArrowTower& t):
additionAttack(0), additionInterval(0), additionRange(0),
preTower(t){}
//比较 tricky 的用法,递归调用前一个阶段 箭塔 的 get 方法
virtual int getAttack(){return additionAttack + preTower.getAttack();}
virtual int getRange(){return additionRange + preTower.getRange();}
virtual int getInterval(){return additionInterval + preTower.getInterval();}
};
class BasicArrowTower : public ArrowUpgradeTower
{
public:
BasicArrowTower(ArrowTower& t):ArrowUpgradeTower(t)
//父类没有默认构造函数,只能显示的调用父类已有的构造函数
{
additionAttack = 10;
additionInterval = -5;
additionRange = 5;
preTower = t;
}
};
class MiddleArrowTower : public ArrowUpgradeTower
{
public:
MiddleArrowTower(ArrowTower& t):ArrowUpgradeTower(t)
{
additionAttack = 10;
additionInterval = -5;
additionRange = 5;
preTower = t;
}
};
class AdvanceArrowTower : public ArrowUpgradeTower
{
public:
AdvanceArrowTower(ArrowTower& t):ArrowUpgradeTower(t)
{
additionAttack = 10;
additionInterval = -5;
additionRange = 5;
preTower = t;
}
};
//main.cpp
#include"ArrowTower.h"
int main()
{
ArrowTower *at = new ArrowTower(); //空箭塔
ArrowTower *bat = new BasicArrowTower(*at); //初级箭塔
ArrowTower *mat = new MiddleArrowTower(*bat); //中级箭塔
ArrowTower *aat = new AdvanceArrowTower(*mat); //高级箭塔
at->Attack();
at->Detect();
at->Interval();
bat->Attack();
bat->Detect();
bat->Interval();
mat->Attack();
mat->Detect();
mat->Interval();
aat->Attack();
aat->Detect();
aat->Interval();
return 0;
}
3、把每个弓箭塔的类型和各种属性看作是当前弓箭塔的一种状态,而不同的状态对应的升级策略不同。最初的两次升级只有一种策略,只有到达三级弓箭塔之后,升级的时候才有不同的策略,因此可以用状态模式来设计类之间的关系,类图如下:
箭塔基类通过持有的状态类,来反映自己的属性,状态类通过传入和反悔箭塔类的指针在 upgrade() 函数里规划出升级方向。体现 “逐层次,有序升级” 这个概念。
//ArrowTower.h
#include<iostream>
enum TYPE
{
Default,
BasicArrowTower,
MiddleArrowTower,
AdvanceArrowTower,
JungleTower,
RoerTower
};
class State;
class BasicArrowTowerState;
class MiddleArrowTowerState;
class AdvanceArrowTowerState;
class JungleTowerState;
class RoerTowerState;
class ArrowTower
{
friend class BasicArrowTowerState;
friend class MiddleArrowTowerState;
friend class AdvanceArrowTowerState;
private:
State *state;
public:
ArrowTower();
virtual int getAttack();
virtual int getInterval();
virtual int getRange();
void Attack()
{
std::cout<<"attack! "<<getAttack()<<std::endl;
}
void Detect()
{
std::cout<<"detect! detect range "<<getRange()<<std::endl;
}
void Interval()
{
std::cout<<"attack interval "<<getInterval()<<std::endl;
}
void Upgrade(TYPE t = Default);
//默认参数的函数只需要在声明的地方写出来,定义的地方不能重复
};
class State
{
public:
int attack;
int interval;
int range;
public:
State():attack(10), interval(20), range(10){}
virtual void Upgrade(ArrowTower& a, TYPE t = Default){}
};
class BasicArrowTowerState : public State
{
public:
BasicArrowTowerState()
{
attack = 15;
interval = 15;
range = 15;
}
virtual void Upgrade(ArrowTower& a, TYPE t = Default);
};
class MiddleArrowTowerState : public State
{
public:
MiddleArrowTowerState()
{
attack = 20;
interval = 10;
range = 20;
}
virtual void Upgrade(ArrowTower& a, TYPE t = Default);
};
class AdvanceArrowTowerState : public State
{
public:
AdvanceArrowTowerState()
{
attack = 25;
interval = 8;
range = 25;
}
virtual void Upgrade(ArrowTower& a, TYPE t = Default);
};
class JungleTowerState:public State
{
private:
using State::Upgrade;
//virtual void Upgrade(ArrowTower& a, TYPE t = Default);
public:
JungleTowerState()
{
attack = 30;
interval = 5;
range = 25;
}
};
class RoerTowerState:public State
{
private:
using State::Upgrade;
//好 tricky 的用法,公有继承下,把父类的公有方法变成私有的
//但是似乎有破解权限的方法,用(static_cast<Base>(x)).func();
public:
RoerTowerState()
{
attack = 30;
interval = 3;
range = 20;
}
};
ArrowTower::ArrowTower()
{
state = new BasicArrowTowerState();
}
int ArrowTower::getAttack(){return state->attack;}
int ArrowTower::getInterval(){return state->interval;}
int ArrowTower::getRange(){return state->range;}
void ArrowTower::Upgrade(TYPE t)
{
state->Upgrade(*this, t);
}
void BasicArrowTowerState::Upgrade(ArrowTower& a, TYPE t)
{
State *tmp = new MiddleArrowTowerState();
delete a.state;
a.state = tmp;
}
void MiddleArrowTowerState::Upgrade(ArrowTower& a, TYPE t)
{
State *tmp = new AdvanceArrowTowerState();
delete a.state;
a.state = tmp;
}
void AdvanceArrowTowerState::Upgrade(ArrowTower& a, TYPE t)
{
State *tmp;
if(t == JungleTower)
tmp = new JungleTowerState();
else
tmp = new RoerTowerState();
delete a.state;
a.state = tmp;
}
#include"State.h"
#include"ArrowTower.h"
#include"UpgradeArrowTower.h"
BasicArrowTowerState::BasicArrowTowerState()
{
attack = 15;
interval = 15;
range = 15;
shell = new BasicArrowTowerShell();
}
MiddleArrowTowerState::MiddleArrowTowerState()
{
attack = 20;
interval = 10;
range = 20;
shell = new MiddleArrowTowerShell();
}
AdvanceArrowTowerState::AdvanceArrowTowerState()
{
attack = 25;
interval = 8;
range = 25;
shell = new AdvancedArrowTowerShell();
}
JungleTowerState::JungleTowerState()
{
attack = 30;
interval = 5;
range = 25;
shell = new JungleTowerTowerShell();
}
RoerTowerState::RoerTowerState()
{
attack = 30;
interval = 3;
range = 20;
shell = new RoerTowerTowerShell();
}
void BasicArrowTowerState::Upgrade(ArrowTower& a, TYPE t)
{
State *tmp = new MiddleArrowTowerState();
delete a.state;
a.state = tmp;
}
void MiddleArrowTowerState::Upgrade(ArrowTower& a, TYPE t)
{
State *tmp = new AdvanceArrowTowerState();
delete a.state;
a.state = tmp;
}
void AdvanceArrowTowerState::Upgrade(ArrowTower& a, TYPE t)
{
State *tmp;
if(t == JungleTower)
tmp = new JungleTowerState();
else
tmp = new RoerTowerState();
delete a.state;
a.state = tmp;
}
状态模式和装饰模式混合,在状态模式的 upgrade() 函数里面给弓箭塔套上对饮的升级外壳,类图如下:
这样设计可以根据不同的状态升级到可选的后续状态,每个状态的返回值都是用升级的塔壳包装过的弓箭塔。这很契合“升级”这个概念,既在原有基础上的特定增加。
可惜我们实际做的时候采用了第一种策略......
后来编程实现的时候发现,装饰模式混合状态模式其实很怪异...装饰模式需要返回父类的指针,而状态模式只是需要目标类作为输入参数...混起来感觉好怪异...
我编程的“升级”采用了状态模式,附加的各种“特殊效果”用类似装饰模式的壳。代码里还有很多地方比较 tricky ,比如交叉引用时候 .h 和 .cpp 文件的组合,改变父类方法的访问权限,函数指针数组代替繁琐的 switch...case 搞了好几天,总算是做出来一个自己比较满意的弓箭塔升级模式。升级搞定了,就是建造防御塔了。
//ArrowTower.h
#ifndef _ARROW_TOWER
#define _ARROW_TOWER
#ifndef _IOSTREAM
#define _IOSTREAM
#include<iostream>
#endif
class State;
enum TYPE;
class ArrowTower
{
friend class BasicArrowTowerState;
friend class MiddleArrowTowerState;
friend class AdvanceArrowTowerState;
protected:
State* state;
public:
ArrowTower();
virtual int getAttack();
virtual int getInterval();
virtual int getRange();
void SpecialFunc0();
void SpecialFunc1();
void Attack()
{
std::cout<<"attack! "<<getAttack()<<std::endl;
}
void Detect()
{
std::cout<<"detect! detect range "<<getRange()<<std::endl;
}
void Interval()
{
std::cout<<"attack interval "<<getInterval()<<std::endl;
}
virtual void Upgrade(TYPE t);
};
#endif
//State.h
#ifndef _STATE
#define _STATE
enum TYPE
{
Default,
BasicArrowTower,
MiddleArrowTower,
AdvanceArrowTower,
JungleTower,
RoerTower
};
class ArrowTowerShell;
class ArrowTower;
class State
{
public:
int attack;
int interval;
int range;
ArrowTowerShell* shell; //用来扩展每种箭塔的特殊技能
public:
virtual void Upgrade(ArrowTower& ,TYPE t = Default){}
};
class BasicArrowTowerState: public State
{
public:
BasicArrowTowerState();
virtual void Upgrade(ArrowTower& a, TYPE t = Default);
};
class MiddleArrowTowerState : public State
{
public:
MiddleArrowTowerState();
virtual void Upgrade(ArrowTower& a, TYPE t = Default);
};
class AdvanceArrowTowerState : public State
{
public:
AdvanceArrowTowerState();
virtual void Upgrade(ArrowTower& a, TYPE t = Default);
};
class JungleTowerState:public State
{
private:
using State::Upgrade;
public:
JungleTowerState();
};
class RoerTowerState:public State
{
private:
using State::Upgrade;
public:
RoerTowerState();
};
#endif
//UpgradeTower.h
#ifndef _UPGRADE_ARROW_TOWER
#define _UPGRADE_ARROW_TOWER
#ifndef _IOSTREAM
#define _IOSTREAM
#include<iostream>
#endif
typedef void(*Func)();
//函数指针,将Func定义成参数为空,返回值也为空的函数指针类型
class ArrowTowerShell
{
protected:
Func func[2]; //用来调用特定状态下使用的特殊效果
public:
virtual void SpecialFunc0(){};
virtual void SpecialFunc1(){};
//除了纯虚函数,只声明不定义会出现 LINK2001 的链接错误...
};
class BasicArrowTowerShell:public ArrowTowerShell
{
public:
BasicArrowTowerShell()
{
func[0] = nullptr;
func[1] = nullptr;
}
private: //初级箭塔没有特效
using ArrowTowerShell::SpecialFunc0;
using ArrowTowerShell::SpecialFunc1;
};
class MiddleArrowTowerShell:public ArrowTowerShell
{
public:
MiddleArrowTowerShell()
{
func[0] = nullptr;
func[1] = nullptr;
}
private: //中级箭塔没有特效
using ArrowTowerShell::SpecialFunc0;
using ArrowTowerShell::SpecialFunc1;
};
class AdvancedArrowTowerShell:public ArrowTowerShell
{
public:
AdvancedArrowTowerShell()
{
func[0] = nullptr;
func[1] = nullptr;
}
private: //高级箭塔没有特效
using ArrowTowerShell::SpecialFunc0;
using ArrowTowerShell::SpecialFunc1;
};
class JungleTowerTowerShell:public ArrowTowerShell
{
public:
JungleTowerTowerShell();
virtual void SpecialFunc0(){func[0]();}
virtual void SpecialFunc1(){func[1]();}
};
class RoerTowerTowerShell:public ArrowTowerShell
{
public:
RoerTowerTowerShell();
virtual void SpecialFunc0(){func[0]();}
virtual void SpecialFunc1(){func[1]();}
};
#endif
//ArrowerTower.cpp
#include"State.h"
#include"ArrowTower.h"
#include"UpgradeArrowTower.h"
ArrowTower::ArrowTower()
{
state = new BasicArrowTowerState();
}
int ArrowTower::getAttack(){return state->attack;}
int ArrowTower::getInterval(){return state->interval;}
int ArrowTower::getRange(){return state->range;}
void ArrowTower::Upgrade(TYPE t)
{
state->Upgrade(*this, Default);
}
void ArrowTower::SpecialFunc0()
{
state->shell->SpecialFunc0();
}
void ArrowTower::SpecialFunc1()
{
state->shell->SpecialFunc1();
}
//State.cpp
#include"State.h"
#include"ArrowTower.h"
#include"UpgradeArrowTower.h"
BasicArrowTowerState::BasicArrowTowerState()
{
attack = 15;
interval = 15;
range = 15;
shell = new BasicArrowTowerShell();
}
MiddleArrowTowerState::MiddleArrowTowerState()
{
attack = 20;
interval = 10;
range = 20;
shell = new MiddleArrowTowerShell();
}
AdvanceArrowTowerState::AdvanceArrowTowerState()
{
attack = 25;
interval = 8;
range = 25;
shell = new AdvancedArrowTowerShell();
}
JungleTowerState::JungleTowerState()
{
attack = 30;
interval = 5;
range = 25;
shell = new JungleTowerTowerShell();
}
RoerTowerState::RoerTowerState()
{
attack = 30;
interval = 3;
range = 20;
shell = new RoerTowerTowerShell();
}
void BasicArrowTowerState::Upgrade(ArrowTower& a, TYPE t)
{
State *tmp = new MiddleArrowTowerState();
delete a.state;
a.state = tmp;
}
void MiddleArrowTowerState::Upgrade(ArrowTower& a, TYPE t)
{
State *tmp = new AdvanceArrowTowerState();
delete a.state;
a.state = tmp;
}
void AdvanceArrowTowerState::Upgrade(ArrowTower& a, TYPE t)
{
State *tmp;
if(t == JungleTower)
tmp = new JungleTowerState();
else
tmp = new RoerTowerState();
delete a.state;
a.state = tmp;
}
//UpgradeArrowTower.cpp
#include"UpgradeArrowTower.h"
void RoerTowerFunc0()
{
std::cout<<"火枪塔特效0"<<std::endl;
}
void RoerTowerFunc1()
{
std::cout<<"火枪塔特效1"<<std::endl;
}
void JungleTowerFunc0()
{
std::cout<<"丛林塔塔特效0"<<std::endl;
}
void JungleTowerFunc1()
{
std::cout<<"丛林塔塔特效1"<<std::endl;
}
JungleTowerTowerShell::JungleTowerTowerShell()
{
func[0] = JungleTowerFunc0;
func[1] = JungleTowerFunc1;
}
RoerTowerTowerShell::RoerTowerTowerShell()
{
func[0] = RoerTowerFunc0;
func[1] = RoerTowerFunc1;
}
//main.cpp
#include"ArrowTower.h"
#include"State.h"
#include"UpgradeArrowTower.h"
int main()
{
TYPE t = JungleTower;
ArrowTower *at = new ArrowTower();
at->Attack();
at->Detect();
at->Interval();
at->Upgrade(MiddleArrowTower);
at->Attack();
at->Detect();
at->Interval();
at->Upgrade(AdvanceArrowTower);
at->Attack();
at->Detect();
at->Interval();
at->Upgrade(t);
at->Attack();
at->Detect();
at->Interval();
at->SpecialFunc0();
at->SpecialFunc1();
return 0;
}
在防御塔建造的时候也有不同的策略,比如工厂模式,我们也想了比较 tricky 的方法。哎,要干活的走起了,下次在聊吧