Skip to content
TweenUtil

工具 / TweenUtil

TweenUtil Class

补间(动画)(来自 in-between)是一个概念,允许你以平滑的方式更改对象的属性。 你只需告诉它哪些属性要更改,当补间结束运行时它们应该具有哪些最终值,以及这需要多长时间, 补间引擎将负责计算从起始点到结束点的值。

使用示例: 创建一个名为TweenExample的脚本,放置在对象栏中,打开脚本,将原本内容修改为如下内容,保存并运行游戏,按下G键,会在场景中随机生成一个导弹并按照设定轨迹飞往目的地。
ts
@Component
export default class TweenExample extends Script {

    protected async onStart(): `Promise`<`void`\> {
        this.useUpdate = true;
        if (SystemUtil.isClient()) {
            let char = (await Player.asyncGetLocalPlayer()).character;
            InputUtil.onKeyDown(Keys.G, async () => {
                let daoDan = new DaoDanScript(char.worldTransform.position, new Vector(Math.random() * 1000, Math.random() * 1000, 0));
            })
        }
    }

    protected onUpdate(dt: number): void {
        TweenUtil.TWEEN.update();
    }

}

class DaoDanScript {

    //导弹预制体
    private prefab: GameObject

    //构造函数,new的时候传入一个位置来进行初始化
    constructor(bornPos: Vector, toPos: Vector) {
        this.init(bornPos, toPos)
    }

    //初始化DaoDan
    private async init(bornPos: Vector, toPos: Vector) {
        // 使用对象池来创建预制体,并设置预制体的位置
        const bombAssetId = "44948";
        const fireAssetId = "13404";
        const cubeAssetId = "197386";
        this.prefab = await GameObjPool.asyncSpawn(cubeAssetId, GameObjPoolSourceType.Asset);
        this.prefab.setVisibility(PropertyStatus.Off, false);
        GameObject.asyncSpawn({ guid: bombAssetId }).then(obj => {
            obj.attachToGameObject(this.prefab);
            obj.localTransform.position = new Vector(0, 0, 0);
            obj.localTransform.rotation = new Rotation(0, -90, 0);
        })
        EffectService.playOnGameObject(fireAssetId, this.prefab, {position:new Vector(0, 0, 0), rotation:new Rotation(0, -90, 0), scale: new Vector(2, 2, 2)});
        this.prefab.worldTransform.position = bornPos;
        this.fireToPos(toPos);
    }

    //导弹飞向一个坐标
    private async fireToPos(targetPos: Vector) {
        // 先播放起飞动画
        this.fireReadyAnim()

        // 在目标位置创建一个特效(预警特效)
        EffectService.playAtPosition("155714", targetPos, {loopCount:1});

        // 将起点坐标解构(方便Tween进行过渡)
        let startLoc = { x: this.prefab.worldTransform.position.x, y: this.prefab.worldTransform.position.y, z: this.prefab.worldTransform.position.z }
        // 将目标点坐标解构(方便Tween进行过渡)
        let targetLoc = { x: targetPos.x, y: targetPos.y, z: targetPos.z }

        // 上一帧位置
        let lastPos: Vector = this.prefab.worldTransform.position.clone()
        // 当前帧位置
        let nowPos: Vector = Vector.zero
        // 中间商位置(避免频繁去new Vector)
        let tempPos: Vector = Vector.zero

        // 创建起点为导弹位置的一个Tween
        const newTween = new mw.Tween(this.prefab.worldTransform.position.clone())

        newTween.to({
            // X轴飞行路径(这些路径点可以自由定义)
            x: [
                startLoc.x + 1000 + Math.random() * 2000,
                startLoc.x + 3000 + Math.random() * 2000,
                startLoc.x + 5000 + Math.random() * 2000,
                targetLoc.x],

            // Y轴飞行路径(这些路径点可以自由定义)
            y: [
                startLoc.y + 1000 + Math.random() * 2000,
                startLoc.y + 3000 + Math.random() * 2000,
                startLoc.y + 5000 + Math.random() * 2000,
                targetLoc.y],

            // Z轴飞行路径(这些路径点可以自由定义)
            z: [
                550 + Math.random() * 1000,
                750 + Math.random() * 1000,
                950 + Math.random() * 1000,
                750 + Math.random() * 1000,
                550 + Math.random() * 1000,
                targetLoc.z]
        // 整个过程持续3000毫秒
        }, 3000)

            //Linear插值:完全线性,拐弯没有过渡,直来直去;
Bezier插值:全程平滑,整个过程都被平滑成一条曲线;
CatmullRom插值:拐弯平滑,只在拐弯处进行平滑
            // 使用CatmullRom插值
            .interpolation(TweenUtil.Interpolation.CatmullRom)
            .onUpdate((value) => {
                // 每次循环,将value(过渡值)赋值给tempPos和nowPos用于运算
                tempPos.set(value.x, value.y, value.z)
                nowPos.set(value.x, value.y, value.z)
                // 将火箭的坐标设置为过渡值
                this.prefab.worldTransform.position = tempPos
                // 根据上一帧位置和这一帧位置,计算火箭的实时朝向
                this.prefab.worldRotation = nowPos.subtract(lastPos).toRotation()
                // 将此帧值赋值给lastPos,用于下一次运算
                lastPos.set(value.x, value.y, value.z)
            })

        // 当Tween播放完毕时
        newTween.onComplete(() => {
            const bombEffectId = "7786";
            // 在结束位置播放一个爆炸特效
            EffectService.playEffectAtLocation(bombEffectId, this.prefab.worldTransform.position, 1)
            // 重置火箭的旋转
            this.prefab.worldTransform.rotation = Rotation.zero
            // 将火箭归还给预制体
            GameObjPool.despawn(this.prefab)
        })

        // 等一秒钟再开始播放(是为了等起飞动画播放完)
        setTimeout(() => {
            newTween.start()
        }, 1000);
    }

