Laya 实现一个Timeline动画系统 可添加帧事件

Laya 实现一个Timeline动画系统 可添加帧事件_第1张图片

创建一个 Timeline动画组件

Laya 实现一个Timeline动画系统 可添加帧事件_第2张图片

播放

timelineAni.play();

循环播放

 timelineAni.play(true);

限定 开始和结束帧 内播放

//循环播放 10% ~ 98.7% 这一段时间轴
timelineAni.play(true,0.1,0.987);
//单次播放 10% ~ 98.7% 这一段时间轴
timelineAni.play(false,0.1,0.987);

结束动画帧事件

非循环播放结束时触发


 //once: 执行一次后 自动移除
 timelineAni.once(TimelineSprite.EVENT_ENDED, this, () => {

        });

最后一帧动画事件

动画帧播放到尾帧时触发

 timelineAni.on(TimelineSprite.EVENT_ENDFRAME, this, () => {

        });

//移除事件监听
timelineAni.offAll(TimelineSprite.EVENT_ENDFRAME)

添加帧事件

   /**
     * 添加一个事件
     * @param pos 位置 百分比 0~1
     * @param caller 
     * @param fun 
     * @param once 是否执行一次后自动移除
     */
addEvent(pos: number, caller: any, fun: Function, once: boolean = false)

调用案例


	//例如 攻击动画 可以在攻击动画播放到一半时 调用
    timelineAni.addEvent(0.5, this, () => {

        }, false);

Clone一个动画组件

clone的性能很高 帧数组是共享的

//clone 会拷贝当前动画的所有状态信息 包括缩放 位置 第几帧 但不会有上一个动画的帧事件
timelineAni.clone()

源码.

Timeline动画系统 由 TimelineSprite 和 TimelineFactory 组成

import { TimelineSprite } from "./TimelineSprite";
/**
 * 所有的 TimelineSprite 对象 统一调度管理 
 * 全局只产生一个定时器
 */
export default class TimelineFactory {

    /**
     * 减少内存碎片 使用单例管理一个类申请的所有内存
     */
    private static instance: TimelineFactory;
    private elements: TimelineSprite[] = [];
    /**
     * 隐藏构造方法 不允许自行创建或访问
     */
    private constructor() { };
    /**
     * 不允许业务逻辑自行创建
     */
    private static genSingleton() {
        TimelineFactory.instance = new TimelineFactory();
        Laya.timer.frameLoop(1, TimelineFactory.instance, TimelineFactory.instance.update);
    }

    /**
     * 更新
     */
    private update() {
        if (this.elements.length > 0) {
            let element: TimelineSprite, delta = Laya.timer.delta;
            for (let i = 0; i < this.elements.length; i++) {
                element = this.elements[i];
                element['tm'] += delta;
                if( element['tm'] >= element['interval'] ){
                    element['tm'] -= element['interval'];
                    element['updateFrame']();
                }
            }
        }
    }

    public appendTimeline(timeline: TimelineSprite) {
        let index = this.elements.indexOf(timeline);
        if (index == -1) {
            this.elements.push(timeline);
        }
    }

    public removeTimeline(timelineOrIndex: TimelineSprite | number) {
        if (typeof timelineOrIndex == "number") {

            if (timelineOrIndex >= 0 && timelineOrIndex < this.elements.length) {

                this.elements.splice(timelineOrIndex, 1);
            }
        }
        else {

            let index = this.elements.indexOf(timelineOrIndex);
            if (index != -1) {
                this.elements.splice(index, 1);
            }
        }
    }

    /**
     * 主动释放  
     * 一般情况下 整个游戏生命周期内 都不需要调用 你可以忽视它
     */
    public release() {
        this.elements = [];
        Laya.timer.clear(TimelineFactory.instance, TimelineFactory.instance.update);
    }
}
import TimelineFactory from "./TimelineFactory";
export class TimelineSprite extends Laya.Sprite {


    protected interval: number;
    protected tm: number;
    protected play_begin_pos: number;
    protected play_end_pos: number;
    protected loop: boolean = false;
    protected index: number = 0;
    protected frames: string[] = [];
    protected frameEvent: { [frameID: number]: Laya.Handler[] } = {};
    protected last_frame: Laya.Texture;

