FSM有限状态机
Marionette 动画系统文档:https://docs.cocos.com/creator/manual/zh/animation/marionette/
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 = new Map()
// 状态机列表
stateMachines: Map = new Map()
// 动画组件
animationComponent: Animation
// 需要等待加载的列表
waitingList: Array = []
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 = 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 = 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
}
if (this.targetY this.y) {
this.y += this.sped
}
if (Math.abs(this.targetY - this.y)
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?