状态机详解

什么是状态机?

状态机(State Machine)是一种数学模型,用于描述对象在不同状态之间的转移和行为,由一组状态、一组事件和一组转换规则构成。
在这里插入图片描述

状态机的基本概念

  1. 状态:在某一时刻对象所处的形态,可以是一个值,也可以是一个抽象的概念。例如在游戏中,角色可以处于行走、跳跃、攻击等状态。
  2. 事件:触发状态转换的条件或信号。例如玩家按下空格键会触发角色从行走状态转换到跳跃状态。
  3. 转换规则:对象在不同状态之间转移条件和行为,当某个事件发生时对象会从一个状态切换到另一个状态。例如从行走状态切换到跳跃状态。

状态机实现

使用Cocos引擎和TypeScript语言来实现一个简单的状态机。实现之前先来看一张状态机的基本执行逻辑图。.
在这里插入图片描述

状态机的核心是一个循环,在循环中根据不同的状态,判断不同的条件,满足条件后切换状态,并执行当前状态下的逻辑。

状态数据

export class StateData {
    //当前状态是否完成
    public finish: boolean = false;
    //状态结果
    public result: number = -1;
    //错误信息
    public errMsg: string = null;
    //重置数据
    public reset(): void {
        this.finish = false;
        this.result = -1;
        this.errMsg = null;
    }
}

状态基类

export interface IState<SD extends StateData> {

    /** 当前状态是否已经开始 */
    started: boolean;

    /** 状态开始的时间 */
    startTime: number;

    /** 当前状态的code */
    code: number;

    /** 重置状态 */
    reset(): void;

    /** 开始执行状态逻辑 */
    start(): void;

    /**
     * 执行状态的初始化逻辑。 
     * @param data 状态数据
     */
    initialize(data: SD): void;

    /**
     * 刷新状态逻辑。
     * @param dt 帧间隔时间
     * @param data 状态数据
     */
    update(dt: number, data: SD): void;

    /**
     * 状态执行结束了。
     * @param data 状态数据
     */
    finish(data: SD): number;
}

状态机对象

class StateWrapper<SD extends StateData> {
    readonly state: IState<SD>;
    readonly data: SD;

    constructor(state: IState<SD>, data: SD) {
        if (state == null || data == null) {
            throw new Error("Invalid state object!");
        }

        this.state = state;
        this.data = data;
    }
}

/**
 * 状态机对象。
 */
export class StateMachine<SD extends StateData> {

    private readonly _states: Map<number, StateWrapper<SD>> = new Map<number, StateWrapper<SD>>();

    private _current: StateWrapper<SD> = null;

    private _running: boolean = false;

    get currentState(): IState<SD> {
        if (this._current == null) {
            return null;
        }

        return this._current.state;
    }
	/**
     * 设置状态机包含的所有状态。
     * @param states 状态列表。
     * @param data 状态数据。
     */
    setStates(states: IState<SD>[], data: SD): void {
        if (!states) {
            return;
        }

        this._states.clear();

        states.forEach((state) => {
            this._states.set(state.code, new StateWrapper<SD>(state, data));
        });
    }

    /**
     * 开始执行状态机的逻辑。
     * @param firstState 第一个状态的code。
     */
    start(firstState: number): void {
        this._running = true;
        this.switchToState(firstState);
    }

    /**
     * 停止执行状态机
     * @param stateCode 
     * @returns 
     */
    stop(): void {
        this._running = false;
    }
	/**
     * 切换到指定的状态。
     * @param stateCode 要切换到的状态的code。
     */
    switchToState(stateCode: number): void {
        let targetState = this._states.get(stateCode);
        if (targetState == null) {
            console.info("Try to switch to invalid state: ", stateCode);
            return;
        }

        this._current = targetState;
        this._current.data.reset();
        this._current.state.reset();

        try {
            this._current.state.start();
            this._current.state.initialize(this._current.data);
        } catch (e: any) {
            this._current.state.onInitializeError(e, this._current.data);
        }
    }

    /**
     * 刷新状态机逻辑。
     */
    update(dt: number): void {
        if (!this._running) {
            return;
        }
        let cur = this._current;
        if (cur == null) {
            return;
        }
        let state = cur.state;
        let data = cur.data;
        // 调用当前状态的Update函数
        state.update(dt, data);
        // 如果当前状态数据标记为完成,切换状态
        if (data != null && data.finish) {
           let nextState = state.finish(data);
           this.switchToState(nextState);
        }
    }
}

示例

protected onStart(): void {
		//创建状态机对象
        this.fsm = new StateMachine();
		//继承IState基类扩展状态
        this.idleState = new BattleRoleDieState(this);
        this.moveState = new BattleRoleMoveState(this);
        this.attackState = new BattleRoleAttackState(this);
		//继承StateData基类扩展数据
        this.stateData = new BattleRoleStateData(this);
        this.fsm.setStates([this.idleState, this.moveState, this.attackState], this.stateData);
        this.fsm.start(BattleRoleStateEnum.IDLE);
    }

当finish为true的时候自动切换到下一个状态,当然也可以手动调用switchToState函数手动切换状态

状态机的优缺点

优点:

  1. 逻辑清晰:状态机将复杂的系统行为分解为简单的状态和状态之间的转换,使系统逻辑更加清晰和易于理解。
  2. 可维护性高:由于状态机的逻辑是模块化的,任何状态的修改或扩展只需在该状态的实现部分进行,不会影响其他部分,增强了代码的可维护性。
  3. 可扩展性强:新的状态和转换可以方便地添加到现有的状态机中,使系统具有良好的扩展性。

缺点:

  1. 状态爆炸:对于复杂系统,状态和转换的数量可能非常庞大,导致状态机图变得复杂难以管理,这被称为“状态爆炸”问题。
  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值