Skip to content
优化显示效果

优化显示效果

预计阅读时间 25 分钟

本章我们要使用 Tween 来优化家具显示效果,让游戏更加生动。并将建筑物分组,实现解锁完前置家具才能接受后续家具的逻辑。

使用 Tween 优化动画显示效果

打开上一章编写的 BuildInfo 脚本:

  • onUpdate 方法默认是关闭的,我们可以在 onStart 方法中将 useUpdate 设置位 true 来开启,开启之后编辑器就会自动每帧调用 onUpdate 函数了。
TypeScript
// 开启服务端的 onUpdate 函数开关
this.useUpdate = true;
// 开启服务端的 onUpdate 函数开关
this.useUpdate = true;
  • 接着在 onUpdate 方法中开启 Tween 更新
TypeScript
protected onUpdate(DeltaTime: number): void {
        TweenUtil.TWEEN.update();
}
protected onUpdate(DeltaTime: number): void {
        TweenUtil.TWEEN.update();
}
  • 新增 showBuild 方法,用来显示建筑和实现 tween 动画。我们想让地板由小变大,所以要改变的是 scale 属性。
TypeScript
/**
 * 显示建筑
 */
protected showBuild() {
    // 定义一个tween, 要变动的值是scale缩放系数,设置初始值为 {x: 0, y: 0, z: 0}
    // 设置好初始值后,物体在 Tween 启动时会 scale 设置为初始值
    const tween = new Tween({ scale: Vector.zero });
    // 更改这个物体的缩放系数,并设置用 500 毫秒完成更改,to 函数里的参数指的是最终结果的大小
    // 这里也就是设置为物体原本的大小
    tween.to({ scale: this.gameObject.worldTransform.scale.clone() }, 500);
    // 在 Tween 启动时会调用这个函数,我们在这里显示建筑并开启碰撞
    tween.onStart(() => {
        // 设置物体可见性为可见
        this.gameObject.setVisibility(PropertyStatus.On);
        // 设置物体碰撞为开启碰撞
        (this.gameObject as Model).setCollision(PropertyStatus.On);
    });
    // 这个函数会每帧自动调用,我们在这里更新物体的缩放
    tween.onUpdate(t => { this.gameObject.worldTransform.scale = t.scale; })
    // 动画完成时关闭 useUpdate 减少无用函数调用
    tween.onComplete(() => { this.useUpdate = false });
    // 启动这个 tween 动画
    tween.start();
}
/**
 * 显示建筑
 */
protected showBuild() {
    // 定义一个tween, 要变动的值是scale缩放系数,设置初始值为 {x: 0, y: 0, z: 0}
    // 设置好初始值后,物体在 Tween 启动时会 scale 设置为初始值
    const tween = new Tween({ scale: Vector.zero });
    // 更改这个物体的缩放系数,并设置用 500 毫秒完成更改,to 函数里的参数指的是最终结果的大小
    // 这里也就是设置为物体原本的大小
    tween.to({ scale: this.gameObject.worldTransform.scale.clone() }, 500);
    // 在 Tween 启动时会调用这个函数,我们在这里显示建筑并开启碰撞
    tween.onStart(() => {
        // 设置物体可见性为可见
        this.gameObject.setVisibility(PropertyStatus.On);
        // 设置物体碰撞为开启碰撞
        (this.gameObject as Model).setCollision(PropertyStatus.On);
    });
    // 这个函数会每帧自动调用,我们在这里更新物体的缩放
    tween.onUpdate(t => { this.gameObject.worldTransform.scale = t.scale; })
    // 动画完成时关闭 useUpdate 减少无用函数调用
    tween.onComplete(() => { this.useUpdate = false });
    // 启动这个 tween 动画
    tween.start();
}
  • 新的显示家具函数编写好之后,我们修改原来的 _unlockBuildFun 方法,注释掉显示模型的代码逻辑,改为调用 showBuild 方法显示建筑
