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