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

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">防御塔的升级模式讲完了,下面应该考虑防御塔的建造问题。</span>
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">《Kingdom Rush》有四种不同类型的防御塔,分别是弓箭塔,魔法塔,炮塔还有士兵塔。前面三种防御塔的基本逻辑类似,可以表示为:</span>


士兵塔的基本逻辑只是训练士兵,士兵训练完成后在范围内的规定地点集合,探测和攻击怪物的任务就交给士兵了,所以士兵塔的流程可以分为两个,一个是士兵塔本身的,一个是训练出来的士兵的。

      



注:存在会远程攻击的怪物,在士兵没能探测到怪物的情况下就有可能被打死了,所以一出来的时候就要检查自己是不是还活着。

虽然这四种防御塔的方法有很多不同,但是都可以通过 BasicLogic 方法对外提供接口,而把内部的 attack,detect,upgrade,specialEffect 封装起来。在各自的 BasicLogic 里面按照一定的顺序调度这几个“工具”方法。按照类设计的思想, attack,detect,upgrade,specialEffect 这几个工具方法就应该对用户不可见。需要注意的是,这里的用户不是指玩家,而是指使用这个类的程序猿;换句话说就是编写战场管理的人。

因为已经把防御塔升级设计成状态模式,只跟防御塔的类型相关,是特定防御塔类型的继承,对外看不出来是新的类型,所以要建造的防御塔其实只有 4 种类型。

一说到建造什么的,首先想到的就是工厂模式这一系列的东东,简单工厂模式,工厂长模式,抽象工厂模式。

简单工厂...就像名字一样,真的很简单。简单工厂最主要的作用是解除 战场管理 和 防御塔 这两个大模块之间的耦合。试着想一下,如果直接在战场管理里面决定建造的是什么类型的防御塔,那么 战场管理 就需要知道所有的防御塔类型,还需要做四个判断,耦合很紧密而且很麻烦...简单工厂无非就是在这两个模块中间插人一层薄薄的 工厂,把判断什么的都移到 工厂 里面,这样耦合就降低了。简单工厂类图如下:


代码如下:

//SimpleFactory.h
#include<iostream>
enum TYPE
{
	Arrow_Tower,
	Magic_Tower,
	Soldier_Tower,
	Cannon_Tower
};

class DefenceTower
{
public:
	virtual void BasicLogic(){};
};

class ArrowTower: public DefenceTower
{
public:
	ArrowTower()
	{
		std::cout<<"弓箭塔建立完成!"<<std::endl;
	}
	virtual void BasicLogic()
	{
		std::cout<<"弓箭塔基本逻辑"<<std::endl;
	}
};

class MagicTower: public DefenceTower
{
public:
	MagicTower()
	{
		std::cout<<"魔法塔建立完成!"<<std::endl;
	}
	virtual void BasicLogic()
	{
		std::cout<<"魔法塔基本逻辑"<<std::endl;
	}
};

class SoldierTower: public DefenceTower
{
public:
	SoldierTower()
	{
		std::cout<<"士兵塔建立完成!"<<std::endl;
	}
	virtual void BasicLogic()
	{
		std::cout<<"士兵塔基本逻辑"<<std::endl;
	}
};

class CannonTower: public DefenceTower
{
public:
	CannonTower()
	{
		std::cout<<"炮塔建立完成!"<<std::endl;
	}
	virtual void BasicLogic()
	{
		std::cout<<"炮塔基本逻辑"<<std::endl;
	}
};

class SimpleFactory
{
public:
	static DefenceTower* BuildTower(TYPE e)
	{
		switch(e)
		{
		case Arrow_Tower:
			{
				return new ArrowTower();
				break;
			}
		case Magic_Tower:
			{
				return new MagicTower();
				break;
			}
		case Soldier_Tower:
			{
				return new SoldierTower();
				break;
			}
		case Cannon_Tower:
			{
				return new CannonTower();
				break;
			}
		}
	}
};
//main.cpp
#include"SimpleFactory.h"
#include<vector>
int main()
{
	DefenceTower *dt;
	std::vector<DefenceTower*> dtLit;

	dt = SimpleFactory::BuildTower(Arrow_Tower);
	dtLit.push_back(dt);
	dt->BasicLogic();
	dt = SimpleFactory::BuildTower(Magic_Tower);
	dtLit.push_back(dt);
	dt->BasicLogic();
	dt = SimpleFactory::BuildTower(Soldier_Tower);
	dtLit.push_back(dt);
	dt->BasicLogic();
	dt = SimpleFactory::BuildTower(Cannon_Tower);
	dtLit.push_back(dt);
	dt->BasicLogic();

	return 0;
}

