aigc实现的并发状态机

参考游戏编程模式
并发状态机
我们决定给英雄拿枪的能力。 当她拿着枪的时候,她还是能做她之前的任何事情:跑动,
跳跃,跳斩,等等。 但是她在做这些的同时也要能开火。
如果我们执着于FSM,我们需要翻倍现有状态。 对于每个现有状态,我们需要另一个她持
枪状态:站立,持枪站立,跳跃,持枪跳跃, 你知道我的意思了吧。
多加几种武器,状态就会指数爆炸。 不但增加了大量的状态,这也增加了大量的冗余: 持
枪和不持枪的状态是完全一样的,只是多了一点负责射击的代码。
问题在于我们将两种状态绑定——她做的和她携带的——到了一个状态机上。 为了处理所有
可能的组合,我们需要为每一对组合写一个状态。 修复方法很明显:使用两个单独的状态
机。
如果她在做什么有n个状态,而她携带了什么有m个状态,要塞到一个状态机中,
我们需要n × m个状态。使用两个状态机,就只有n + m个。
我们保留之前记录她在做什么的状态机,不用管它。 然后定义她携带了什么的单独状态机
。 Heroine将会有两个“状态”引用,每个对应一个状态机,就像这样:
···
class Heroine
{
// 其他代码……
private:
HeroineState* state_;
HeroineState* equipment_;
};
···
为了便于说明,她的装备也使用了状态模式。 在实践中,由于装备只有两个状态
,一个布尔标识就够了。
当英雄把输入委托给了状态,两个状态都需要委托:
···
void Heroine::handleInput(Input input)
{
state_->handleInput(this, input);
equipment_->handleInput(
this, input);
}
···
功能更完备的系统也许能让状态机销毁输入,这样其他状态机就不会收到了。 这
能阻止两个状态机响应同一输入。
每个状态机之后都能响应输入,发生行为,独立于其它机器改变状态。 当两个状态集合几
乎没有联系的时候,它工作得不错。
在实践中,你会发现状态有时需要交互。 举个例子,也许她在跳跃时不能开火,或者她在
持枪时不能跳斩攻击。 为了完成这个,你也许会在状态的代码中做一些粗糙的if测试其他
状态来协同, 这不是最优雅的解决方案,但这可以搞定工作。

···
// 定义角色状态
enum CharacterState {
Idle,
Moving,
Attacking,
Ducking,
Jumping,
}
// 定义角色装备状态
enum EquipmentState {
None,
Gun,
}
// 定义角色类
class Character extends cc.Component {
private _stateStack: CharacterState[] = [CharacterState.Idle]; // 角色状态栈
private _equipmentStack: EquipmentState[] = [EquipmentState.None]; // 角色装备状态栈
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;
}
// 切换角色状态方法
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;
}
}
// 结束当前角色状态方法
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;
}
}
// 切换角色装备状态方法
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;
}
}
// 结束当前角色装备状态方法
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 的角色类,实现了并发状态机。我们使用两个状态栈 _stateStack 和 _equipmentStack 分别表示角色的行为状态和装备状态。在 update 方法中,我们更新开火计时器 _fireTimer 的值。在 changeCharacterState 和 endCurrentCharacterState 方法中,我们根据新状态切换角色的行为,并在需要时播放相应的动画。在 changeEquipmentState 和 endCurrentEquipmentState 方法中,我们根据新装备切换角色的装备状态,并在需要时播放相应的动画。在 move、fire、jump、duck 和 standUp 方法中,我们切换角色的状态,并在需要时播放相应的动画。需要注意的是,在实际开发中,我们还需要根据具体需求进行修改和优化,并添加相应的条件和转换条件。同时,在使用并发状态机时,我们需要小心处理状态之间的交互和冲突问题,并尽可能地保持代码简洁和易于维护。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352