# 【程序化动画の震动】

震动算法,配合曲线调整震动节奏。

Markdown 图片

ShakeAnimation.ts

module vp 
{
    export type ShakeParams = {
        directionCount: number; // 方向数量(必须大于1) 默认8
        shakeAmount: number; // 变化次数 默认60
        interval: number; // 每次变化时间间隔(毫秒) 默认15
        distance: number; // 变化的幅度 默认 5 
    };

    /** 
     * 抖动的动画
     */
    export class ShakeAnimation
    {
        /** 抖动基础参数 */
        private _sp:ShakeParams;

        /** 变化曲线参数 */
        private _cp:CurveParam;

        /** 初始位置 */
        private _basePos:Phaser.Math.Vector2 = new Phaser.Math.Vector2(0,0);

        public get basePos():Phaser.Math.Vector2
        {
            return this._basePos;
        }

        /** 抖动的偏移数组 */
        private _shakePosArr:Phaser.Math.Vector2[] = [];

        /** 抖动方向数组 */
        private _shakeDirectionArr:Phaser.Math.Vector2[] = [];

        /** 设置为未播放中 */
        private _isPlaying:boolean = false;

        public get isPlaying():boolean
        {
            return this._isPlaying;
        }

        /** 开始播放的时间 */
        private _startPlayTime:number = 0;
        
        /** 当前的时间 */
        private _curTime:number = 0;

        /** 当前输出 */
        private _curResult:Phaser.Math.Vector2 = new Phaser.Math.Vector2();

        /** 当前抖动的偏移 */
        private get curShakePos():Phaser.Math.Vector2 | undefined
        {
            const len = this._shakePosArr.length;
            if(len < 1)return undefined;
            const curProgress = this.curProgress;
            if(curProgress >= 1){
                return undefined;// 已经播放完毕
            }
            const idx = Math.floor(len * curProgress);
            return this._shakePosArr[idx];
        }

        private _curProgress:number = 0;

        /** 当前播放的进度 0 ~ 1 */
        public get curProgress():number
        {
            const total = this.duraction;
            const overTime = this._curTime - this._startPlayTime;
            let progress = overTime / total;
            if(progress < 0)progress = 0;
            if(progress > 1)progress = 1;
            this._curProgress = progress;
            return progress;
        }

        /** 总持续的时间,单位毫秒 */
        public get duraction():number
        {
            return this._sp.interval * this._sp.shakeAmount;
        }

        public init(sp:ShakeParams,cp:CurveParam,baseX:number,baseY:number):void
        {
            this._isPlaying = false;
            this._sp = sp;
            this._cp = cp;
            this._basePos.x = baseX;
            this._basePos.y = baseY;

            this._shakeDirectionArr = [];
            const dc = this._sp.directionCount;
            const pi = 3.1415926536;
            for (let i = 0; i < dc; i++) {
                const radin = (i / (dc - 1)) * pi;
                const dir = new Phaser.Math.Vector2();
                dir.x = Math.cos(radin);
                dir.y = Math.sin(radin);
                this._shakeDirectionArr.push(dir);
            }

            const dirLen = this._shakeDirectionArr.length;
            this._shakePosArr = [];
            const shakeAmount = this._sp.shakeAmount;
            const distance = this._sp.distance;
            for (let i = 0; i < shakeAmount; i++) {
                const shakePos = new Phaser.Math.Vector2();
                const dirIndex = i % dirLen;
                const dir = this._shakeDirectionArr[dirIndex];
                shakePos.x = dir.x * distance;
                shakePos.y = dir.y * distance;
                this._shakePosArr.push(shakePos);
            }

            // 打乱抖动数组
            this._shakePosArr.sort(() => Math.random() - 0.5);
        }

        public play():void
        {
            this._startPlayTime = new Date().getTime();
            this._curTime = this._startPlayTime;
            this._isPlaying = true;
        }

        public stop():void
        {
            this._isPlaying = false;
        }

        /** 
         * 
         * time 当前的时间
         * delta 与上一帧的时间差,单位毫秒
         * 
         */
        public update(delta: number):Phaser.Math.Vector2
        {
            if(!this._isPlaying)return;
            this._curTime += delta;
            const cur = this.curShakePos;
            if(!cur){
                // 播放完毕
                this._isPlaying = false;
                this._curResult.x = this._basePos.x;
                this._curResult.y = this._basePos.y;
                return this._curResult;
            }
            let value = this._curProgress;
            if(this._cp){
                value = CurveLibrary.getCurveResult(value,this._cp);
            }
            const offsetX = this._lerp(0,cur.x,value);
            const offsetY = this._lerp(0,cur.y,value);

            this._curResult.x = this._basePos.x + offsetX;
            this._curResult.y = this._basePos.y + offsetY;
            return this._curResult;
        }

        private _lerp(a:number,b:number,t:number):number
        {
            return (1 - t) * a + t * b;
        }
    }
}

CurveLibrary.ts

module vp 
{
    export type CurveFunc = (x:number)=>number;

    export enum PartType
    {
        BOTH,
        LEFT,
        RIGHT
    }

    export type CurveParam =
    {
        idx:number;
        partType:PartType;
        reverse:boolean;
    };
    

    /** 
     * 曲线预置库
     */
    export class CurveLibrary
    {
        /** 5x7 */
        public static MAX_COUNT:number = 35;

