Skip to content
存储数据

存储数据

打怪小游戏是一个数值向的游戏,如果没有存档来保存玩家游玩的数据,那么便失去了自身的核心玩法。因为需要有存档,才能够激励玩家上线不断地对自己的角色进行养成。

1.创建玩家模块数据脚本

模块管理可以联合数据中心进行数据处理,我们需要先创建一个模块数据脚本

① 点击“新建脚本”

② 将脚本命名为PlayerModuleData

image-20230922175827137

2.编写PlayerModuleData脚本

该脚本有两个要点

需要存储的数据字段,必须添加装饰器@Decorator.persistence()

initDefaultData函数的作用是初始化数据,当玩家第一次上线时,会使用该数据作为默认值

ts
export class PlayerModuleData extends Subdata {

    @Decorator.persistence()
    public atk: number

    @Decorator.persistence()
    public energy: number

    protected initDefaultData(): void {
        this.atk = 50
        this.energy = 0
    }
}
export class PlayerModuleData extends Subdata {

    @Decorator.persistence()
    public atk: number

    @Decorator.persistence()
    public energy: number

    protected initDefaultData(): void {
        this.atk = 50
        this.energy = 0
    }
}

3.将PlayerModuleData脚本与模块进行关联

我们前面编写PlayerModuleC以及PlayerModuleS的时候,都将本应添加模块数据的位置填入了null,现在我们需要在这几个位置填写上我们的模块数据

服务端模块、客户端模块、注册模块,都需要与PlayerModuleData进行关联

image-20230922180556450

4.向PlayerModuleS中添加存储数据函数

由于存储数据的逻辑,只能在服务端执行生效。所以我们可以向PlayerModuleS中添加一个存储数据的网络方法,来提供给客户端发起存储数据请求。

PlayerModuleS脚本:

ts
import { PlayerModuleC } from "./PlayerModuleC";
import { PlayerModuleData } from "./PlayerModuleData";


export class PlayerModuleS extends ModuleS<PlayerModuleC, PlayerModuleData>{

    protected onStart(): void {
        console.log("角色服务端模块启动")
    }

    public net_playAnim() {
        // 播放攻击动作
        this.currentPlayer.character.loadAnimation("84912").play()
    }

    public net_saveData(atk: number, energy: number) { 
        // 改变数据 
        this.currentData.atk = atk 
        this.currentData.energy = energy 
        // 存储数据 
        this.currentData.save(true) 
    } 
}
import { PlayerModuleC } from "./PlayerModuleC";
import { PlayerModuleData } from "./PlayerModuleData";


export class PlayerModuleS extends ModuleS<PlayerModuleC, PlayerModuleData>{

    protected onStart(): void {
        console.log("角色服务端模块启动")
    }

    public net_playAnim() {
        // 播放攻击动作
        this.currentPlayer.character.loadAnimation("84912").play()
    }

    public net_saveData(atk: number, energy: number) { 
        // 改变数据 
        this.currentData.atk = atk 
        this.currentData.energy = energy 
        // 存储数据 
        this.currentData.save(true) 
    } 
}

5.PlayerModuleC请求存储数据

上一点提到,存储数据只能在服务端执行,所以我们需要让客户端在属性发生变化的位置,通过网络方法让服务端保存玩家的数据。

PlayerModuleC脚本:

在PlayerModuleC中添加了一个函数saveData来专门调用服务端模块的存储数据函数

由于数据发生变化时,都会调用freshUI函数,所以只需要让freshUI函数调用saveData即可

ts
export class PlayerModuleC extends ModuleC<PlayerModuleS, PlayerModuleData>{
    // 省略代码
    ......

    private freshUI() {
        this._upgradeUI.freshInfo(this._nowEnergy, this._nowAtk, this._nowUpgrade)
        this.saveData() 
    }