TypeScript
this._unlockBuildFun = (other: GameObject) => {
    // 判断进入的对象是 Character 才执行
    if (other instanceof Character) {
        ... 代码略
        // 对象池回收对象
        GameObjPool.despawn(this._unlockBtn);
        // 显示建筑模型 
        // this.gameObject.setVisibility(PropertyStatus.On); 
        this.showBuild(); 
    }
}
this._unlockBuildFun = (other: GameObject) => {
    // 判断进入的对象是 Character 才执行
    if (other instanceof Character) {
        ... 代码略
        // 对象池回收对象
        GameObjPool.despawn(this._unlockBtn);
        // 显示建筑模型 
        // this.gameObject.setVisibility(PropertyStatus.On); 
        this.showBuild(); 
    }
}
  • 查看效果

将建筑分组

接下来将 BuildInfo 脚本拖入到每个需要解锁的家具下,并适当调节各个解锁按钮的相对位置,效果如下:

  • 所有家具家具的解锁按钮正常显示后,接下来我们实现以下效果:

    • 逐个显示家具的解锁按钮。
    • 解锁完当前组所有家具,显示下一组解锁按钮。
  • 打开 BuildInfo 脚本,添加一个新属性 _listener 用来保存解锁事件的监听器。

typescript
/** 事件监听器,需在解锁按钮回收时注销 */
    private _listener: EventListener;
/** 事件监听器,需在解锁按钮回收时注销 */
    private _listener: EventListener;
  • 添加一个新方法 ensureNeeds,用来判断解锁按钮是否满足显示条件
typescript
/**  
 * 验证是否满足显示解锁按钮的需求  
 */  
protected ensureNeeds() {  
    // 满足需求,显示解锁按钮  
    if (++this._curPro >= this.needs) {  
        this.initUnlockBtn();  
        // 注销listener  
        this._listener.disconnect();  
    }  
}
/**  
 * 验证是否满足显示解锁按钮的需求  
 */  
protected ensureNeeds() {  
    // 满足需求,显示解锁按钮  
    if (++this._curPro >= this.needs) {  
        this.initUnlockBtn();  
        // 注销listener  
        this._listener.disconnect();  
    }  
}
  • onStart 方法中,添加对 groupId 不 0 的处理逻辑
typescript
protected onStart(): void {
    // 如果是客户端就直接返回
    if (SystemUtil.isClient()) return;

    // 开启服务端的onUpdate
    this.useUpdate = true;

    // 默认隐藏,并显示第0组解锁建筑按钮
    this.gameObject.setVisibility(PropertyStatus.Off);
    // 关闭碰撞
    (this.gameObject as Model).setCollision(PropertyStatus.Off);
    // 显示组ID是0的组
    if (this.groupId === 0) {
        this.initUnlockBtn();
    } else {  
        // 监听是否显示解锁建筑按钮事件,事件名是 "Show_Unlock_Button" + 组号  
        this._listener = Event.addLocalListener("Show_Unlock_Button" + this.groupId, this.ensureNeeds.bind(this));  
    } 
}
protected onStart(): void {
    // 如果是客户端就直接返回
    if (SystemUtil.isClient()) return;

    // 开启服务端的onUpdate
    this.useUpdate = true;

    // 默认隐藏,并显示第0组解锁建筑按钮
    this.gameObject.setVisibility(PropertyStatus.Off);
    // 关闭碰撞
    (this.gameObject as Model).setCollision(PropertyStatus.Off);
    // 显示组ID是0的组
    if (this.groupId === 0) {
        this.initUnlockBtn();
    } else {  
        // 监听是否显示解锁建筑按钮事件,事件名是 "Show_Unlock_Button" + 组号  
        this._listener = Event.addLocalListener("Show_Unlock_Button" + this.groupId, this.ensureNeeds.bind(this));  
    } 
}
  • 事件监听方法编写完成后,我们需要一个发送事件的方法,找到 showBuild 函数,我们在它动画播放完成后发送一条事件,让程序判断是否显示下一个解锁按钮
