分层状态机
再充实一下英雄的行为,她可能会有更多相似的状态。 举个例子,她也许有站立,行走,
奔跑,和滑铲状态。在这些状态中,按B跳,按下蹲。
如果使用简单的状态机实现,我们在每个状态中的都重复了代码。 如果我们能够实现一次
,在多个状态间重用就好了。
如果这是面向对象的代码而不是状态机的,在状态间分享代码的方式是通过继承。 我们可
以为“在地面上”定义一个类处理跳跃和速降。 站立,行走,奔跑和滑铲都从它继承,然后
增加各自的附加行为。
它的影响有好有坏。 继承是一种有力的代码重用工具,但也在两块代码间建立了
非常强的耦合。 这是重锤,所以请小心使用。
你会发现,这是个被称为分层状态机的通用结构。 状态可以有父状态(这让它变为子状态
)。 当一个事件进来,如果子状态没有处理,它就会交给链上的父状态。 换言之,它像重
载的继承方法那样运作。
事实上,如果我们使用状态模式实现FSM,我们可以使用继承来实现层次。 定义一个基类
作为父状态:
class OnGroundState : public HeroineState
{
public:
virtual void handleInput(Heroine& heroine, Input input)
{
if (input == PRESS_B)
{
// 跳跃……
}
else if (input == PRESS_DOWN)
{
// 俯卧……
}
}
};
每个子状态继承它:
class DuckingState : public OnGroundState
{
public:
virtual void handleInput(Heroine& heroine, Input input)
{
if (input == RELEASE_DOWN)
{
// 站起……
}
else
{
// 没有处理输入,返回上一层
OnGroundState::handleInput(heroine, input);
}
}
};
这当然不是唯一实现层次的方法。 如果你没有使用GoF的状态模式,这可能不会有用。 相
反,你可以显式的使用状态栈而不是单一状态来表示当前状态的父状态链。
栈顶的状态是当前状态,在他下面是它的直接父状态, 然后是那个父状态的父状态,以此
类推。 当你需要状态的特定行为,你从栈的顶端开始, 然后向下寻找,直到某一个状态处
理了它。(如果到底也没找到,就无视它。)
···
// 定义角色状态
enum CharacterState {
Idle,
Moving,
Attacking,
Ducking,
Jumping,
}
// 定义角色类
class Character extends cc.Component {
private _stateStack: CharacterState[] = [CharacterState.Idle]; // 状态栈
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 changeState(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 endCurrentState() {
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 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:停止播放跳跃动画等操作
}
// 移动方法
public move(direction: cc.Vec3) {
if (direction.mag() > 0) {
if (this._stateStack[this._stateStack.length - 1] !== CharacterState.Moving) {
this.changeState(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.endCurrentState();
}
}
}
// 开火方法
public fire() {
if (this._stateStack[this._stateStack.length - 1] !== CharacterState.Attacking) {
this.changeState(CharacterState.Attacking);
}
}
// 跳跃方法
public jump() {
if (this._stateStack[this._stateStack.length - 1] !== CharacterState.Jumping) {
this.changeState(CharacterState.Jumping);
}
}
// 俯卧方法
public duck() {
if (this._stateStack[this._stateStack.length - 1] !== CharacterState.Ducking) {
this.changeState(CharacterState.Ducking);
}
}
// 站起方法
public standUp() {
if (this._stateStack[this._stateStack.length - 1] === CharacterState.Ducking) {
this.endCurrentState();
}
}
}
···
在上面的代码中,我们定义了一个名为 Character 的角色类,实现了分层状态机。我们使用状态栈 _stateStack 来表示角色的状态,每个状态都可以有一个或多个父状态。在 update 方法中,我们更新开火计时器 _fireTimer 的值。在 changeState 方法中,我们根据新状态切换角色的行为,并在需要时播放相应的动画。在 endCurrentState 方法中,我们结束当前状态,并根据需要停止相应的动画。在 move 方法中,我们根据移动方向更新角色的位置,并根据需要切换角色的状态。在 fire、jump、duck 和 standUp 方法中,我们切换角色的状态,并在需要时播放相应的动画。
需要注意的是,在实际开发中,我们还需要根据具体需求进行修改和优化,并添加相应的条件和转换条件。同时,在使用分层状态机时,我们需要小心使用继承和耦合问题,并尽可能地保持代码简洁和易于维护。