游戲編程模式(二):命令模式和享元模式
命令模式
命令模式在GoF中有個(gè)晦澀的定義:
將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而使你可用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化;對(duì)請(qǐng)求排隊(duì)或記錄請(qǐng)求日志,以及支持可撤銷的操作。
高度抽象的定義加上蹩腳的翻譯使其難懂,接下來會(huì)使用一些代碼案例描述我們開發(fā)過程中可能遇到的場(chǎng)景,并展示解耦的過程演化。
一.
在實(shí)現(xiàn)人物控制的功能時(shí),簡(jiǎn)單的實(shí)現(xiàn)會(huì)是:
void InputHandler::handleInput()
{
? ?if (isPressed(BUTTON_X)) jump();
? ?else if (isPressed(BUTTON_Y)) fireGun();
? ?...
}
二.
游戲中一般會(huì)有配置按鍵的功能,這時(shí)就要求類似jump()這種方法調(diào)用轉(zhuǎn)變?yōu)榭勺儞Q的東西,自然我們會(huì)將其用一個(gè)對(duì)象來表示,這時(shí)我們就進(jìn)入了命令模式,用一個(gè)Command類來作為基類,將類似jump()這種游戲行為都定義為一個(gè)子類:
// 定義子類
class JumpCommand : public Command
{
public:
? ?virtual void execute() { jump(); }
};
class FireCommand : public Command
{
public:
????virtual void execute() { fireGun(); }
};
class InputHandler
{
public:
? ?void handleInput();
? ?// 綁定命令的方法……
private:
? ?Command* buttonX_;
? ?Command* buttonY_;
? ?...
};
void InputHandler::handleInput()
{
? ?if (isPressed(BUTTON_X)) buttonX_->execute();
? ?else if (isPressed(BUTTON_Y)) buttonY_->execute();
? ?...
}
此時(shí)就可以通過改變各個(gè)button變量的值來改變按鍵的設(shè)置。
三.
jump()這些行為方法一般會(huì)與某個(gè)玩家角色進(jìn)行關(guān)聯(lián),但是我們不應(yīng)該讓方法去找它們控制的角色,而是應(yīng)將角色對(duì)象作為參數(shù)傳進(jìn)去:
class JumpCommand : public Command
{
public:
? ?virtual void execute(GameActor& actor)
? ?{
? ? actor.jump();
? ?}
};
這樣做的好處就是可以靈活控制任意的角色,只需要傳入不同的角色對(duì)象。但此時(shí)需要把a(bǔ)ctor角色對(duì)象與handleInput()方法進(jìn)行解耦,把execute()的執(zhí)行放到外面去:
Command* InputHandler::handleInput()
{
? ?if (isPressed(BUTTON_X)) return buttonX_;
? ?if (isPressed(BUTTON_Y)) return buttonY_;
? ?...
}
Command* command = inputHandler.handleInput();
if (command)
{
command->execute(actor);
}
四.
在典型的回合制策略游戲中,一般會(huì)有回滾這一概念,可以撤銷或者重做玩家不喜歡的操作。命令模式完美地提供了這一問題的解決方案。
比如,在實(shí)現(xiàn)移動(dòng)某個(gè)單位的功能時(shí),代碼可能如下:
class MoveUnitCommand : public Command
{
public:
...
? ?virtual void execute()
? ?{
? ? unit_->moveTo(x_, y_);
? ?}
private:
? ?Unit* unit_;
? ?int x_, y_;
};
這里的命令更特殊,將x,y作為成員變量,這意味著控制代碼會(huì)在玩家做出操作時(shí)創(chuàng)造一個(gè)實(shí)例:
Command* handleInput()
{
? ?Unit* unit = getSelectedUnit();
? ?if (isPressed(BUTTON_UP)) {
? ? ? ?// 向上移動(dòng)單位
? ? ? ?int destY = unit->y() - 1;
? ? ? ?return new MoveUnitCommand(unit, unit->x(), destY);
? ?}
? ?if (isPressed(BUTTON_DOWN)) {
? ? ? ?// 向下移動(dòng)單位
? ? ? ?int destY = unit->y() + 1;
? ? ? ?return new MoveUnitCommand(unit, unit->x(), destY);
? ?}
? ?// 其他的移動(dòng)……
? ?return NULL;
}
在此基礎(chǔ)上,可以增加一個(gè)記錄移動(dòng)之前位置的變量,來達(dá)到撤銷操作的目的:
class MoveUnitCommand : public Command
{
public:
? ?...
? ?virtual void execute()
? ?{
? ? ? ?// 保存移動(dòng)之前的位置
? ? ? ?xBefore_ = unit_->x();
? ? ? ?yBefore_ = unit_->y();
? ? ? ?unit_->moveTo(x_, y_);
? ?}
? ?virtual void undo()
? ?{
? ? unit_->moveTo(xBefore_, yBefore_);
? ?}
private:
? ?Unit* unit_;
? ?int xBefore_, yBefore_;
? ?int x_, y_;
};
五.
撤銷和重做是一對(duì)正反操作,實(shí)現(xiàn)類似軟件中的撤銷列表和重做列表,只需將其維護(hù)成一個(gè)鏈表或數(shù)組:

享元模式
享元模式非常簡(jiǎn)單,稍微提一下。
游戲開發(fā)中,如果要呈現(xiàn)一片巨大的森林,成千上萬的樹,每棵樹都由上千的多邊形組成,在不做任何優(yōu)化的情況下,可以就是每秒送到GPU六十次的百萬個(gè)多邊形。
一.
定義一個(gè)樹看起來會(huì)是:
class Tree
{
private:
? ?Mesh mesh_; ?//多邊形網(wǎng)格
? ?Texture bark_;
? ?Texture leaves_; //樹皮、樹葉紋理
? ?Vector position_; //位置
? ?double height_;
? ?double thickness_; //高度厚度
? ?Color barkTint_;
? ?Color leafTint_; //顏色
};
二.
class TreeModel
{
private:
? ?Mesh mesh_;
? ?Texture bark_;
? ?Texture leaves_;
};
class Tree
{
private:
? ?TreeModel* model_; //所有樹共用一個(gè)模型實(shí)例
? ?Vector position_;
? ?double height_;
? ?double thickness_;
? ?Color barkTint_;
? ?Color leafTint_;
};
拓展一下,多數(shù)情況下不會(huì)在一開始就創(chuàng)建所有享元,當(dāng)你需要一個(gè)時(shí),一般會(huì)先檢查是否已經(jīng)創(chuàng)建了一個(gè)相同的實(shí)例,這通常意味著需要將構(gòu)造函數(shù)封裝在檢查實(shí)例是否已經(jīng)存在的接口之后,這也許會(huì)用到工廠方法。
為了返回一個(gè)已有的享元,通常需要一個(gè)存儲(chǔ)它們的地方,使用對(duì)象池會(huì)是一個(gè)好辦法。
在狀態(tài)模式