        /** 单排数量 */
        public static X_NUM:number = 7;

        private static _CurveFuncArr: CurveFunc[] = [
            (x)=>(1.0 - Math.pow(Math.abs(x),0.5)),
            (x)=>(1.0 - Math.pow(Math.abs(x),1.0)),
            (x)=>(1.0 - Math.pow(Math.abs(x),1.5)),
            (x)=>(1.0 - Math.pow(Math.abs(x),2.0)),
            (x)=>(1.0 - Math.pow(Math.abs(x),2.5)),
            (x)=>(1.0 - Math.pow(Math.abs(x),3.0)),
            (x)=>(1.0 - Math.pow(Math.abs(x),3.5)),
            (x)=>(Math.pow(Math.cos(3.1415926536 * x / 2.0),0.5)),
            (x)=>(Math.pow(Math.cos(3.1415926536 * x / 2.0),1.0)),
            (x)=>(Math.pow(Math.cos(3.1415926536 * x / 2.0),1.5)),
            (x)=>(Math.pow(Math.cos(3.1415926536 * x / 2.0),2.0)),
            (x)=>(Math.pow(Math.cos(3.1415926536 * x / 2.0),2.5)),
            (x)=>(Math.pow(Math.cos(3.1415926536 * x / 2.0),3.0)),
            (x)=>(Math.pow(Math.cos(3.1415926536 * x / 2.0),3.5)),
            (x)=>(1.0 - Math.pow(Math.abs(Math.sin(3.1415926536 * x / 2.0)),0.5)),
            (x)=>(1.0 - Math.pow(Math.abs(Math.sin(3.1415926536 * x / 2.0)),1.0)),
            (x)=>(1.0 - Math.pow(Math.abs(Math.sin(3.1415926536 * x / 2.0)),1.5)),
            (x)=>(1.0 - Math.pow(Math.abs(Math.sin(3.1415926536 * x / 2.0)),2.0)),
            (x)=>(1.0 - Math.pow(Math.abs(Math.sin(3.1415926536 * x / 2.0)),2.5)),
            (x)=>(1.0 - Math.pow(Math.abs(Math.sin(3.1415926536 * x / 2.0)),3.0)),
            (x)=>(1.0 - Math.pow(Math.abs(Math.sin(3.1415926536 * x / 2.0)),3.5)),
            (x)=>(Math.pow(Math.min(Math.cos(3.1415926536 * x / 2.0),1.0-Math.abs(x)),0.5)),
            (x)=>(Math.pow(Math.min(Math.cos(3.1415926536 * x / 2.0),1.0-Math.abs(x)),1.0)),
            (x)=>(Math.pow(Math.min(Math.cos(3.1415926536 * x / 2.0),1.0-Math.abs(x)),1.5)),
            (x)=>(Math.pow(Math.min(Math.cos(3.1415926536 * x / 2.0),1.0-Math.abs(x)),2.0)),
            (x)=>(Math.pow(Math.min(Math.cos(3.1415926536 * x / 2.0),1.0-Math.abs(x)),2.5)),
            (x)=>(Math.pow(Math.min(Math.cos(3.1415926536 * x / 2.0),1.0-Math.abs(x)),3.0)),
            (x)=>(Math.pow(Math.min(Math.cos(3.1415926536 * x / 2.0),1.0-Math.abs(x)),3.5)),
            (x)=>(1.0 - Math.pow(Math.max(0.0,Math.abs(x) * 2.0 - 1.0),0.5)),
            (x)=>(1.0 - Math.pow(Math.max(0.0,Math.abs(x) * 2.0 - 1.0),1.0)),
            (x)=>(1.0 - Math.pow(Math.max(0.0,Math.abs(x) * 2.0 - 1.0),1.5)),
            (x)=>(1.0 - Math.pow(Math.max(0.0,Math.abs(x) * 2.0 - 1.0),2.0)),
            (x)=>(1.0 - Math.pow(Math.max(0.0,Math.abs(x) * 2.0 - 1.0),2.5)),
            (x)=>(1.0 - Math.pow(Math.max(0.0,Math.abs(x) * 2.0 - 1.0),3.0)),
            (x)=>(1.0 - Math.pow(Math.max(0.0,Math.abs(x) * 2.0 - 1.0),3.5)),
        ];

        private static getCurveFuncWithIndex(idx: number): CurveFunc {
            return this._CurveFuncArr[idx];
        }

        /** 
         * 获取计算结果
         * lineX 在 0 ~ 1之间 
         */
        public static getCurveResult(lineX:number,cp:CurveParam):number
        {
            if(lineX > 1)lineX = 1;
            if(lineX < 0)lineX = 0;
            let remapLineX = lineX;
            if(cp.partType === PartType.BOTH){
                // 把 lineX 映射到 -1 ~ 1
                remapLineX = lineX * 2 - 1;
            }else if(cp.partType === PartType.LEFT){
                // 把 lineX 映射到 -1 ~ 0
                remapLineX = lineX - 1;
            }
            const cf = CurveLibrary.getCurveFuncWithIndex(cp.idx);
            const baseResult = cf(remapLineX);
            if(cp.reverse){
                return 1.0 - baseResult;
            }
            return baseResult;
        }

    }
}