    //起飞阶段动画
    private fireReadyAnim() {
        let tempRotate: Rotation = Rotation.zero
        let startPos: Vector = this.prefab.worldTransform.position.clone()

        let tweenA = new mw.Tween({ y: 0 }).to({ y: 60 + Math.random() * 30 }, 1000).onUpdate((value) => {
            tempRotate.y = value.y
            this.prefab.worldTransform.rotation = tempRotate
        }).start().easing(TweenUtil.Easing.Cubic.Out)

        let tweenB = new mw.Tween(startPos).to(startPos.clone().add(new mw.Vector(0, 0, Math.random() * 100)), 1000).onUpdate((value) => {
            this.prefab.worldTransform.position = value
        }).start().easing(TweenUtil.Easing.Cubic.Out)
    }
}
@Component
export default class TweenExample extends Script {

    protected async onStart(): `Promise`<`void`\> {
        this.useUpdate = true;
        if (SystemUtil.isClient()) {
            let char = (await Player.asyncGetLocalPlayer()).character;
            InputUtil.onKeyDown(Keys.G, async () => {
                let daoDan = new DaoDanScript(char.worldTransform.position, new Vector(Math.random() * 1000, Math.random() * 1000, 0));
            })
        }
    }

    protected onUpdate(dt: number): void {
        TweenUtil.TWEEN.update();
    }

}

class DaoDanScript {

    //导弹预制体
    private prefab: GameObject

    //构造函数,new的时候传入一个位置来进行初始化
    constructor(bornPos: Vector, toPos: Vector) {
        this.init(bornPos, toPos)
    }

    //初始化DaoDan
    private async init(bornPos: Vector, toPos: Vector) {
        // 使用对象池来创建预制体,并设置预制体的位置
        const bombAssetId = "44948";
        const fireAssetId = "13404";
        const cubeAssetId = "197386";
        this.prefab = await GameObjPool.asyncSpawn(cubeAssetId, GameObjPoolSourceType.Asset);
        this.prefab.setVisibility(PropertyStatus.Off, false);
        GameObject.asyncSpawn({ guid: bombAssetId }).then(obj => {
            obj.attachToGameObject(this.prefab);
            obj.localTransform.position = new Vector(0, 0, 0);
            obj.localTransform.rotation = new Rotation(0, -90, 0);
        })
        EffectService.playOnGameObject(fireAssetId, this.prefab, {position:new Vector(0, 0, 0), rotation:new Rotation(0, -90, 0), scale: new Vector(2, 2, 2)});
        this.prefab.worldTransform.position = bornPos;
        this.fireToPos(toPos);
    }

