參考游戲編程模式
并發(fā)狀態(tài)機(jī)
我們決定給英雄拿槍的能力。 當(dāng)她拿著槍的時候,她還是能做她之前的任何事情:跑動,
跳躍,跳斬,等等。 但是她在做這些的同時也要能開火。
如果我們執(zhí)著于FSM,我們需要翻倍現(xiàn)有狀態(tài)。 對于每個現(xiàn)有狀態(tài),我們需要另一個她持
槍狀態(tài):站立,持槍站立,跳躍,持槍跳躍, 你知道我的意思了吧。
多加幾種武器,狀態(tài)就會指數(shù)爆炸。 不但增加了大量的狀態(tài),這也增加了大量的冗余: 持
槍和不持槍的狀態(tài)是完全一樣的,只是多了一點負(fù)責(zé)射擊的代碼。
問題在于我們將兩種狀態(tài)綁定——她做的和她攜帶的——到了一個狀態(tài)機(jī)上。 為了處理所有
可能的組合,我們需要為每一對組合寫一個狀態(tài)。 修復(fù)方法很明顯:使用兩個單獨的狀態(tài)
機(jī)。
如果她在做什么有n個狀態(tài),而她攜帶了什么有m個狀態(tài),要塞到一個狀態(tài)機(jī)中,
我們需要n × m個狀態(tài)。使用兩個狀態(tài)機(jī),就只有n + m個。
我們保留之前記錄她在做什么的狀態(tài)機(jī),不用管它。 然后定義她攜帶了什么的單獨狀態(tài)機(jī)
。 Heroine將會有兩個“狀態(tài)”引用,每個對應(yīng)一個狀態(tài)機(jī),就像這樣:
···
class Heroine
{
// 其他代碼……
private:
HeroineState* state_;
HeroineState* equipment_;
};
···
為了便于說明,她的裝備也使用了狀態(tài)模式。 在實踐中,由于裝備只有兩個狀態(tài)
,一個布爾標(biāo)識就夠了。
當(dāng)英雄把輸入委托給了狀態(tài),兩個狀態(tài)都需要委托:
···
void Heroine::handleInput(Input input)
{
state_->handleInput(this, input);
equipment_->handleInput(this, input);
}
···
功能更完備的系統(tǒng)也許能讓狀態(tài)機(jī)銷毀輸入,這樣其他狀態(tài)機(jī)就不會收到了。 這
能阻止兩個狀態(tài)機(jī)響應(yīng)同一輸入。
每個狀態(tài)機(jī)之后都能響應(yīng)輸入,發(fā)生行為,獨立于其它機(jī)器改變狀態(tài)。 當(dāng)兩個狀態(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;
}
}
// 結(jié)束當(dāng)前角色狀態(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;
}
}
// 結(jié)束當(dāng)前角色裝備狀態(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:播放攻擊動畫等操作,并在攻擊結(jié)束后調(diào)用 stopAttacking 方法停止攻擊
}
}
// 停止攻擊方法
private stopAttacking() {
this._isFiring = false;
// TODO:停止播放攻擊動畫等操作
}
// 開始俯臥方法
private startDucking() {
// TODO:播放俯臥動畫等操作,并在俯臥結(jié)束后調(diào)用 stopDucking 方法停止俯臥
}
// 停止俯臥方法
private stopDucking() {
// TODO:停止播放俯臥動畫等操作
}
// 開始跳躍方法
private startJumping() {
// TODO:播放跳躍動畫等操作,并在跳躍結(jié)束后調(diào)用 stopJumping 方法停止跳躍
}
// 停止跳躍方法
private stopJumping() {
// TODO:停止播放跳躍動畫等操作
}
// 開始開火方法
private startFiring() {
if (!this._isFiring && this._fireTimer >= this._fireInterval) {
this._isFiring = true;
this._fireTimer = 0;
// TODO:播放開火動畫等操作,并在開火結(jié)束后調(diào)用 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)機(jī)。我們使用兩個狀態(tài)棧 _stateStack 和 _equipmentStack 分別表示角色的行為狀態(tài)和裝備狀態(tài)。在 update 方法中,我們更新開火計時器 _fireTimer 的值。在 changeCharacterState 和 endCurrentCharacterState 方法中,我們根據(jù)新狀態(tài)切換角色的行為,并在需要時播放相應(yīng)的動畫。在 changeEquipmentState 和 endCurrentEquipmentState 方法中,我們根據(jù)新裝備切換角色的裝備狀態(tài),并在需要時播放相應(yīng)的動畫。在 move、fire、jump、duck 和 standUp 方法中,我們切換角色的狀態(tài),并在需要時播放相應(yīng)的動畫。需要注意的是,在實際開發(fā)中,我們還需要根據(jù)具體需求進(jìn)行修改和優(yōu)化,并添加相應(yīng)的條件和轉(zhuǎn)換條件。同時,在使用并發(fā)狀態(tài)機(jī)時,我們需要小心處理狀態(tài)之間的交互和沖突問題,并盡可能地保持代碼簡潔和易于維護(hù)。