需要说明一下,在 main.cpp 里面有个dtList 。在一般来说,在游戏主场景里面,有众多对象,逐一对其更新管理相当繁琐,更常见的做法是加入到有个队列里面,统一进行管理,这里保留了防御塔指针的队列。需要注意,这不是防御塔对象的队列是防御塔指针的队列。因为 vector 的 push_back 函数会保存一个对象的副本,如果直接保存防御塔对象,就会存在两个防御塔对象,而原本 new 出来的对象会因为没有指针指向而变成“野对象”,造成内存泄露......如果保存的是防御塔对象的指针就不会有两份对象,也一直持有这个对象的指针,不会野掉。但是在析构的时候要非常小心,因为是普通指针,所以 vector 销毁的时候只会销毁掉指针,并不会析构指针指向的对象。所以上面的写法还是会内存泄露的......

有几个办法解决这种问题:

1、vector 里面保存智能指针

2、在析构 vector 之前先遍历里面所有的指针,依次调用 delete XXX; 然后在析构 vector

3、继承 vector ,手动扩展其析构方法,递归的销毁元素

对于这个游戏来说,简单工厂其实完全就够用了,因为防御塔类型不会变多(不扩展)。在模仿一款成功的游戏的时候,我们已经知道成品是什么样子,所以不会扩展,但是在真实的开发过程中,功能( and 需求)会不停的变动,增多是在所难免的(比如游戏策划脑洞大开,一会儿一个 idea...)。这个时候,不仅要在 防御塔 里增加子类,还要在 简单工厂 里面就需要不断的修改,这不符合 “对扩展开放,对修改关闭” 的原则。

我对 “对扩展开放,对修改关闭” 的理解很浅显,就是尽量用继承,多态,这样的东东去扩展新功能,尽量不要用修改之前就已经有的类。

要解决简单工厂对修改也开放的问题,可以升级到 工厂模式,其类图如下:


代码如下:

//Factory.h
#include<iostream>
enum TYPE
{
	Arrow_Tower,
	Magic_Tower,
	Soldier_Tower,
	Cannon_Tower
};

class DefenceTower
{
public:
	virtual void BasicLogic(){};
};

class Factory
{
public:
	virtual DefenceTower* BuildTower() = 0;
};

class ArrowTower: public DefenceTower
{
public:
	ArrowTower()
	{
		std::cout<<"弓箭塔建立完成!"<<std::endl;
	}
	virtual void BasicLogic()
	{
		std::cout<<"弓箭塔基本逻辑"<<std::endl;
	}
};

class MagicTower: public DefenceTower
{
public:
	MagicTower()
	{
		std::cout<<"魔法塔建立完成!"<<std::endl;
	}
	virtual void BasicLogic()
	{
		std::cout<<"魔法塔基本逻辑"<<std::endl;
	}
};

class SoldierTower: public DefenceTower
{
public:
	SoldierTower()
	{
		std::cout<<"士兵塔建立完成!"<<std::endl;
	}
	virtual void BasicLogic()
	{
		std::cout<<"士兵塔基本逻辑"<<std::endl;
	}
};

class CannonTower: public DefenceTower
{
public:
	CannonTower()
	{
		std::cout<<"炮塔建立完成!"<<std::endl;
	}
	virtual void BasicLogic()
	{
		std::cout<<"炮塔基本逻辑"<<std::endl;
	}
};

class ArrowTowerFactory:public Factory
{
	virtual DefenceTower* BuildTower()
	{
		return new ArrowTower();
	}
};

class MagicTowerFactory:public Factory
{
	virtual DefenceTower* BuildTower()
	{
		return new MagicTower();
	}
};

class SoldierTowerFactory:public Factory
{
	virtual DefenceTower* BuildTower()
	{
		return new SoldierTower();
	}
};

class CannonTowerFactory:public Factory
{
	virtual DefenceTower* BuildTower()
	{
		return new CannonTower();
	}
};
//main.cpp
#include"Factory.h"
#include<vector>
int main()
{
	DefenceTower *dt;
	Factory *factory;
	std::vector<DefenceTower*> dtList;

	factory = new ArrowTowerFactory();
	dt = factory->BuildTower();
	dtList.push_back(dt);
	dt->BasicLogic();

	if(factory != nullptr)
	{	
		delete factory;
	}
	factory = new MagicTowerFactory();
	dt = factory->BuildTower();
	dtList.push_back(dt);
	dt->BasicLogic();

	if(factory != nullptr)
	{	
		delete factory;
	}
	factory = new SoldierTowerFactory();
	dt = factory->BuildTower();
	dtList.push_back(dt);
	dt->BasicLogic();

	if(factory != nullptr)
	{	
		delete factory;
	}
	factory = new CannonTowerFactory();
	dt = factory->BuildTower();
	dtList.push_back(dt);
	dt->BasicLogic();

	for(std::vector<DefenceTower*>::iterator it = dtList.begin(); it != dtList.end(); ++it)
	{
		delete *it;
	}

	return 0;
}
(注:这里用了刚才讲的第2种方法解决内存泄露)

