Skip to content
更新怪物血量

更新怪物血量

在上一节,我们制作了怪物的血条UI,让其能够正确显示怪物的名字以及怪物的血量。但是血条并不具备更新血量的功能,所以本节课我们就来给血条添加更新逻辑。

1.给MonsterUI添加刷新函数

刷新血条,主要需要改变的是代表HP图片的缩放值,以及血量文本,所以这个函数编写如下:

MonsterUI脚本

此次逻辑添加的要点:

① 添加了一个属性:_maxHP【最大血量】 (因为需要使用它来和当前血量计算血量百分比)

② 添加了一个属性:_hpScale【血条图片缩放值】(因为需要频繁的对图片的缩放值进行赋值,使用缓存变量可以减少内存消耗)

ts
import MonsterUI_Generate from "../ui-generate/MonsterUI_generate";
export default class MonsterUI extends MonsterUI_Generate {
	/**最大血量 */  
    private _maxHP: number  
    /**血条图片缩放值 */  
    private _hpScale: Vector2 = new Vector2(1, 1)  

    public init(name: string, hp: number) {
        this.mName_txt.text = name
        this.mHP_txt.text = hp + "/" + hp
        this._maxHP = hp  
    }


    /**  
     * 刷新血量  
     * @param hp 怪物当前的血量  
     */  
    public freshHP(hp: number) {  
        // 更新血量文本  
        this.mHP_txt.text = hp + "/" + this._maxHP  
        // 计算血条图片的缩放值  
        this._hpScale.x = hp / this._maxHP  
        // 更改血条图片的缩放  
        this.mHP_img.renderScale = this._hpScale  
    }  

}
import MonsterUI_Generate from "../ui-generate/MonsterUI_generate";
export default class MonsterUI extends MonsterUI_Generate {
	/**最大血量 */  
    private _maxHP: number  
    /**血条图片缩放值 */  
    private _hpScale: Vector2 = new Vector2(1, 1)  

    public init(name: string, hp: number) {
        this.mName_txt.text = name
        this.mHP_txt.text = hp + "/" + hp
        this._maxHP = hp  
    }


    /**  
     * 刷新血量  
     * @param hp 怪物当前的血量  
     */  
    public freshHP(hp: number) {  
        // 更新血量文本  
        this.mHP_txt.text = hp + "/" + this._maxHP  
        // 计算血条图片的缩放值  
        this._hpScale.x = hp / this._maxHP  
        // 更改血条图片的缩放  
        this.mHP_img.renderScale = this._hpScale  
    }  

}

2.在客户端随机修改怪物血量

为了测试刷新函数逻辑是否正确,我们可以在 MonsterScript 脚本中添加测试代码进行测试

MonsterScript脚本

在MonsterUI初始化好之后,用setInterval开启一个间隔函数,每秒随机更新血量

ts
import MonsterUI from "./UI/MonsterUI"

@Component
export default class MonsterScirpt extends Script {

    @Property({ displayName: "怪物名" })
    monsterName: string = ""

    @Property({ displayName: "最大血量" })
    maxHP: number = 100

    private monsterUI: MonsterUI = null

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {

        if (SystemUtil.isClient()) {
            // 获取世界UI
            let worldUI = this.gameObject.getChildByName("世界UI") as UIWidget
            // 创建怪物UI
            this.monsterUI = UIService.create(MonsterUI)
            // 将怪物UI显示在世界UI上
            worldUI.setTargetUIWidget(this.monsterUI.uiWidgetBase)

            this.monsterUI.init(this.monsterName, this.maxHP)
            
            setInterval(()=>{  
                this.monsterUI.freshHP(Math.floor(Math.random()*100))  
            },100)  
            
        }
    }
}
import MonsterUI from "./UI/MonsterUI"

@Component
export default class MonsterScirpt extends Script {

    @Property({ displayName: "怪物名" })
    monsterName: string = ""

    @Property({ displayName: "最大血量" })
    maxHP: number = 100

    private monsterUI: MonsterUI = null

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {

        if (SystemUtil.isClient()) {
            // 获取世界UI
            let worldUI = this.gameObject.getChildByName("世界UI") as UIWidget
            // 创建怪物UI
            this.monsterUI = UIService.create(MonsterUI)
            // 将怪物UI显示在世界UI上
            worldUI.setTargetUIWidget(this.monsterUI.uiWidgetBase)

            this.monsterUI.init(this.monsterName, this.maxHP)
            
            setInterval(()=>{  
                this.monsterUI.freshHP(Math.floor(Math.random()*100))  
            },100)  
            
        }
    }
}

代码运行效果如下:

MetaApp20230922-135014

3.在服务端随机修改怪物血量

如果只在客户端修改血量,大家运行两个客户端后就会发现,怪物的血量是不同步的。我们希望打怪游戏中的玩家可以共享各个怪物的血量,所以要让血量同步到各个客户端,最直接的办法就是在服务端修改怪物血量。

3.1.添加"当前血量"属性

为了让各个客户端都能够获取到一个相同的值,所以需要在脚本中添加一个“当前血量”来作为各个客户端的同步目标。

MonsterScript脚本