typescript
protected showBuild() {
    // 定义一个tween,要变动的值是scale缩放系数,初始值是{x: 0, y: 0, z: 0}
    const tween = new Tween({ scale: Vector.zero });
    // 变道这个解锁建筑默认的缩放大小,并设置用500毫秒完成这个动画
    tween.to({ scale: this.gameObject.worldTransform.scale.clone() }, 500);
    // 设置在启动时,显示这个建筑
    tween.onStart(() => {
        // 显示
        this.gameObject.setVisibility(PropertyStatus.On);
        // 开启碰撞
        (this.gameObject as Model).setCollision(PropertyStatus.On);
    });
    // 设置每帧更新时,更新它的缩放
    tween.onUpdate(t => { this.gameObject.worldTransform.scale = t.scale; });
    // 动画完成时关闭useUpdate
    tween.onComplete(() => {
        // 动画播放完成,显示下一组解锁按钮  
        Event.dispatchToLocal("Show_Unlock_Button" + (this.groupId + 1));  
        this.useUpdate = false;
    });
    // 启动这个tween动画
    tween.start();
}
protected showBuild() {
    // 定义一个tween,要变动的值是scale缩放系数,初始值是{x: 0, y: 0, z: 0}
    const tween = new Tween({ scale: Vector.zero });
    // 变道这个解锁建筑默认的缩放大小,并设置用500毫秒完成这个动画
    tween.to({ scale: this.gameObject.worldTransform.scale.clone() }, 500);
    // 设置在启动时,显示这个建筑
    tween.onStart(() => {
        // 显示
        this.gameObject.setVisibility(PropertyStatus.On);
        // 开启碰撞
        (this.gameObject as Model).setCollision(PropertyStatus.On);
    });
    // 设置每帧更新时,更新它的缩放
    tween.onUpdate(t => { this.gameObject.worldTransform.scale = t.scale; });
    // 动画完成时关闭useUpdate
    tween.onComplete(() => {
        // 动画播放完成,显示下一组解锁按钮  
        Event.dispatchToLocal("Show_Unlock_Button" + (this.groupId + 1));  
        this.useUpdate = false;
    });
    // 启动这个tween动画
    tween.start();
}
  • 脚本编写完成后,将解锁建筑分组,在各自的属性面板中设置好其 groupId,以及 needs 属性。
    • 这里将地板的 groupId 设置为 0
    • 三面木墙的 groupId 设置为 1。
    • 屋顶的 groupId 设置为 2,needs 设置为 3 (意思是上一组解锁了 3 个才能显示这个按钮,也就是说必须将墙壁全部解锁了才显示屋顶解锁按钮)。
    • 灯的 groupId 设置为 3,床、凳子、桌子、垫子的 groupId 设置为 4

img

  • 运行游戏,看最终效果

脚本完整代码

现在 BuildInfo 中完整代码如下 (点击展开)
TypeScript
@Component
export default class BuildInfo extends Script {

    /** 显示创建按钮的组别 */
    @Property({ group: "基本信息", tooltip: "组号,用来确认显示建造按钮的组,配置时需保证组号之间是衔接的,即第一组从0开始,第二组就是1" })
    public groupId: number = 0;

    /** 这个建筑解锁按钮的相对位置 */
    @Property({ group: "基本信息", displayName: "解锁按钮的相对位置", tooltip: "指当将这个建筑设置为父节点时,子节点的相对位置relativeLocation" })
    public unlockBtnLoc: Vector = Vector.zero;

    @Property({group: "基本信息", displayName: "需要数量", tooltip: "显示这个解锁按钮组,需要多少前置解锁" })
    public needs: number = 1;

    /** 当前显示解锁按钮组进度 */  
    private _curPro: number = 0;  

    /** 解锁按钮 */
    private _unlockBtn: GameObject;

    /** 事件监听器,需在解锁按钮回收时注销 */  
    private _listener: EventListener;  