    /**
     * 最后一帧派发
     */
    public static readonly EVENT_ENDFRAME = "EVENT_ENDFRAME";
    /**
     * 非循环播放结束时触发
     */
    public static readonly EVENT_ENDED = "EVENT_ENDED";

    /**
     * 构造方法
     * @param frames 动画帧数组
     * @param frameRate 帧率 例如: frameRate = 30 那每帧的间隔就是33.33ms ( 1000 / 30 )
     */
    constructor(frames: string[], frameRate: number = 30) {
        super();
        this.frames = frames;
        this.interval = Math.floor(1000.0 * 1000 / frameRate) / 1000;
        this.tm = 0;
    }

    /**
     * 设置新的动画组
     * @param frames 动画帧数组
     * @param frameRate 帧率 可以不填 采用上一个动画的方案 默认30帧每秒
     */
    setNewAnimationFrames(frames: string[], frameRate?: number) {
        if (frameRate)
            this.interval = Math.floor(1000.0 * 1000 / frameRate) / 1000;
        this.frames = frames;
    }

    /**
     * 播放
     * @param loop 是否循环播放  默认false
     * @param start 开始播放的位置 百分百 0~1  循环中也是以这个为主
     * @param end 结束播放的位置 百分比 0~1
     */
    play(loop: boolean = false, start: number = 0, end: number = 1.0) {
        this.tm = 0;
        this.loop = loop;
        this.play_begin_pos = Math.floor(start * this.frames.length);
        this.play_end_pos = Math.floor(end * this.frames.length);
        this.index = this.play_begin_pos;
        this.last_frame = Laya.loader.getRes(this.frames[this.index]);
        this.add2aniPool();
    }


    /**
     * 添加一个事件
     * @param pos 位置 百分比 0~1
     * @param caller 
     * @param fun 
     * @param once 是否执行一次后自动移除
     */
    addEvent(pos: number, caller: any, fun: Function, once: boolean = false) {
        let frameIndex = Math.floor(pos * this.frames.length);
        let handlers = this.frameEvent[frameIndex];

        if (handlers && handlers.find(handler => {
            return handler.caller == caller && handler.method == fun;
        }) != null) {
            return;
        }
        handlers = this.frameEvent[frameIndex] || [];
        handlers.push(Laya.Handler.create(caller, fun, null, once));
        this.frameEvent[frameIndex] = handlers;
    }

    /**
     * 停止播放
     * 会停留在当前帧
     * 你可以使用 visible = false; 来隐藏 动画展示
     */
    stop() {
        this.removeFromPool();
    }

    /**
     * 
     * 更新帧
     */
    protected updateFrame(): void {
        this.onframeEvent();
        ++this.index;
        if (this.index >= this.play_end_pos) {
            this.event(TimelineSprite.EVENT_ENDFRAME);
            if (!this.loop) {
                this.removeFromPool();
                this.event(TimelineSprite.EVENT_ENDED);
                return;
            }
            else {
                this.index = this.play_begin_pos;
            }
        }
        this.last_frame = Laya.loader.getRes(this.frames[this.index]);
        this.draw();
    }

    protected onframeEvent() {
        const events = this.frameEvent[this.index];
        if (events) {
            for (let i = 0; i < events.length; i++) {
                events[i].run();
                if (events[i].once) {
                    events.splice(i--, 1);
                }
            }
        }
    }

    private draw() {
        this.graphics.clear();
        this.graphics.drawImage(this.last_frame);
    }

    protected add2aniPool() {
        !TimelineFactory['instance'] && TimelineFactory['genSingleton']();
        TimelineFactory['instance'].appendTimeline(this);
    }

    protected removeFromPool() {
        TimelineFactory['instance'] && TimelineFactory['instance'].removeTimeline(this);
    }

    public clone() {
        let obj = new TimelineSprite(this.frames);
        for (let k in this) {
            if (typeof k == "number" || typeof k == "boolean") {
                obj[k] = this[k];
            }
        }
        return obj;
    }
}

Demo

gitee
https://gitee.com/welcome2jcSpace/laya_-time-line_-demo

你可能感兴趣的:(laya,LayaAir,Laya,帧动画,Animation,Timeline)