向脚本中添加了“nowHP”来作为怪物的当前血量,并且在怪物首次初始化的时候,在服务端让这个值等于最大血量

ts
import MonsterUI from "./UI/MonsterUI"

@Component
export default class MonsterScirpt extends Script {

    @Property({ displayName: "怪物名" })
    monsterName: string = ""

    @Property({ displayName: "最大血量" })
    maxHP: number = 100

    nowHP: number = 100 

    private monsterUI: MonsterUI = null

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {

        if (SystemUtil.isClient()) {
            // 获取世界UI
            let worldUI = this.gameObject.getChildByName("世界UI") as UIWidget
            // 创建怪物UI
            this.monsterUI = UIService.create(MonsterUI)
            // 将怪物UI显示在世界UI上
            worldUI.setTargetUIWidget(this.monsterUI.uiWidgetBase)

            this.monsterUI.init(this.monsterName, this.maxHP)

        }

        if (SystemUtil.isServer()) { 
            this.nowHP = this.maxHP 
        } 
    }

}
import MonsterUI from "./UI/MonsterUI"

@Component
export default class MonsterScirpt extends Script {

    @Property({ displayName: "怪物名" })
    monsterName: string = ""

    @Property({ displayName: "最大血量" })
    maxHP: number = 100

    nowHP: number = 100 

    private monsterUI: MonsterUI = null

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {

        if (SystemUtil.isClient()) {
            // 获取世界UI
            let worldUI = this.gameObject.getChildByName("世界UI") as UIWidget
            // 创建怪物UI
            this.monsterUI = UIService.create(MonsterUI)
            // 将怪物UI显示在世界UI上
            worldUI.setTargetUIWidget(this.monsterUI.uiWidgetBase)

            this.monsterUI.init(this.monsterName, this.maxHP)

        }

        if (SystemUtil.isServer()) { 
            this.nowHP = this.maxHP 
        } 
    }

}

3.2.将属性变化同步给客户端

在一个双端脚本里,服务端的属性是不会自动同步给客户端的。在这里我们需要使用到属性装饰器的另一个设置:属性同步(replicated)

我们可以通过属性装饰器,将要进行同步的属性进行标记,这样属性就能够在服务端发生变化时,自动同步给所有客户端

MonsterScript脚本:

① 给属性装饰器的"replicated"字段赋值为true,即为开启属性同步

② 给属性装饰器的"onChanged"字段赋值为"onHPChanged",效果是让函数名为"onHPChanged"的函数,在属性发生变更时,在客户端自动执行。

③ 在onHPChanged函数中,添加了刷新血条UI的逻辑

④ 在服务端开启了一个间隔函数,来对当前血量属性进行随机更改

ts
import MonsterUI from "./UI/MonsterUI"

@Component
export default class MonsterScirpt extends Script {

    @Property({ displayName: "怪物名" })
    monsterName: string = ""

    @Property({ displayName: "最大血量" })
    maxHP: number = 100

    @Property({ replicated: true, onChanged: "onHPChanged" }) 
    nowHP: number = 100

    private monsterUI: MonsterUI = null

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {

        if (SystemUtil.isClient()) {
            // 获取世界UI
            let worldUI = this.gameObject.getChildByName("世界UI") as UIWidget
            // 创建怪物UI
            this.monsterUI = UIService.create(MonsterUI)
            // 将怪物UI显示在世界UI上
            worldUI.setTargetUIWidget(this.monsterUI.uiWidgetBase)

            this.monsterUI.init(this.monsterName, this.maxHP)


        }

        if (SystemUtil.isServer()) {
            this.nowHP = this.maxHP

            setInterval(() => { 
                // 服务端随机更新血量 
                this.nowHP = Math.floor(Math.random() * 100) 
            }, 1000) 
        }
    }

    private onHPChanged() { 
        // 调用血条刷新的逻辑 
        if (this.monsterUI) { 
            this.monsterUI.freshHP(this.nowHP) 
        } 
    } 
    
}
import MonsterUI from "./UI/MonsterUI"

@Component
export default class MonsterScirpt extends Script {

    @Property({ displayName: "怪物名" })
    monsterName: string = ""

    @Property({ displayName: "最大血量" })
    maxHP: number = 100

    @Property({ replicated: true, onChanged: "onHPChanged" }) 
    nowHP: number = 100

    private monsterUI: MonsterUI = null

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {

        if (SystemUtil.isClient()) {
            // 获取世界UI
            let worldUI = this.gameObject.getChildByName("世界UI") as UIWidget
            // 创建怪物UI
            this.monsterUI = UIService.create(MonsterUI)
            // 将怪物UI显示在世界UI上
            worldUI.setTargetUIWidget(this.monsterUI.uiWidgetBase)

            this.monsterUI.init(this.monsterName, this.maxHP)


        }

        if (SystemUtil.isServer()) {
            this.nowHP = this.maxHP

            setInterval(() => { 
                // 服务端随机更新血量 
                this.nowHP = Math.floor(Math.random() * 100) 
            }, 1000) 
        }
    }

    private onHPChanged() { 
        // 调用血条刷新的逻辑 
        if (this.monsterUI) { 
            this.monsterUI.freshHP(this.nowHP) 
        } 
    } 
    
}

效果演示

20230922-141239