    private saveData() { 
        this.server.net_saveData(this._nowAtk, this._nowEnergy) 
    } 
}
export class PlayerModuleC extends ModuleC<PlayerModuleS, PlayerModuleData>{
    // 省略代码
    ......

    private freshUI() {
        this._upgradeUI.freshInfo(this._nowEnergy, this._nowAtk, this._nowUpgrade)
        this.saveData() 
    }

    private saveData() { 
        this.server.net_saveData(this._nowAtk, this._nowEnergy) 
    } 
}

6.PlayerModuleC读取数据

有了存档数据后,需要在玩家刚上线的时候,读取到存档数据,并加载到游戏中

完整的PlayerModuleC脚本:

ts
import MonsterScirpt from "../MonsterScirpt";
import UpgradeUI from "../UI/UpgradeUI";
import { PlayerModuleData } from "./PlayerModuleData";
import { PlayerModuleS } from "./PlayerModuleS";


export class PlayerModuleC extends ModuleC<PlayerModuleS, PlayerModuleData>{

    private _nowAtk: number = 50

    private _nowEnergy: number = 0

    private _nowUpgrade: number = 500


    private _upgradeUI: UpgradeUI = null

    protected async onStart(): Promise<void> {
        console.log("角色客户端模块启动")

        // 读取数据 
        await DataCenterC.ready() 

        this._nowAtk = this.data.atk 
        this._nowEnergy = this.data.energy 
        this._nowUpgrade = this._nowAtk * 10 


        this._upgradeUI = UIService.show(UpgradeUI)
        this.freshUI()
    }

    public atk() {
        // 范围检测
        let result = QueryUtil.sphereOverlap(this.localPlayer.character.worldTransform.position, 100, false)
        // 筛选出怪物
        for (let obj of result) {
            if (obj instanceof Character) {
                continue
            }

            if (obj.tag == "Monster") {
                // 让怪物受伤
                let scripts = obj.getScripts()
                for (let script of scripts) {
                    if (script instanceof MonsterScirpt) {
                        let damage = script.hurt(this._nowAtk)
                        this._nowEnergy += damage
                    }
                }
            }
        }

        this.server.net_playAnim()

        // 播放音效
        SoundService.playSound("209818", 1)

        this.freshUI()
    }



    private freshUI() {
        this._upgradeUI.freshInfo(this._nowEnergy, this._nowAtk, this._nowUpgrade)
        this.saveData()
    }


    public upgrade() {
        // 判断能量是否足够进行升级
        if (this._nowEnergy >= this._nowUpgrade) {
            // 扣除能量
            this._nowEnergy -= this._nowUpgrade

            // 每次升级提示10%攻击力
            this._nowAtk = Math.round(this._nowAtk * 1.1)

            // 下一次升级所需要的能量等于当前攻击力的十倍
            this._nowUpgrade = this._nowAtk * 10

            this.freshUI()

            // 播放升级特效
            EffectService.playOnGameObject("160498", this.localPlayer.character, { slotType: HumanoidSlotType.Root })
            // 播放音效
            SoundService.playSound("169179", 1)
        }
    }

    private saveData() {
        this.server.net_saveData(this._nowAtk, this._nowEnergy)
    }
}
import MonsterScirpt from "../MonsterScirpt";
import UpgradeUI from "../UI/UpgradeUI";
import { PlayerModuleData } from "./PlayerModuleData";
import { PlayerModuleS } from "./PlayerModuleS";


export class PlayerModuleC extends ModuleC<PlayerModuleS, PlayerModuleData>{

    private _nowAtk: number = 50

    private _nowEnergy: number = 0

    private _nowUpgrade: number = 500


    private _upgradeUI: UpgradeUI = null

    protected async onStart(): Promise<void> {
        console.log("角色客户端模块启动")

        // 读取数据 
        await DataCenterC.ready() 

        this._nowAtk = this.data.atk 
        this._nowEnergy = this.data.energy 
        this._nowUpgrade = this._nowAtk * 10 


        this._upgradeUI = UIService.show(UpgradeUI)
        this.freshUI()
    }

