您当前的位置: 首页 >  游戏

cocos小游戏实战-03-FSM有限状态机

发布时间:2022-07-10 23:04:07 ,浏览量:6

FSM有限状态机

Marionette 动画系统文档:https://docs.cocos.com/creator/manual/zh/animation/marionette/

https://web03-1252477692.cos.ap-guangzhou.myqcloud.com/blog/images/QQ%E6%88%AA%E5%9B%BE20220710211957.png

状态机实现人物各方向动画切换

Base/StateMachine.ts

状态机核心抽象类

import { _decorator, Component, Animation, SpriteFrame } from 'cc' import { FSM_PARAMS_TYPE_ENUM } from '../Enum' import State from '../Base/State' import { SubStateMachine } from './SubStateMachine' const { ccclass, property } = _decorator type ParamsValueType = boolean | number export interface IParamsValue { type: FSM_PARAMS_TYPE_ENUM value: ParamsValueType } export const initParamsTrigger = { type: FSM_PARAMS_TYPE_ENUM.TRIGGER, value: false, } export const initParamsNumber = { type: FSM_PARAMS_TYPE_ENUM.NUMBER, value: 0, } @ccclass('StateMachine') export abstract class StateMachine extends Component { // 当前选中的动画 private _currentState: State | SubStateMachine = null // 参数列表 params: Map<string, IParamsValue> = new Map() // 状态机列表 stateMachines: Map<string, State | SubStateMachine> = new Map() // 动画组件 animationComponent: Animation // 需要等待加载的列表 waitingList: Array<Promise<SpriteFrame[]>> = [] get currentState() { return this._currentState } set currentState(newState) { this._currentState = newState this._currentState.run() } getParams(paramsName: string) { if (this.params.has(paramsName)) { return this.params.get(paramsName).value } } setParams(paramsName: string, value: ParamsValueType) { if (this.params.has(paramsName)) { this.params.get(paramsName).value = value this.run() this.resetTrigger() } } resetTrigger() { for (const [_, value] of this.params) { if (value.type === FSM_PARAMS_TYPE_ENUM.TRIGGER) { value.value = false } } } abstract run(): void abstract init(): void } 

Base/State.ts

播放状态机当前动画 行动类

/**
 * 需要知道animationClip
 * 需要播放动画能力animation
 */ import { AnimationClip, animation, Sprite, SpriteFrame } from 'cc' import ResourceManager from '../Runtime/ResourceManager' import { StateMachine } from './StateMachine' const ANIMATION_SPEED = 1 / 8 // 1秒8帧 export default class State { animationClip: AnimationClip constructor( private fsm: StateMachine, private path: string, private wrapMode: AnimationClip.WrapMode = AnimationClip.WrapMode.Normal, ) { this.init() } // 渲染人物 async init() { // 加载资源文件夹 const promise = ResourceManager.Instance.loadDir(this.path) this.fsm.waitingList.push(promise) const spriteFrames = await promise this.animationClip = new AnimationClip() // 设置名称,目的为了判断取消 this.animationClip.name = this.path // 创建一个对象轨道 const track = new animation.ObjectTrack() // 添加轨道路径为Sprite组件 track.path = new animation.TrackPath().toComponent(Sprite).toProperty('spriteFrame') const frames: Array<[number, SpriteFrame]> = spriteFrames.map((item, index) => [index * ANIMATION_SPEED, item]) // 设置一条通道channel的关键帧 track.channel.curve.assignSorted(frames) // 最后将轨道添加到动画剪辑以应用 this.animationClip.addTrack(track) // 整个动画剪辑的周期 帧数*帧率 this.animationClip.duration = frames.length * ANIMATION_SPEED // 循环播放 this.animationClip.wrapMode = this.wrapMode } run() { // 设置动画,defaultClip,并且播放 this.fsm.animationComponent.defaultClip = this.animationClip this.fsm.animationComponent.play() } } 

/Base/SubStateMachine.ts

状态机抽象类,状态机记录所有状态的动画状态,并展示当前需要展示的动画

import State from '../Base/State' import { StateMachine } from './StateMachine' export abstract class SubStateMachine { constructor(public fsm: StateMachine) {} // 当前选中的动画 private _currentState: State = null // 状态机列表 stateMachines: Map<string, State> = new Map() get currentState() { return this._currentState } set currentState(newState) { this._currentState = newState this._currentState.run() } abstract run(): void } 

Base/DirectionSubStateMachine.ts

只是为了抽离run方法,在Idle | Turn SubStateManager,转向动画和方向动画播放

import { _decorator } from 'cc' import { SubStateMachine } from './SubStateMachine' import { DIRECTION_ORDER_ENUM, PARAMS_NAME_ENUM } from '../Enum' const { ccclass, property } = _decorator @ccclass('DirectionSubStateMachine') export class DirectionSubStateMachine extends SubStateMachine { run(): void { const value = this.fsm.getParams(PARAMS_NAME_ENUM.DIRECTION) this.currentState = this.stateMachines.get(DIRECTION_ORDER_ENUM[value as number]) } } 

Scripts/Player/IdleSubStateMachine.ts(TurnSubStateMachine.ts同理)

继承状态机,并给状态机添加 上下左右 动画 状态

import { _decorator, AnimationClip } from 'cc' import { StateMachine } from '../../Base/StateMachine' import { DIRECTION_ENUM } from '../../Enum' import State from '../../Base/State' import { DirectionSubStateMachine } from '../../Base/DirectionSubStateMachine' const { ccclass } = _decorator const BASE_URL = 'texture/player/idle' @ccclass('IdleSubStateMachine') export class IdleSubStateMachine extends DirectionSubStateMachine { constructor(fsm: StateMachine) { super(fsm) // 人物动画,无限播放 this.stateMachines.set(DIRECTION_ENUM.TOP, new State(fsm, `${BASE_URL}/top`, AnimationClip.WrapMode.Loop)) this.stateMachines.set(DIRECTION_ENUM.BOTTOM, new State(fsm, `${BASE_URL}/bottom`, AnimationClip.WrapMode.Loop)) this.stateMachines.set(DIRECTION_ENUM.LEFT, new State(fsm, `${BASE_URL}/left`, AnimationClip.WrapMode.Loop)) this.stateMachines.set(DIRECTION_ENUM.RIGHT, new State(fsm, `${BASE_URL}/right`, AnimationClip.WrapMode.Loop)) } } 

assets/Scripts/Player/PlayerStateMachine.ts

初始化状态机,为状态机添加一些初始动画

import { _decorator, Animation } from 'cc' import { FSM_PARAMS_TYPE_ENUM, PARAMS_NAME_ENUM } from '../../Enum' import State from '../../Base/State' import { initParamsNumber, initParamsTrigger, StateMachine } from '../../Base/StateMachine' import { IdleSubStateMachine } from './IdleSubStateMachine' import { TurnLeftSubStateMachine } from './TurnLeftSubStateMachine' const { ccclass, property } = _decorator @ccclass('PlayerStateMachine') export class PlayerStateMachine extends StateMachine { resetTrigger() { for (const [_, value] of this.params) { if (value.type === FSM_PARAMS_TYPE_ENUM.TRIGGER) { value.value = false } } } // 初始化参数 initParams() { this.params.set(PARAMS_NAME_ENUM.IDLE, initParamsTrigger) this.params.set(PARAMS_NAME_ENUM.TURN_LEFT, initParamsTrigger) this.params.set(PARAMS_NAME_ENUM.DIRECTION, initParamsNumber) } // 初始化状态机 initStateMachine() { // 人物动画,无限播放 this.stateMachines.set(PARAMS_NAME_ENUM.IDLE, new IdleSubStateMachine(this)) // 左转动画,播放一次 this.stateMachines.set(PARAMS_NAME_ENUM.TURN_LEFT, new TurnLeftSubStateMachine(this)) } // 初始化动画 initAnimationEvent() { this.animationComponent.on(Animation.EventType.FINISHED, () => { // 执行完动画需要恢复默认idle动画的白名单 const whiteList = ['turn'] const name = this.animationComponent.defaultClip.name if (whiteList.some(v => name.includes(v))) { this.currentState = this.stateMachines.get(PARAMS_NAME_ENUM.IDLE) } }) } async init() { // 添加动画组件 this.animationComponent = this.addComponent(Animation) this.initParams() this.initStateMachine() this.initAnimationEvent() // 确保资源资源加载 await Promise.all(this.waitingList) } run() { switch (this.currentState) { case this.stateMachines.get(PARAMS_NAME_ENUM.TURN_LEFT): case this.stateMachines.get(PARAMS_NAME_ENUM.IDLE): if (this.params.get(PARAMS_NAME_ENUM.TURN_LEFT).value) { this.currentState = this.stateMachines.get(PARAMS_NAME_ENUM.TURN_LEFT) } else if (this.params.get(PARAMS_NAME_ENUM.IDLE).value) { this.currentState = this.stateMachines.get(PARAMS_NAME_ENUM.IDLE) } else { // 为了触发子状态机的改变 this.currentState = this.currentState } break default: this.currentState = this.stateMachines.get(PARAMS_NAME_ENUM.IDLE) } } } 

Base/EntityManager.ts

抽离一些数据,给下面继承用

import { _decorator, Component, Sprite, UITransform } from 'cc' import { DIRECTION_ENUM, DIRECTION_ORDER_ENUM, ENTITY_STATE_ENUM, PARAMS_NAME_ENUM } from '../Enum' import { IEntity } from '../Levels' import { PlayerStateMachine } from '../Scripts/Player/PlayerStateMachine' import { TILE_HEIGHT, TILE_WIDTH } from '../Scripts/Tile/TileManager' const { ccclass, property } = _decorator @ccclass('EntityManager') export class EntityManager extends Component { // 坐标 x: number = 0 y: number = 0 // 状态机 fsm: PlayerStateMachine // 人物当前方向 private _direction: DIRECTION_ENUM // 人物当前状态 private _state: ENTITY_STATE_ENUM get direction() { return this._direction } set direction(newDirection) { this._direction = newDirection // 设置fsm化动画 this.fsm.setParams(PARAMS_NAME_ENUM.DIRECTION, DIRECTION_ORDER_ENUM[newDirection]) } get state() { return this._state } set state(newState) { this._state = newState this.fsm.setParams(this.state, true) } async init(params: IEntity) { // 渲染人物 const sprite = this.addComponent(Sprite) sprite.sizeMode = Sprite.SizeMode.CUSTOM const transform = this.getComponent(UITransform) transform.setContentSize(TILE_WIDTH * 4, TILE_HEIGHT * 4) // this.fsm = this.addComponent(PlayerStateMachine) // await this.fsm.init() this.x = params.x this.y = params.y this.direction = params.direction // 设置fsm化动画 this.state = params.state // 设置初始方向 this.direction = DIRECTION_ENUM.TOP } update() { // 更新移动位置,由于人物占4个格子居中需要偏移 this.node.setPosition(this.x * TILE_WIDTH - TILE_WIDTH * 1.5, -this.y * TILE_HEIGHT + TILE_HEIGHT * 1.5) } } 

Scripts/Player/PlayerManager.ts

人物移动,改变状态,触发状态机更新

import { _decorator } from 'cc' import { CONTROLLER_ENUM, DIRECTION_ENUM, ENTITY_STATE_ENUM, ENTITY_TYPE_ENUM, EVENT_ENUM } from '../../Enum' import EventManager from '../../Runtime/EventManager' import { PlayerStateMachine } from './PlayerStateMachine' import { EntityManager } from '../../Base/EntityManager' const { ccclass, property } = _decorator @ccclass('PlayerManager') export class PlayerManager extends EntityManager { // 目标坐标 targetX: number = 0 targetY: number = 0 // 速度 private readonly sped = 1 / 10 async init() { this.fsm = this.addComponent(PlayerStateMachine) await this.fsm.init() super.init({ x: 0, y: 0, type: ENTITY_TYPE_ENUM.PLAYER, direction: DIRECTION_ENUM.TOP, // 设置初始方向 state: ENTITY_STATE_ENUM.IDLE, // 设置fsm化动画 }) EventManager.Instance.on(EVENT_ENUM.PLAY_CTRL, this.move, this) } update() { // 更新人物移动坐标数据 this.updateXY() super.update() } // 更新xy,让xy无限趋近于targetX targetY updateXY() { if (this.targetX < this.x) { this.x -= this.sped } else if (this.targetX > this.x) { this.x += this.sped } if (this.targetY < this.y) { this.y -= this.sped } else if (this.targetY > this.y) { this.y += this.sped } if (Math.abs(this.targetY - this.y) <= 0.1 && Math.abs(this.targetX - this.x) <= 0.1) { this.x = this.targetX this.y = this.targetY } } // 人物移动 move(inputDirection: CONTROLLER_ENUM) { switch (inputDirection) { case CONTROLLER_ENUM.BOTTOM: this.targetY += 1 break case CONTROLLER_ENUM.LEFT: this.targetX -= 1 break case CONTROLLER_ENUM.TOP: this.targetY -= 1 break case CONTROLLER_ENUM.RIGHT: this.targetX += 1 break case CONTROLLER_ENUM.TURN_LEFT: // 旋转方向 switch (this.direction) { case DIRECTION_ENUM.TOP: this.direction = DIRECTION_ENUM.LEFT break case DIRECTION_ENUM.LEFT: this.direction = DIRECTION_ENUM.BOTTOM break case DIRECTION_ENUM.BOTTOM: this.direction = DIRECTION_ENUM.RIGHT break case DIRECTION_ENUM.RIGHT: this.direction = DIRECTION_ENUM.TOP break } this.state = ENTITY_STATE_ENUM.IDLE break case CONTROLLER_ENUM.TURN_RIGHT: break } } } 

https://web03-1252477692.cos.ap-guangzhou.myqcloud.com/blog/images/QQ%E6%88%AA%E5%9B%BE20220710225808.png

本节源码地址:

https://gitee.com/yuan30/cramped-room-of-death/tree/day3/

关注
打赏
1688896170
查看更多评论

暂无认证

  • 6浏览

    0关注

    105934博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0580s