參考游戲編程模式
并發(fā)狀態(tài)機
我們決定給英雄拿槍的能力昨悼。 當她拿著槍的時候,她還是能做她之前的任何事情:跑動跃洛,
跳躍率触,跳斬,等等汇竭。 但是她在做這些的同時也要能開火葱蝗。
如果我們執(zhí)著于FSM,我們需要翻倍現(xiàn)有狀態(tài)细燎。 對于每個現(xiàn)有狀態(tài)两曼,我們需要另一個她持
槍狀態(tài):站立,持槍站立玻驻,跳躍悼凑,持槍跳躍, 你知道我的意思了吧璧瞬。
多加幾種武器户辫,狀態(tài)就會指數(shù)爆炸。 不但增加了大量的狀態(tài)嗤锉,這也增加了大量的冗余: 持
槍和不持槍的狀態(tài)是完全一樣的寸莫,只是多了一點負責射擊的代碼。
問題在于我們將兩種狀態(tài)綁定——她做的和她攜帶的——到了一個狀態(tài)機上档冬。 為了處理所有
可能的組合膘茎,我們需要為每一對組合寫一個狀態(tài)。 修復方法很明顯:使用兩個單獨的狀態(tài)
機酷誓。
如果她在做什么有n個狀態(tài)披坏,而她攜帶了什么有m個狀態(tài),要塞到一個狀態(tài)機中盐数,
我們需要n × m個狀態(tài)棒拂。使用兩個狀態(tài)機,就只有n + m個。
我們保留之前記錄她在做什么的狀態(tài)機帚屉,不用管它谜诫。 然后定義她攜帶了什么的單獨狀態(tài)機
。 Heroine將會有兩個“狀態(tài)”引用攻旦,每個對應一個狀態(tài)機喻旷,就像這樣:
···
class Heroine
{
// 其他代碼……
private:
HeroineState* state_;
HeroineState* equipment_;
};
···
為了便于說明,她的裝備也使用了狀態(tài)模式牢屋。 在實踐中且预,由于裝備只有兩個狀態(tài)
,一個布爾標識就夠了烙无。
當英雄把輸入委托給了狀態(tài)锋谐,兩個狀態(tài)都需要委托:
···
void Heroine::handleInput(Input input)
{
state_->handleInput(this, input);
equipment_->handleInput(this, input);
}
···
功能更完備的系統(tǒng)也許能讓狀態(tài)機銷毀輸入,這樣其他狀態(tài)機就不會收到了截酷。 這
能阻止兩個狀態(tài)機響應同一輸入涮拗。
每個狀態(tài)機之后都能響應輸入,發(fā)生行為迂苛,獨立于其它機器改變狀態(tài)三热。 當兩個狀態(tài)集合幾
乎沒有聯(lián)系的時候,它工作得不錯灾部。
在實踐中康铭,你會發(fā)現(xiàn)狀態(tài)有時需要交互惯退。 舉個例子赌髓,也許她在跳躍時不能開火,或者她在
持槍時不能跳斬攻擊催跪。 為了完成這個锁蠕,你也許會在狀態(tài)的代碼中做一些粗糙的if測試其他
狀態(tài)來協(xié)同, 這不是最優(yōu)雅的解決方案懊蒸,但這可以搞定工作荣倾。
···
// 定義角色狀態(tài)
enum CharacterState {
Idle,
Moving,
Attacking,
Ducking,
Jumping,
}
// 定義角色裝備狀態(tài)
enum EquipmentState {
None,
Gun,
}
// 定義角色類
class Character extends cc.Component {
private _stateStack: CharacterState[] = [CharacterState.Idle]; // 角色狀態(tài)棧
private _equipmentStack: EquipmentState[] = [EquipmentState.None]; // 角色裝備狀態(tài)棧
private _isFiring: boolean = false; // 是否正在開火
private _moveSpeed: number = 100; // 移動速度
private _fireInterval: number = 0.5; // 開火間隔時間
private _fireTimer: number = 0; // 開火計時器
constructor() {
super();
}
update(dt: number) {
this._fireTimer += dt;
}
// 切換角色狀態(tài)方法
private changeCharacterState(newState: CharacterState) {
if (this._stateStack[this._stateStack.length - 1] === newState) {
return;
}
this._stateStack.push(newState);
switch (newState) {
case CharacterState.Idle:
this.stopMoving();
break;
case CharacterState.Moving:
this.startMoving();
break;
case CharacterState.Attacking:
this.startAttacking();
break;
case CharacterState.Ducking:
this.startDucking();
break;
case CharacterState.Jumping:
this.startJumping();
break;
}
}
// 結束當前角色狀態(tài)方法
private endCurrentCharacterState() {
const currentState = this._stateStack.pop()!;
switch (currentState) {
case CharacterState.Idle:
break;
case CharacterState.Moving:
this.stopMoving();
break;
case CharacterState.Attacking:
this.stopAttacking();
break;
case CharacterState.Ducking:
this.stopDucking();
break;
case CharacterState.Jumping:
this.stopJumping();
break;
}
}
// 切換角色裝備狀態(tài)方法
private changeEquipmentState(newState: EquipmentState) {
if (this._equipmentStack[this._equipmentStack.length - 1] === newState) {
return;
}
this._equipmentStack.push(newState);
switch (newState) {
case EquipmentState.None:
this.stopFiring();
break;
case EquipmentState.Gun:
this.startFiring();
break;
}
}
// 結束當前角色裝備狀態(tài)方法
private endCurrentEquipmentState() {
const currentState = this._equipmentStack.pop()!;
switch (currentState) {
case EquipmentState.None:
break;
case EquipmentState.Gun:
this.stopFiring();
break;
}
}
// 開始移動方法
private startMoving() {
// TODO:播放移動動畫等操作
}
// 停止移動方法
private stopMoving() {
// TODO:播放停止動畫等操作
}
// 開始攻擊方法
private startAttacking() {
if (!this._isFiring && this._fireTimer >= this._fireInterval) {
this._isFiring = true;
this._fireTimer = 0;
// TODO:播放攻擊動畫等操作,并在攻擊結束后調用 stopAttacking 方法停止攻擊
}
}
// 停止攻擊方法
private stopAttacking() {
this._isFiring = false;
// TODO:停止播放攻擊動畫等操作
}
// 開始俯臥方法
private startDucking() {
// TODO:播放俯臥動畫等操作骑丸,并在俯臥結束后調用 stopDucking 方法停止俯臥
}
// 停止俯臥方法
private stopDucking() {
// TODO:停止播放俯臥動畫等操作
}
// 開始跳躍方法
private startJumping() {
// TODO:播放跳躍動畫等操作舌仍,并在跳躍結束后調用 stopJumping 方法停止跳躍
}
// 停止跳躍方法
private stopJumping() {
// TODO:停止播放跳躍動畫等操作
}
// 開始開火方法
private startFiring() {
if (!this._isFiring && this._fireTimer >= this._fireInterval) {
this._isFiring = true;
this._fireTimer = 0;
// TODO:播放開火動畫等操作,并在開火結束后調用 stopFiring 方法停止開火
}
}
// 停止開火方法
private stopFiring() {
this._isFiring = false;
// TODO:停止播放開火動畫等操作
}
// 移動方法
public move(direction: cc.Vec3) {
if (direction.mag() > 0) {
if (this._stateStack[this._stateStack.length - 1] !== CharacterState.Moving) {
this.changeCharacterState(CharacterState.Moving);
}
this.node.position = this.node.position.add(direction.normalize().mul(this._moveSpeed));
} else {
if (this._stateStack[this._stateStack.length - 1] === CharacterState.Moving) {
this.endCurrentCharacterState();
}
}
}
// 切換裝備方法
public switchEquipment(equipment: EquipmentState) {
if (this._equipmentStack[this._equipmentStack.length - 1] !== equipment) {
this.endCurrentEquipmentState();
this.changeEquipmentState(equipment);
}
}
// 開火方法
public fire() {
if (this._equipmentStack[this._equipmentStack.length - 1] === EquipmentState.Gun) {
if (this._stateStack[this._stateStack.length - 1] !== CharacterState.Attacking) {
this.changeCharacterState(CharacterState.Attacking);
}
}
}
// 跳躍方法
public jump() {
if (this._stateStack[this._stateStack.length - 1] !== CharacterState.Jumping) {
this.changeCharacterState(CharacterState.Jumping);
}
}
// 俯臥方法
public duck() {
if (this._stateStack[this._stateStack.length - 1] !== CharacterState.Ducking) {
this.changeCharacterState(CharacterState.Ducking);
}
}
// 站起方法
public standUp() {
if (this._stateStack[this._stateStack.length - 1] === CharacterState.Ducking) {
this.endCurrentCharacterState();
}
}
}
···
在上面的代碼中通危,我們定義了一個名為 Character 的角色類铸豁,實現(xiàn)了并發(fā)狀態(tài)機。我們使用兩個狀態(tài)棧 _stateStack 和 _equipmentStack 分別表示角色的行為狀態(tài)和裝備狀態(tài)菊碟。在 update 方法中节芥,我們更新開火計時器 _fireTimer 的值。在 changeCharacterState 和 endCurrentCharacterState 方法中,我們根據(jù)新狀態(tài)切換角色的行為头镊,并在需要時播放相應的動畫蚣驼。在 changeEquipmentState 和 endCurrentEquipmentState 方法中,我們根據(jù)新裝備切換角色的裝備狀態(tài)相艇,并在需要時播放相應的動畫颖杏。在 move、fire厂捞、jump输玷、duck 和 standUp 方法中,我們切換角色的狀態(tài)靡馁,并在需要時播放相應的動畫欲鹏。需要注意的是,在實際開發(fā)中臭墨,我們還需要根據(jù)具體需求進行修改和優(yōu)化赔嚎,并添加相應的條件和轉換條件。同時胧弛,在使用并發(fā)狀態(tài)機時尤误,我們需要小心處理狀態(tài)之間的交互和沖突問題,并盡可能地保持代碼簡潔和易于維護结缚。