自制 塔防游戏 和 设计模式(一)

《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 的方法。哎,要干活的走起了,下次在聊吧

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值