存储数据
打怪小游戏是一个数值向的游戏,如果没有存档来保存玩家游玩的数据,那么便失去了自身的核心玩法。因为需要有存档,才能够激励玩家上线不断地对自己的角色进行养成。
1.创建玩家模块数据脚本
模块管理可以联合数据中心进行数据处理,我们需要先创建一个模块数据脚本
① 点击“新建脚本”
② 将脚本命名为PlayerModuleData
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进行关联
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 {
}
}