    //导弹飞向一个坐标
    private async fireToPos(targetPos: Vector) {
        // 先播放起飞动画
        this.fireReadyAnim()

        // 在目标位置创建一个特效(预警特效)
        EffectService.playAtPosition("155714", targetPos, {loopCount:1});

        // 将起点坐标解构(方便Tween进行过渡)
        let startLoc = { x: this.prefab.worldTransform.position.x, y: this.prefab.worldTransform.position.y, z: this.prefab.worldTransform.position.z }
        // 将目标点坐标解构(方便Tween进行过渡)
        let targetLoc = { x: targetPos.x, y: targetPos.y, z: targetPos.z }

        // 上一帧位置
        let lastPos: Vector = this.prefab.worldTransform.position.clone()
        // 当前帧位置
        let nowPos: Vector = Vector.zero
        // 中间商位置(避免频繁去new Vector)
        let tempPos: Vector = Vector.zero

        // 创建起点为导弹位置的一个Tween
        const newTween = new mw.Tween(this.prefab.worldTransform.position.clone())

        newTween.to({
            // X轴飞行路径(这些路径点可以自由定义)
            x: [
                startLoc.x + 1000 + Math.random() * 2000,
                startLoc.x + 3000 + Math.random() * 2000,
                startLoc.x + 5000 + Math.random() * 2000,
                targetLoc.x],

            // Y轴飞行路径(这些路径点可以自由定义)
            y: [
                startLoc.y + 1000 + Math.random() * 2000,
                startLoc.y + 3000 + Math.random() * 2000,
                startLoc.y + 5000 + Math.random() * 2000,
                targetLoc.y],

            // Z轴飞行路径(这些路径点可以自由定义)
            z: [
                550 + Math.random() * 1000,
                750 + Math.random() * 1000,
                950 + Math.random() * 1000,
                750 + Math.random() * 1000,
                550 + Math.random() * 1000,
                targetLoc.z]
        // 整个过程持续3000毫秒
        }, 3000)

            //Linear插值:完全线性,拐弯没有过渡,直来直去;
Bezier插值:全程平滑,整个过程都被平滑成一条曲线;
CatmullRom插值:拐弯平滑,只在拐弯处进行平滑
            // 使用CatmullRom插值
            .interpolation(TweenUtil.Interpolation.CatmullRom)
            .onUpdate((value) => {
                // 每次循环,将value(过渡值)赋值给tempPos和nowPos用于运算
                tempPos.set(value.x, value.y, value.z)
                nowPos.set(value.x, value.y, value.z)
                // 将火箭的坐标设置为过渡值
                this.prefab.worldTransform.position = tempPos
                // 根据上一帧位置和这一帧位置,计算火箭的实时朝向
                this.prefab.worldRotation = nowPos.subtract(lastPos).toRotation()
                // 将此帧值赋值给lastPos,用于下一次运算
                lastPos.set(value.x, value.y, value.z)
            })

        // 当Tween播放完毕时
        newTween.onComplete(() => {
            const bombEffectId = "7786";
            // 在结束位置播放一个爆炸特效
            EffectService.playEffectAtLocation(bombEffectId, this.prefab.worldTransform.position, 1)
            // 重置火箭的旋转
            this.prefab.worldTransform.rotation = Rotation.zero
            // 将火箭归还给预制体
            GameObjPool.despawn(this.prefab)
        })

        // 等一秒钟再开始播放(是为了等起飞动画播放完)
        setTimeout(() => {
            newTween.start()
        }, 1000);
    }

    //起飞阶段动画
    private fireReadyAnim() {
        let tempRotate: Rotation = Rotation.zero
        let startPos: Vector = this.prefab.worldTransform.position.clone()

        let tweenA = new mw.Tween({ y: 0 }).to({ y: 60 + Math.random() * 30 }, 1000).onUpdate((value) => {
            tempRotate.y = value.y
            this.prefab.worldTransform.rotation = tempRotate
        }).start().easing(TweenUtil.Easing.Cubic.Out)

        let tweenB = new mw.Tween(startPos).to(startPos.clone().add(new mw.Vector(0, 0, Math.random() * 100)), 1000).onUpdate((value) => {
            this.prefab.worldTransform.position = value
        }).start().easing(TweenUtil.Easing.Cubic.Out)
    }
}

Table of contents

Properties

TWEEN: TweenGroup
全局补间组单例。在创建补间时,如不特别指定,默认添加到该补间组

Properties

TWEEN

Static TWEEN: TweenGroup

全局补间组单例。在创建补间时,如不特别指定,默认添加到该补间组