import { CoroutineV2 } from "../../CatanEngine/CoroutineV2/CoroutineV2"; import { RandomEx } from "../../Utils/Number/RandomEx"; const { ccclass, property, requireComponent, menu } = cc._decorator; @ccclass("State") export class State { @property() public name: string = ""; @property({ type: cc.AnimationClip }) public clip: cc.AnimationClip = null; @property() public transitionTo: string = ""; } @ccclass @menu("Plug-in/Animation/Animator") @requireComponent(cc.Animation) export class Animator extends cc.Component { @property({ displayName: "Default State" }) public defaultState: string = ""; @property({ type: State }) public states: State[] = []; public nowPlayName: string = ""; _animation: cc.Animation = null; /** 動畫速度 */ private _speed: number = 1; get animation(): cc.Animation { if (this._animation == null) { this._animation = this.node.getComponent(cc.Animation); } return this._animation; } onLoad(): void { if (CC_DEV) { let animationClip: cc.AnimationClip[] = this.animation.getClips(); for (let s of this.states) { let state: State = null; for (let i: number = 0; i < animationClip.length; i++) { const clip: cc.AnimationClip = animationClip[i]; if (s.clip != null && s.clip.name === clip.name) { state = s; break; } } if (state === null) { console.error(`node: ${this.node.name}, anim: ${s.clip?.name}, 動畫沒有掛在Animation上面`); } } } } onEnable(): void { this.stopState(); if (this.defaultState !== "") { this.playState(this.defaultState); } } onDisable(): void { this.stopState(); } /** * runStateAndWait(動作機只會接一次動畫) * @param name 動畫State的名稱 * @param callback callback 沒有transitionTo才會觸發 */ public *runStateAndWait(name: string, callback: Function = null): any { if (name === "") { return; } this.animation.stop(); this.nowPlayName = name; let state: State = null; for (let s of this.states) { if (s.name === name && s.clip != null && s.clip.isValid) { state = s; break; } } if (state == null) { return; } let animationState: cc.AnimationState = this.animation.play(state.clip.name); animationState.speed = this.animation.currentClip.speed * this._speed; this.animation.sample(animationState.name); if (animationState.duration) { yield CoroutineV2.WaitTime(animationState.duration); } if (callback && !state.transitionTo) { callback(); } yield* this.runStateAndWait(state.transitionTo, callback); } /** playState(動作機只會接一次動畫) */ public playState(name: string, callback: Function = null): void { if (!this.node?.activeInHierarchy) { cc.warn(`Animator error name: ${this.node.name}, activeInHierarchy: ${this.node.activeInHierarchy}`); } CoroutineV2.Single(this.runStateAndWait(name, callback)).Start(this); } /** playState(隨機) */ public playRandomState(callback: Function = null): void { let random: number = RandomEx.GetInt(0, this.states.length); let state: State = this.states[random]; this.playState(state.name, callback); } public stopState(): void { CoroutineV2.StopCoroutinesBy(this); this.nowPlayName = ""; } /** * 設定動畫速率(原有動畫的Speed在乘上倍率) * @param speed 速率 */ public SetSpeed(speed: number): void { this._speed = speed; } public getAnimTime(name: string, isGetNext: boolean = false): number { for (let s of this.states) { if (s.name === name && s.clip != null) { let time: number = s.clip.duration / this._speed; if (isGetNext && s.transitionTo !== "") { time += this.getAnimTime(s.transitionTo, true); } return time; } } return null; } /** * 暫停在某時間 * @param name Animator設定的動畫名稱 * @param time 要停的時間點 * @example * this._anim.GotoTimeAndPause(name, 0); */ public GotoTimeAndPause(name: string, time: number = 0): void { let clipName: string = null; for (let s of this.states) { if (s.name === name && s.clip != null) { clipName = s.clip.name; } } if (!clipName) { cc.error(`GotoFrameAndPause get clip error: ${name}`); return; } this.animation.play(clipName); this.animation.stop(clipName); this.animation.setCurrentTime(time); this.animation.sample(clipName); } }