    /** 解锁建筑的方法 */ 
    private _unlockBuildFun;

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {
        // 如果是客户端就直接返回
        if (SystemUtil.isClient()) return;

        // 开启服务端的onUpdate
        this.useUpdate = true;

        // 默认隐藏,并显示第0组解锁建筑按钮
        this.gameObject.setVisibility(PropertyStatus.Off);
        // 关闭碰撞
        (this.gameObject as Model).setCollision(PropertyStatus.Off);
        // 显示组ID是0的组
        if (this.groupId === 0) {
            this.initUnlockBtn();
        } else {  
            // 监听是否显示解锁建筑按钮事件,事件名是 "Show_Unlock_Button" + 组号  
            this._listener = Event.addLocalListener("Show_Unlock_Button" + this.groupId, this.ensureNeeds.bind(this));  
        } 
    }

    /**  
     * 验证是否满足显示解锁按钮的需求  
     */  
    protected ensureNeeds() {  
        // 满足需求,显示解锁按钮  
        if (++this._curPro >= this.needs) {  
            this.initUnlockBtn();  
            // 注销listener  
            this._listener.disconnect();  
        }  
    } 

    /**
     * 初始化解锁建筑按钮
     */
    protected async initUnlockBtn() {
        // 注意这儿spawn的AssetID是解锁按钮预制体的id,第二个参数指资源类型,这儿因为是预制体的资源所以传递GameObjPoolSourceType.Prefab
        this._unlockBtn = await GameObjPool.asyncSpawn("D442F26A43DED08F57F592B57CC2B56E", GameObjPoolSourceType.Prefab);
        // 设置按钮的父节点为当前对象
        this._unlockBtn.parent = this.gameObject;
        // 设置按钮的大小
        this._unlockBtn.worldTransform.scale = Vector.one;
        // 设置按钮的相对位置
        this._unlockBtn.localTransform.position = this.unlockBtnLoc;

        this._unlockBuildFun = (other: GameObject) => {
            // 判断进入的对象是一个Character实例才创建
            if (other instanceof Character) {
                // 用完了就先取消绑定
                trigger.onEnter.remove(this._unlockBuildFun);
                // 对象池回收解锁按钮
                GameObjPool.despawn(this._unlockBtn);
                this.showBuild();
            }
        }

        // 拿到解锁按钮预制体下面的触发器
        const trigger = this._unlockBtn.getChildByName("触发器") as Trigger;
        // 绑定触发器的进入事件
        trigger.onEnter.add(this._unlockBuildFun);
    }

    /**
     * 显示建筑
     */
    protected showBuild() {
        // 定义一个tween,要变动的值是scale缩放系数,初始值是{x: 0, y: 0, z: 0}
        const tween = new Tween({ scale: Vector.zero });
        // 变道这个解锁建筑默认的缩放大小,并设置用500毫秒完成这个动画
        tween.to({ scale: this.gameObject.worldTransform.scale.clone() }, 500);
        // 设置在启动时,显示这个建筑
        tween.onStart(() => {
            // 显示
            this.gameObject.setVisibility(PropertyStatus.On);
            // 开启碰撞
            (this.gameObject as Model).setCollision(PropertyStatus.On);
        });
        // 设置每帧更新时,更新它的缩放
        tween.onUpdate(t => { this.gameObject.worldTransform.scale = t.scale; });
        // 动画完成时关闭useUpdate
        tween.onComplete(() => {
            // 动画播放完成,显示下一组解锁按钮  
            Event.dispatchToLocal("Show_Unlock_Button" + (this.groupId + 1));  
            this.useUpdate = false;
        });
        // 启动这个tween动画
        tween.start();
    }

    protected onUpdate(DeltaTime: number): void {
        TweenUtil.TWEEN.update();
    }
}
@Component
export default class BuildInfo extends Script {

    /** 显示创建按钮的组别 */
    @Property({ group: "基本信息", tooltip: "组号,用来确认显示建造按钮的组,配置时需保证组号之间是衔接的,即第一组从0开始,第二组就是1" })
    public groupId: number = 0;

    /** 这个建筑解锁按钮的相对位置 */
    @Property({ group: "基本信息", displayName: "解锁按钮的相对位置", tooltip: "指当将这个建筑设置为父节点时,子节点的相对位置relativeLocation" })
    public unlockBtnLoc: Vector = Vector.zero;