    public atk() {
        // 范围检测
        let result = QueryUtil.sphereOverlap(this.localPlayer.character.worldTransform.position, 100, false)
        // 筛选出怪物
        for (let obj of result) {
            if (obj instanceof Character) {
                continue
            }

            if (obj.tag == "Monster") {
                // 让怪物受伤
                let scripts = obj.getScripts()
                for (let script of scripts) {
                    if (script instanceof MonsterScirpt) {
                        let damage = script.hurt(this._nowAtk)
                        this._nowEnergy += damage
                    }
                }
            }
        }

        this.server.net_playAnim()

        // 播放音效
        SoundService.playSound("209818", 1)

        this.freshUI()
    }



    private freshUI() {
        this._upgradeUI.freshInfo(this._nowEnergy, this._nowAtk, this._nowUpgrade)
        this.saveData()
    }


    public upgrade() {
        // 判断能量是否足够进行升级
        if (this._nowEnergy >= this._nowUpgrade) {
            // 扣除能量
            this._nowEnergy -= this._nowUpgrade

            // 每次升级提示10%攻击力
            this._nowAtk = Math.round(this._nowAtk * 1.1)

            // 下一次升级所需要的能量等于当前攻击力的十倍
            this._nowUpgrade = this._nowAtk * 10

            this.freshUI()

            // 播放升级特效
            EffectService.playOnGameObject("160498", this.localPlayer.character, { slotType: HumanoidSlotType.Root })
            // 播放音效
            SoundService.playSound("169179", 1)
        }
    }

    private saveData() {
        this.server.net_saveData(this._nowAtk, this._nowEnergy)
    }
}

7.设置存储环境

存储环境是我们必须要进行设置的。因为游戏在PIE(编辑器运行环境)环境下,数据是没有服务器进行托管的,所以只能将数据存储在本地。然而当游戏发布到手机端之后,数据就可以由服务器进行托管,我们就可以通过设置,来让数据进行永久存储。

我们可以在 GameStart脚本 中设置存储环境

ts
import { PlayerModuleC } from "./Module/PlayerModuleC";
import { PlayerModuleData } from "./Module/PlayerModuleData";
import { PlayerModuleS } from "./Module/PlayerModuleS";

@Component
export default class GameStart extends Script {

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

        ModuleService.registerModule(PlayerModuleS, PlayerModuleC, PlayerModuleData)

        if (SystemUtil.isServer()) { 
            DataStorage.setTemporaryStorage(SystemUtil.isPIE) 
        } 
    }

    /**
     * 周期函数 每帧执行
     * 此函数执行需要将this.useUpdate赋值为true
     * @param dt 当前帧与上一帧的延迟 / 秒
     */
    protected onUpdate(dt: number): void {
        // 对Tween进行了驱动
        TweenUtil.TWEEN.update()
    }

    /** 脚本被销毁时最后一帧执行完调用此函数 */
    protected onDestroy(): void {

    }
}
import { PlayerModuleC } from "./Module/PlayerModuleC";
import { PlayerModuleData } from "./Module/PlayerModuleData";
import { PlayerModuleS } from "./Module/PlayerModuleS";

@Component
export default class GameStart extends Script {

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

        ModuleService.registerModule(PlayerModuleS, PlayerModuleC, PlayerModuleData)

        if (SystemUtil.isServer()) { 
            DataStorage.setTemporaryStorage(SystemUtil.isPIE) 
        } 
    }

    /**
     * 周期函数 每帧执行
     * 此函数执行需要将this.useUpdate赋值为true
     * @param dt 当前帧与上一帧的延迟 / 秒
     */
    protected onUpdate(dt: number): void {
        // 对Tween进行了驱动
        TweenUtil.TWEEN.update()
    }

    /** 脚本被销毁时最后一帧执行完调用此函数 */
    protected onDestroy(): void {

    }
}