以后扩展防御塔类型就只需要 Factory 类和 DefenceTower 类里面添加新的子类就可以了,对原有的类不需要改动。如果在 战场管理 里面就明确知道要修建哪种类型的防御塔,还真可以做到 ”对扩展开放,对修改关闭“ ,然而不幸的是,在玩家选择何种防御塔之前 战场管理 并不知道,这一点让对静态把握尚佳的 工厂模式 很尴尬......个人感觉,这种情况下还不如选择 简单工厂 + 函数指针数组,这样就能避免很长的 switch-case,还能起到一点点优化的效果,代码如下:

//SimpleFactory.h
#include<iostream>
#include<memory>
enum TYPE
{
	Arrow_Tower,
	Magic_Tower,
	Soldier_Tower,
	Cannon_Tower
};

class DefenceTower
{
public:
	virtual void BasicLogic(){};
};

class ArrowTower : public DefenceTower
{
public:
	ArrowTower()
	{
		std::cout << "弓箭塔建立完成!" << std::endl;
	}
	virtual void BasicLogic()
	{
		std::cout << "弓箭塔基本逻辑" << std::endl;
	}
};

class MagicTower : public DefenceTower
{
public:
	MagicTower()
	{
		std::cout << "魔法塔建立完成!" << std::endl;
	}
	virtual void BasicLogic()
	{
		std::cout << "魔法塔基本逻辑" << std::endl;
	}
};

class SoldierTower : public DefenceTower
{
public:
	SoldierTower()
	{
		std::cout << "士兵塔建立完成!" << std::endl;
	}
	virtual void BasicLogic()
	{
		std::cout << "士兵塔基本逻辑" << std::endl;
	}
};

class CannonTower : public DefenceTower
{
public:
	CannonTower()
	{
		std::cout << "炮塔建立完成!" << std::endl;
	}
	virtual void BasicLogic()
	{
		std::cout << "炮塔基本逻辑" << std::endl;
	}
};

typedef std::shared_ptr<DefenceTower>(*Func)();

class SimpleFactory
{
public:
	static Func buildTower[4];
public:
	static std::shared_ptr<DefenceTower> BuildTower(TYPE e)
	{		
		return buildTower[e]();
	}
};
//SimpleFactory.cpp
#include"SimpleFactory.h"
std::shared_ptr<DefenceTower> BuildArrowTower()
{
	return	std::make_shared<ArrowTower>();
}

std::shared_ptr<DefenceTower> BuildMagicTower()
{
	return	std::make_shared<MagicTower>();
}

std::shared_ptr<DefenceTower> BuildSoldierTower()
{
	return	std::make_shared<SoldierTower>();
}

std::shared_ptr<DefenceTower> BuildCannonTower()
{
	return	std::make_shared<CannonTower>();
}

Func SimpleFactory::buildTower[4] = { 
	BuildArrowTower, 
	BuildMagicTower,
	BuildSoldierTower,
	BuildCannonTower 
};
//main.cpp
#include"SimpleFactory.h"
#include<vector>
int main()
{
	std::shared_ptr<DefenceTower> dt;
	std::vector<std::shared_ptr<DefenceTower>> dtList;

	dt = SimpleFactory::BuildTower(Arrow_Tower);
	dtList.push_back(dt);
	dt->BasicLogic();
	dt = SimpleFactory::BuildTower(Magic_Tower);
	dtList.push_back(dt);
	dt->BasicLogic();
	dt = SimpleFactory::BuildTower(Soldier_Tower);
	dtList.push_back(dt);
	dt->BasicLogic();
	dt = SimpleFactory::BuildTower(Cannon_Tower);
	dtList.push_back(dt);
	dt->BasicLogic();

	return 0;
}
这里用上面说的第一种方法解决 析构、内存泄露 问题,C++11 的智能指针还是挺好用的,不过使用的时候还是要谨慎,一定不要和普通指针混用,不然很容易出析构空指针的问题。

至于 抽象工厂 ,在这个防御塔建造的环境下用不上......一般说来,抽象工厂 用于多个生产系列的产品,而这些产品有基本相同的功能,但是用某个系列的产品又生产不出来别的系列。这么说有点抽象,举个例子。比如现在地图上有一片区域是沼泽,沼泽上不能建造现有的这一批防御塔,但是有一批功能相仿的新防御塔能建造在上面。这两批防御塔用不同的 type 标识,这个时候 抽象工厂 就有用了,类图如下:


好吧,我承认我画得很烂很抽象...这幅类图想表达的意思只是抽象工厂派生了两种系列的工厂,在各自的系列里面又有具体工厂,通过具体工厂生产具体产品。所有工厂的方法名是相同的,实现方法不同而已,所以在 战场管理 里面用相同的操作就能建造各种不同的防御塔。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值