    @Property({group: "基本信息", displayName: "需要数量", tooltip: "显示这个解锁按钮组,需要多少前置解锁" })
    public needs: number = 1;

    /** 当前显示解锁按钮组进度 */  
    private _curPro: number = 0;  

    /** 解锁按钮 */
    private _unlockBtn: GameObject;

    /** 事件监听器,需在解锁按钮回收时注销 */  
    private _listener: EventListener;  

    /** 解锁建筑的方法 */ 
    private _unlockBuildFun;

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {
        // 如果是客户端就直接返回
        if (SystemUtil.isClient()) return;

        // 开启服务端的onUpdate
        this.useUpdate = true;

        // 默认隐藏,并显示第0组解锁建筑按钮
        this.gameObject.setVisibility(PropertyStatus.Off);
        // 关闭碰撞
        (this.gameObject as Model).setCollision(PropertyStatus.Off);
        // 显示组ID是0的组
        if (this.groupId === 0) {
            this.initUnlockBtn();
        } else {  
            // 监听是否显示解锁建筑按钮事件,事件名是 "Show_Unlock_Button" + 组号  
            this._listener = Event.addLocalListener("Show_Unlock_Button" + this.groupId, this.ensureNeeds.bind(this));  
        } 
    }

    /**  
     * 验证是否满足显示解锁按钮的需求  
     */  
    protected ensureNeeds() {  
        // 满足需求,显示解锁按钮  
        if (++this._curPro >= this.needs) {  
            this.initUnlockBtn();  
            // 注销listener  
            this._listener.disconnect();  
        }  
    } 

    /**
     * 初始化解锁建筑按钮
     */
    protected async initUnlockBtn() {
        // 注意这儿spawn的AssetID是解锁按钮预制体的id,第二个参数指资源类型,这儿因为是预制体的资源所以传递GameObjPoolSourceType.Prefab
        this._unlockBtn = await GameObjPool.asyncSpawn("D442F26A43DED08F57F592B57CC2B56E", GameObjPoolSourceType.Prefab);
        // 设置按钮的父节点为当前对象
        this._unlockBtn.parent = this.gameObject;
        // 设置按钮的大小
        this._unlockBtn.worldTransform.scale = Vector.one;
        // 设置按钮的相对位置
        this._unlockBtn.localTransform.position = this.unlockBtnLoc;

        this._unlockBuildFun = (other: GameObject) => {
            // 判断进入的对象是一个Character实例才创建
            if (other instanceof Character) {
                // 用完了就先取消绑定
                trigger.onEnter.remove(this._unlockBuildFun);
                // 对象池回收解锁按钮
                GameObjPool.despawn(this._unlockBtn);
                this.showBuild();
            }
        }

        // 拿到解锁按钮预制体下面的触发器
        const trigger = this._unlockBtn.getChildByName("触发器") as Trigger;
        // 绑定触发器的进入事件
        trigger.onEnter.add(this._unlockBuildFun);
    }

    /**
     * 显示建筑
     */
    protected showBuild() {
        // 定义一个tween,要变动的值是scale缩放系数,初始值是{x: 0, y: 0, z: 0}
        const tween = new Tween({ scale: Vector.zero });
        // 变道这个解锁建筑默认的缩放大小,并设置用500毫秒完成这个动画
        tween.to({ scale: this.gameObject.worldTransform.scale.clone() }, 500);
        // 设置在启动时,显示这个建筑
        tween.onStart(() => {
            // 显示
            this.gameObject.setVisibility(PropertyStatus.On);
            // 开启碰撞
            (this.gameObject as Model).setCollision(PropertyStatus.On);
        });
        // 设置每帧更新时,更新它的缩放
        tween.onUpdate(t => { this.gameObject.worldTransform.scale = t.scale; });
        // 动画完成时关闭useUpdate
        tween.onComplete(() => {
            // 动画播放完成,显示下一组解锁按钮  
            Event.dispatchToLocal("Show_Unlock_Button" + (this.groupId + 1));  
            this.useUpdate = false;
        });
        // 启动这个tween动画
        tween.start();
    }

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