自制 塔防遊戲 和 設計模式(二)

<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 標識,這個時候 抽象工廠 就有用了,類圖如下:


好吧,我承認我畫得很爛很抽象...這幅類圖想表達的意思只是抽象工廠派生了兩種系列的工廠,在各自的系列裏面又有具體工廠,通過具體工廠生產具體產品。所有工廠的方法名是相同的,實現方法不同而已,所以在 戰場管理 裏面用相同的操作就能建造各種不同的防禦塔。

發佈了38 篇原創文章 · 獲贊 27 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章