存储玩家数据
预计阅读时间 15 分钟
本章我们要实现玩家存档的功能,将玩家解锁的家居信息与金币数量存储起来。
前面几章我们已经将核心玩法基本完成了,生成金币、消耗金币、购买等逻辑功能都已经完成,本章我们来实现玩家存档功能(数据持久化),将前面的数据保存到服务器。
PlayerData 玩家数据持久化
将一个变量永久存储
在 subData 的子类中,要将一个属性永久保存在存档中可以使用装饰器 @Decorator.persistence()
来修饰它。
- 在 PlayerData 脚本中给玩家拥有的金币数量属性加上装饰器。
TypeScript
/** 金币数量 */
@Decorator.persistence()
public gold: number = 55;
/** 金币数量 */
@Decorator.persistence()
public gold: number = 55;
- 在 PlayerData 脚本中添加玩家的金币增长基数。
TypeScript
/** 金币增长速率 */
@Decorator.persistence()
public goldGrowthRate: number = 1;
/** 金币增长速率 */
@Decorator.persistence()
public goldGrowthRate: number = 1;
- 在 PlayerData 脚本中添加玩家的建筑解锁进度。
TypeScript
/** 当前解锁到的组 */
@Decorator.persistence()
public curGroupId: number = 0;
/** 这个组已解锁的建筑序号列表 */
@Decorator.persistence()
public unlockedIds: number[] = [];
/** 当前解锁到的组 */
@Decorator.persistence()
public curGroupId: number = 0;
/** 这个组已解锁的建筑序号列表 */
@Decorator.persistence()
public unlockedIds: number[] = [];
- 在 PlayerData 脚本中,找到上一节编写的 changeGold 函数,调用 save 函数,将金币保存到存档中,并同步给客户端。
typescript
public changeGold(deltaNum: number) {
this.gold += deltaNum;
// 服务端改变金币,将这个操作同步给客户端
this.syncToClient();
this.onGoldChange.call(this.gold);
// 保存到存档中并同步客户端
this.save(true);
}
public changeGold(deltaNum: number) {
this.gold += deltaNum;
// 服务端改变金币,将这个操作同步给客户端
this.syncToClient();
this.onGoldChange.call(this.gold);
// 保存到存档中并同步客户端
this.save(true);
}
接入建筑解锁信息
- 在 PlayerData 脚本中添加新函数用于改变金币增长基数、更新建筑解锁信息。在 updateBuildUnlockInfo 函数中,我们新加了一个序号的概念,这个序号之后会添加到 BuildInfo 中,用来标记某个家具。
TypeScript
/**
* 更改金币增长基数
* @param deltaNum 改变值
*/
public changeGoldGrowthRate(deltaNum: number) {
this.goldGrowthRate += deltaNum;
// 保存到存档中并同步客户端
this.save(true);
}
/**
* 更新建筑解锁信息
* @param groupId 组id
* @param sId 序号
*/
public updateBuildUnlockInfo(groupId: number, sId: number) {
// 判断当前组号是否与要解锁的组合相同,如果不同说明当前组全部解锁完毕了
if (this.curGroupId != groupId) {
// 解锁组改变,需要先清空解锁建筑列表
this.unlockedIds.length = 0;
this.curGroupId = groupId;
}
// 将新解锁的建筑的序号保存到已解锁数组中
this.unlockedIds.push(sId);
// 保存到存档中并同步客户端
this.save(true);
}
/**
* 更改金币增长基数
* @param deltaNum 改变值
*/
public changeGoldGrowthRate(deltaNum: number) {
this.goldGrowthRate += deltaNum;
// 保存到存档中并同步客户端
this.save(true);
}
/**
* 更新建筑解锁信息
* @param groupId 组id
* @param sId 序号
*/
public updateBuildUnlockInfo(groupId: number, sId: number) {
// 判断当前组号是否与要解锁的组合相同,如果不同说明当前组全部解锁完毕了
if (this.curGroupId != groupId) {
// 解锁组改变,需要先清空解锁建筑列表
this.unlockedIds.length = 0;
this.curGroupId = groupId;
}
// 将新解锁的建筑的序号保存到已解锁数组中
this.unlockedIds.push(sId);
// 保存到存档中并同步客户端
this.save(true);
}
- BuildInfo 中新增序号属性: id,它用来标记家具是某个组中的哪一个。这个序号会存储在数据模块 unlockedIds 数组中。
TypeScript
/** 显示创建按钮的组别 */
@Property({ group: "基本信息", displayName: "序号", tooltip: "这个组的第几个,默认从1开始" })
public id: number = 1;
/** 显示创建按钮的组别 */
@Property({ group: "基本信息", displayName: "序号", tooltip: "这个组的第几个,默认从1开始" })
public id: number = 1;
- 因为我们的建筑物解锁信息存储在了服务器,所以初始化函数也需要更改 。
- 我们创建一个服务端RPC方法,
res_init
,它将用来同步玩家刚上线时,建筑物的解锁状态。 - 我们创建一个
req_init
函数,在客户端准备完成后,用来向服务器请求初始化家具。 - 这两个函数替代了上一节写在 onPlayerEnter 函数的玩家上线建筑同步逻辑,所以这里将 onPlayerEnter 中的代码删除掉,同样把之前在 onStart 中解锁默认家居的方法删除掉。
TypeScript
protected onStart(): void {
// 客户端请求建筑 解锁信息来初始化
if (SystemUtil.isClient()) {
this.req_init();
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));
// }
// 监听是否显示解锁建筑按钮事件,事件名是 "Show_Unlock_Button" + 组号
this._listener = Event.addLocalListener("Show_Unlock_Button" + this.groupId, this.ensureNeeds.bind(this)); // [!code focus] // [!code ++]
}
/** 服务端响应客户端初始化 */
@RemoteFunction(Server)
public async res_init(curGroupId: number, unlockedIds: number[]) {
// 如果当前要解锁的组小于已经解锁完毕的组 说明这个组已经全部解锁了
if (curGroupId > this.groupId) {
this.gameObject.setVisibility(PropertyStatus.On);
(this.gameObject as Model).setCollision(PropertyStatus.On);
}
if (curGroupId === this.groupId) {
if (unlockedIds.includes(this.id)) {
// 如果解锁列表中包含这个家居的序号,说明已经解锁了 我们将它显示出来 并且发送事件通知解锁下一个家居的解锁按钮
this.gameObject.setVisibility(PropertyStatus.On);
(this.gameObject as Model).setCollision(PropertyStatus.On);
// 显示下一组解锁按钮
Event.dispatchToLocal("Show_Unlock_Button" + (this.groupId + 1));
} else {
this.initUnlockBtn();
}
}
}
/** 请求客户端的解锁信息来完成初始化 */
public async req_init() {
// 等到客户端的数据中心准备好
await DataCenterC.ready();
const playerData = DataCenterC.getData(PlayerData);
this.res_init(playerData.curGroupId, playerData.unlockedIds);
}
/**
* 玩家进入房间,初始化已经显示出来的世界UI
* @param player 上线的玩家
*/
protected onPlayerEnter(player: Player) {
// 当前建筑按钮显示且当前建筑隐藏
// if (this._unlockBtn && !this.gameObject.getVisibility()) {
// this.initWorldUIOnlyOne(player, this._unlockBtn.guid);
// }
}
protected onStart(): void {
// 客户端请求建筑 解锁信息来初始化
if (SystemUtil.isClient()) {
this.req_init();
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));
// }
// 监听是否显示解锁建筑按钮事件,事件名是 "Show_Unlock_Button" + 组号
this._listener = Event.addLocalListener("Show_Unlock_Button" + this.groupId, this.ensureNeeds.bind(this)); // [!code focus] // [!code ++]
}
/** 服务端响应客户端初始化 */
@RemoteFunction(Server)
public async res_init(curGroupId: number, unlockedIds: number[]) {
// 如果当前要解锁的组小于已经解锁完毕的组 说明这个组已经全部解锁了
if (curGroupId > this.groupId) {
this.gameObject.setVisibility(PropertyStatus.On);
(this.gameObject as Model).setCollision(PropertyStatus.On);
}
if (curGroupId === this.groupId) {
if (unlockedIds.includes(this.id)) {
// 如果解锁列表中包含这个家居的序号,说明已经解锁了 我们将它显示出来 并且发送事件通知解锁下一个家居的解锁按钮
this.gameObject.setVisibility(PropertyStatus.On);
(this.gameObject as Model).setCollision(PropertyStatus.On);
// 显示下一组解锁按钮
Event.dispatchToLocal("Show_Unlock_Button" + (this.groupId + 1));
} else {
this.initUnlockBtn();
}
}
}
/** 请求客户端的解锁信息来完成初始化 */
public async req_init() {
// 等到客户端的数据中心准备好
await DataCenterC.ready();
const playerData = DataCenterC.getData(PlayerData);
this.res_init(playerData.curGroupId, playerData.unlockedIds);
}
/**
* 玩家进入房间,初始化已经显示出来的世界UI
* @param player 上线的玩家
*/
protected onPlayerEnter(player: Player) {
// 当前建筑按钮显示且当前建筑隐藏
// if (this._unlockBtn && !this.gameObject.getVisibility()) {
// this.initWorldUIOnlyOne(player, this._unlockBtn.guid);
// }
}
- 在解锁建筑时,需要保存当前解锁的建筑以及持久化金币增长基数,我们在 PlayerModuleS 中添加持久化的方法
typescript
import { PlayerData } from "./PlayerData";
import { PlayerModuleC } from "./PlayerModuleC";
export class PlayerModuleS extends ModuleS<PlayerModuleC, PlayerData> {
/**
* 客户端改变金币的rpc方法
* @param deltaNum 要改变的数量
*/
public net_changeGold(deltaNum: number): boolean {
return this.changeGold(this.currentPlayerId, deltaNum);
}
public net_changeGoldGrowthRate(deltaNum: number): void {
this.changeGoldGrowthRate(this.currentPlayerId, deltaNum);
}
public net_updateBuildUnlockInfo(groupId: number, sId: number): void {
this.updateBuildUnlockInfo(this.currentPlayerId, groupId, sId);
}
/**
* 改变金币
* @param pid 要改变金币数量的玩家id
* @param deltaNum 改变的数量
*/
public changeGold(pid: number, deltaNum: number): boolean {
// 获取玩家pid的数据
const data = this.getPlayerData(pid);
// 要改变的值是负数,且钱不够
if (deltaNum < 0 && data.gold < Math.abs(deltaNum)) {
return false;
}
data.changeGold(deltaNum);
return true;
}
/**
* 改变金币增长速率
* @param pid 要改变金币数量的玩家id
* @param deltaNum 改变的数量
*/
public changeGoldGrowthRate(pid: number, deltaNum: number) {
// 获取玩家pid的数据
const data = this.getPlayerData(pid);
data.changeGoldGrowthRate(deltaNum);
}
/**
* 服务端更新建筑解锁信息
* @param pid 玩家id
* @param groupId 组号
* @param sId 序号
*/
public updateBuildUnlockInfo(pid: number, groupId: number, sId: number) {
// 获取玩家pid的数据
const data = this.getPlayerData(pid);
data.updateBuildUnlockInfo(groupId, sId);
}
}
import { PlayerData } from "./PlayerData";
import { PlayerModuleC } from "./PlayerModuleC";
export class PlayerModuleS extends ModuleS<PlayerModuleC, PlayerData> {
/**
* 客户端改变金币的rpc方法
* @param deltaNum 要改变的数量
*/
public net_changeGold(deltaNum: number): boolean {
return this.changeGold(this.currentPlayerId, deltaNum);
}
public net_changeGoldGrowthRate(deltaNum: number): void {
this.changeGoldGrowthRate(this.currentPlayerId, deltaNum);
}
public net_updateBuildUnlockInfo(groupId: number, sId: number): void {
this.updateBuildUnlockInfo(this.currentPlayerId, groupId, sId);
}
/**
* 改变金币
* @param pid 要改变金币数量的玩家id
* @param deltaNum 改变的数量
*/
public changeGold(pid: number, deltaNum: number): boolean {
// 获取玩家pid的数据
const data = this.getPlayerData(pid);
// 要改变的值是负数,且钱不够
if (deltaNum < 0 && data.gold < Math.abs(deltaNum)) {
return false;
}
data.changeGold(deltaNum);
return true;
}
/**
* 改变金币增长速率
* @param pid 要改变金币数量的玩家id
* @param deltaNum 改变的数量
*/
public changeGoldGrowthRate(pid: number, deltaNum: number) {
// 获取玩家pid的数据
const data = this.getPlayerData(pid);
data.changeGoldGrowthRate(deltaNum);
}
/**
* 服务端更新建筑解锁信息
* @param pid 玩家id
* @param groupId 组号
* @param sId 序号
*/
public updateBuildUnlockInfo(pid: number, groupId: number, sId: number) {
// 获取玩家pid的数据
const data = this.getPlayerData(pid);
data.updateBuildUnlockInfo(groupId, sId);
}
}
- 在 PlayerModuleC 中也需要添加对应方法
typescript
import MainUI from "../../ui/MainUI";
import { PlayerData } from "./PlayerData";
import { PlayerModuleS } from "./PlayerModuleS";
export class PlayerModuleC extends ModuleC<PlayerModuleS, PlayerData> {
protected override onStart(): void {
// 显示mainUI
UIService.show(MainUI);
}
/**
* 客户端改变金币
* @param deltaNum 要改变的数量
*/
public async changeGold(deltaNum: number): Promise<boolean> {
return this.server.net_changeGold(deltaNum);
}
public changeGoldGrowthRate(deltaNum: number): void {
this.server.net_changeGoldGrowthRate(deltaNum);
}
public updateBuildUnlockInfo(groupId: number, sId: number): void {
this.server.net_updateBuildUnlockInfo(groupId, sId);
}
}
import MainUI from "../../ui/MainUI";
import { PlayerData } from "./PlayerData";
import { PlayerModuleS } from "./PlayerModuleS";
export class PlayerModuleC extends ModuleC<PlayerModuleS, PlayerData> {
protected override onStart(): void {
// 显示mainUI
UIService.show(MainUI);
}
/**
* 客户端改变金币
* @param deltaNum 要改变的数量
*/
public async changeGold(deltaNum: number): Promise<boolean> {
return this.server.net_changeGold(deltaNum);
}
public changeGoldGrowthRate(deltaNum: number): void {
this.server.net_changeGoldGrowthRate(deltaNum);
}
public updateBuildUnlockInfo(groupId: number, sId: number): void {
this.server.net_updateBuildUnlockInfo(groupId, sId);
}
}
- 之后在 BuildInfo 中解锁的地方调 PlayerModuleC 中的方法
TypeScript
/**
* 初始化解锁建筑按钮
*/
protected async initUnlockBtn() {
// 注意这儿spawn的 GameObjectID 是解锁按钮预制体的id,第二个参数指资源类型,这儿因为是预制体的资源所以传递GameObjPoolSourceType.Prefab
this._unlockBtn = await GameObjPool.asyncSpawn("D442F26A43DED08F57F592B57CC2B56E", GameObjPoolSourceType.Prefab);
// 初始化所有玩家的世界UI
this.initWorldUIAllPlayer(this._unlockBtn.guid);
// 防御性编程,防止解锁按钮没创建出来报错阻碍游戏进程
if (this._unlockBtn) {
// 设置按钮的父节点为当前对象
this._unlockBtn.parent = this.gameObject;
// 设置按钮的相对位置
this._unlockBtn.localTransform.position = this.unlockBtnLoc;
this._unlockBuildFun = (other: GameObject) => {
// 判断进入的对象是一个Character实例才创建
if (other instanceof Character) {
// 钱够吗
const isGoldEnough = ModuleService.getModule(PlayerModuleS).changeGold(other.player.playerId, -this.unlockPrice);
// 扣钱成功才显示
if (isGoldEnough) {
// 用完了就先取消绑定
trigger.onEnter.remove(this._unlockBuildFun);
// 对象池回收解锁按钮
GameObjPool.despawn(this._unlockBtn);
// 显示这个模型
this.showBuild();
// 金币增长基数
Event.dispatchToClient(other.player, "GoldGrowthRate", this.profit);
// 持久化增长基数
ModuleService.getModule(PlayerModuleS).changeGoldGrowthRate(other.player.playerId, this.profit);
// 更新解锁建筑组信息
ModuleService.getModule(PlayerModuleS).updateBuildUnlockInfo(other.player.playerId, this.groupId, this.id);
} else {
console.error("钱不够!");
}
}
}
// 拿到解锁按钮预制体下面的触发器
const trigger = this._unlockBtn.getChildByName("触发器") as Trigger;
// 绑定触发器的进入事件
trigger.onEnter.add(this._unlockBuildFun);
} else {
console.error("初始化解锁按钮失败,请检查是不是spawn的id");
}
}
/**
* 初始化解锁建筑按钮
*/
protected async initUnlockBtn() {
// 注意这儿spawn的 GameObjectID 是解锁按钮预制体的id,第二个参数指资源类型,这儿因为是预制体的资源所以传递GameObjPoolSourceType.Prefab
this._unlockBtn = await GameObjPool.asyncSpawn("D442F26A43DED08F57F592B57CC2B56E", GameObjPoolSourceType.Prefab);
// 初始化所有玩家的世界UI
this.initWorldUIAllPlayer(this._unlockBtn.guid);
// 防御性编程,防止解锁按钮没创建出来报错阻碍游戏进程
if (this._unlockBtn) {
// 设置按钮的父节点为当前对象
this._unlockBtn.parent = this.gameObject;
// 设置按钮的相对位置
this._unlockBtn.localTransform.position = this.unlockBtnLoc;
this._unlockBuildFun = (other: GameObject) => {
// 判断进入的对象是一个Character实例才创建
if (other instanceof Character) {
// 钱够吗
const isGoldEnough = ModuleService.getModule(PlayerModuleS).changeGold(other.player.playerId, -this.unlockPrice);
// 扣钱成功才显示
if (isGoldEnough) {
// 用完了就先取消绑定
trigger.onEnter.remove(this._unlockBuildFun);
// 对象池回收解锁按钮
GameObjPool.despawn(this._unlockBtn);
// 显示这个模型
this.showBuild();
// 金币增长基数
Event.dispatchToClient(other.player, "GoldGrowthRate", this.profit);
// 持久化增长基数
ModuleService.getModule(PlayerModuleS).changeGoldGrowthRate(other.player.playerId, this.profit);
// 更新解锁建筑组信息
ModuleService.getModule(PlayerModuleS).updateBuildUnlockInfo(other.player.playerId, this.groupId, this.id);
} else {
console.error("钱不够!");
}
}
}
// 拿到解锁按钮预制体下面的触发器
const trigger = this._unlockBtn.getChildByName("触发器") as Trigger;
// 绑定触发器的进入事件
trigger.onEnter.add(this._unlockBuildFun);
} else {
console.error("初始化解锁按钮失败,请检查是不是spawn的id");
}
}
- 在 MailBox 脚本中,对于玩家进游戏时,等待客户端数据中心准备好后 ,初始化金币增长速率
TypeScript
public async init() {
// 等待这个模型在客户端加载好
await this.gameObject.asyncReady();
// 等到客户端的数据中心准备好
await DataCenterC.ready();
// 初始化金币增长速率
this._alterNum = DataCenterC.getData(PlayerData).goldGrowthRate;
// 拿到世界UI
const worldUI = this.gameObject.getChildByName("世界UI") as UIWidget;
// 拿到targetUI
const targetUI = worldUI.getTargetUIWidget();
// 拿到文本控件
this._goldNumTxt = targetUI.findChildByPath("RootCanvas/goldNumTxt") as TextBlock;
this._addGoldTxt = targetUI.findChildByPath("RootCanvas/addGoldPerSecTxt") as TextBlock;
// 初始化文本
this._goldNumTxt.text = this._goldNum.toString();
this._addGoldTxt.text = this._alterNum + "/秒";
// 定时器
this.inter = setInterval(() => {
this._goldNum += this._alterNum;
this._goldNumTxt.text = this._goldNum.toString();
}, 1000);
const trigger = this.gameObject.getChildByName("触发器") as Trigger;
trigger.onEnter.add(() => {
ModuleService.getModule(PlayerModuleC).changeGold(this._goldNum);
this._goldNum = 0;
this._goldNumTxt.text = this._goldNum.toString();
});
// 监听金币基数改变的事件
Event.addServerListener("GoldGrowthRate", (deltaNum: number) => {
this._alterNum += deltaNum;
this._addGoldTxt.text = this._alterNum + "/秒";
})
}
public async init() {
// 等待这个模型在客户端加载好
await this.gameObject.asyncReady();
// 等到客户端的数据中心准备好
await DataCenterC.ready();
// 初始化金币增长速率
this._alterNum = DataCenterC.getData(PlayerData).goldGrowthRate;
// 拿到世界UI
const worldUI = this.gameObject.getChildByName("世界UI") as UIWidget;
// 拿到targetUI
const targetUI = worldUI.getTargetUIWidget();
// 拿到文本控件
this._goldNumTxt = targetUI.findChildByPath("RootCanvas/goldNumTxt") as TextBlock;
this._addGoldTxt = targetUI.findChildByPath("RootCanvas/addGoldPerSecTxt") as TextBlock;
// 初始化文本
this._goldNumTxt.text = this._goldNum.toString();
this._addGoldTxt.text = this._alterNum + "/秒";
// 定时器
this.inter = setInterval(() => {
this._goldNum += this._alterNum;
this._goldNumTxt.text = this._goldNum.toString();
}, 1000);
const trigger = this.gameObject.getChildByName("触发器") as Trigger;
trigger.onEnter.add(() => {
ModuleService.getModule(PlayerModuleC).changeGold(this._goldNum);
this._goldNum = 0;
this._goldNumTxt.text = this._goldNum.toString();
});
// 监听金币基数改变的事件
Event.addServerListener("GoldGrowthRate", (deltaNum: number) => {
this._alterNum += deltaNum;
this._addGoldTxt.text = this._alterNum + "/秒";
})
}
- 运行游戏,解锁和持有一些金币后,关闭游戏,再次进入游戏,测试存档是否还在。
TIP
为了性能考虑,服务端每 10 秒保存一次存档。在 PC 测试时请不要在调用保存数据方法后马上关闭服务端。
- 查看本地的 DBCache 存档 (这个就是玩家的存档,方便我们在测试时检查问题)
- 找一个目录右键,选择“打开文件所在的位置”
- 找到这个项目“TycoonDemo”的根目录
- 点击“DBChache”文件夹,即可查看自己的存档
- 如果需要重新测试,把这个存档删掉即可
- 点击工程按钮
- 点击“删除 PIE 缓存”
完整代码
当前 MailBox
脚本完整代码(点击展开)
typescript
import PlayerData from "../modules/player/PlayerData";
import PlayerModuleC from "../modules/player/PlayerModuleC";
@Component
export default class MailBox extends Script {
/** 当前金币 */
private _goldNum: number = 0;
/** 金币增加基数 */
private _alterNum: number = 1;
/** 金币数量文本控件 */
private _goldNumTxt: TextBlock;
/** 金币增加基数文本控件 */
private _addGoldTxt: TextBlock;
/** 定时器对象 */
private inter: any;
protected onStart(): void {
// 如果是服务端直接退出
if (SystemUtil.isServer()) return;
this.init();
}
public async init() {
// 等待邮箱模型加载好
await this.gameObject.asyncReady();
// 等待客户端数据中心准备好
await DataCenterC.ready();
// 初始化金币增长速率
this._alterNum = DataCenterC.getData(PlayerData).goldGrowthRate;
// 拿到世界UI逻辑对象
const worldUI = this.gameObject.getChildByName("世界UI") as UIWidget;
// 拿到targetUI
const targetUI = worldUI.getTargetUIWidget();
// 拿到文本控件
this._goldNumTxt = targetUI.findChildByPath("RootCanvas/goldNumTxt") as TextBlock;
this._addGoldTxt = targetUI.findChildByPath("RootCanvas/addGoldPerSecTxt") as TextBlock;
// 初始化文本控件内容
this._goldNumTxt.text = this._goldNum.toString();
this._addGoldTxt.text = this._alterNum + "/秒";
// 创建定时器
this.inter = setInterval(() => {
this._goldNum += this._alterNum;
this._goldNumTxt.text = this._goldNum.toString();
}, 1000);
// 获取触发器 添加进入事件 进入之后获取金币
const trigger = this.gameObject.getChildByName("触发器") as Trigger;
trigger.onEnter.add(() => {
ModuleService.getModule(PlayerModuleC).changeGold(this._goldNum);
this._goldNum = 0;
this._goldNumTxt.text = this._goldNum.toString();
});
// 监听金币增加基数改变的事件
Event.addServerListener("GoldGrowthRate", (deltaNum: number) => {
this._alterNum += deltaNum;
this._addGoldTxt.text = this._alterNum + "/秒";
})
}
protected onDestroy(): void {
// 销毁计时器
if(this.inter){
clearInterval(this.inter);
this.inter = null;
}
}
}
import PlayerData from "../modules/player/PlayerData";
import PlayerModuleC from "../modules/player/PlayerModuleC";
@Component
export default class MailBox extends Script {
/** 当前金币 */
private _goldNum: number = 0;
/** 金币增加基数 */
private _alterNum: number = 1;
/** 金币数量文本控件 */
private _goldNumTxt: TextBlock;
/** 金币增加基数文本控件 */
private _addGoldTxt: TextBlock;
/** 定时器对象 */
private inter: any;
protected onStart(): void {
// 如果是服务端直接退出
if (SystemUtil.isServer()) return;
this.init();
}
public async init() {
// 等待邮箱模型加载好
await this.gameObject.asyncReady();
// 等待客户端数据中心准备好
await DataCenterC.ready();
// 初始化金币增长速率
this._alterNum = DataCenterC.getData(PlayerData).goldGrowthRate;
// 拿到世界UI逻辑对象
const worldUI = this.gameObject.getChildByName("世界UI") as UIWidget;
// 拿到targetUI
const targetUI = worldUI.getTargetUIWidget();
// 拿到文本控件
this._goldNumTxt = targetUI.findChildByPath("RootCanvas/goldNumTxt") as TextBlock;
this._addGoldTxt = targetUI.findChildByPath("RootCanvas/addGoldPerSecTxt") as TextBlock;
// 初始化文本控件内容
this._goldNumTxt.text = this._goldNum.toString();
this._addGoldTxt.text = this._alterNum + "/秒";
// 创建定时器
this.inter = setInterval(() => {
this._goldNum += this._alterNum;
this._goldNumTxt.text = this._goldNum.toString();
}, 1000);
// 获取触发器 添加进入事件 进入之后获取金币
const trigger = this.gameObject.getChildByName("触发器") as Trigger;
trigger.onEnter.add(() => {
ModuleService.getModule(PlayerModuleC).changeGold(this._goldNum);
this._goldNum = 0;
this._goldNumTxt.text = this._goldNum.toString();
});
// 监听金币增加基数改变的事件
Event.addServerListener("GoldGrowthRate", (deltaNum: number) => {
this._alterNum += deltaNum;
this._addGoldTxt.text = this._alterNum + "/秒";
})
}
protected onDestroy(): void {
// 销毁计时器
if(this.inter){
clearInterval(this.inter);
this.inter = null;
}
}
}
当前 BuildInfo
脚本完整代码(点击展开)
typescript
import PlayerData from "../modules/player/PlayerData";
import PlayerModuleS from "../modules/player/PlayerModuleS";
@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;
/** 解锁价格 */
@Property({ group: "基本信息", displayName: "解锁价格" })
public unlockPrice: number = 10;
@Property({ group: "基本信息", displayName: "每秒带来收益" })
public profit: number = 1;
@Property({ group: "基本信息", displayName: "序号", tooltip: "这个组的第几个,默认从1开始" })
public id: number = 1;
/** 事件监听器 需要在解锁按钮回收时注销 */
private _listener: EventListener;
/** 显示当前解锁按钮组进度 */
private _curPro: number = 0;
/** 进入触发器事件 */
private _unlockbuildFun = null;
/** 解锁按钮 */
private _unlockBtn: GameObject = null;
protected onStart(): void {
// 客户端直接 返回
if (SystemUtil.isClient()) {
this.req_init();
return;
}
// 开启服务端 onUpdate 方法
this.useUpdate = true;
// 关闭碰撞
(this.gameObject as Model).setCollision(PropertyStatus.Off);
// 关闭显示
this.gameObject.setVisibility(PropertyStatus.Off);
// 监听是否显示解锁建筑按钮事件,事件名是 "Show_Unlock_Button" + 组号
this._listener = Event.addLocalListener("Show_Unlock_Button" + this.groupId, this.ensureNeeds.bind(this));
}
/**
* 服务端响应客户端初始化
* @param curGroupId 组id
* @param unlockedIds 当前组解锁的家具id列表
*/
@RemoteFunction(Server)
public res_init(curGroupId: number, unlockedIds: number[]) {
// 如果当前要解锁的组小于已经解锁完毕的组 说明这个组已经全部解锁了
if (curGroupId > this.groupId) {
this.gameObject.setVisibility(PropertyStatus.On);
(this.gameObject as Model).setCollision(PropertyStatus.On);
}
if (curGroupId === this.groupId) {
// 如果解锁列表中包含这个家居的序号,说明已经解锁了 我们将它显示出来 并且发送事件通知解锁下一个家居的解锁按钮
if (unlockedIds.includes(this.id)) {
this.gameObject.setVisibility(PropertyStatus.On);
(this.gameObject as Model).setCollision(PropertyStatus.On);
Event.dispatchToLocal("Show_Unlock_Button" + (this.groupId + 1));
} else {
this.initUnlockBtn();
}
}
}
/**
* 向服务端发送解锁请求
*/
public async req_init() {
// 等待客户端数据中心准备好
await DataCenterC.ready();
const playerData = DataCenterC.getData(PlayerData);
this.res_init(playerData.curGroupId, playerData.unlockedIds);
}
protected onUpdate(dt: number): void {
TweenUtil.TWEEN.update();
}
/**
* 验证是否满足解锁条件
*/
private ensureNeeds() {
// 满足条件就显示解锁按钮
if (++this._curPro >= this.needs) {
// 先注销 listener
this._listener.disconnect();
// 初始化按钮
this.initUnlockBtn();
}
}
/**
* 给指定玩家初始化 UI
* @param player 玩家
* @param unlockBtnGuid 按钮 GameObjectID
*/
@RemoteFunction(Client)
private initWorldUIOnlyOne(player: Player, unlockBtnGuid: string): void {
this.initWorldUI(unlockBtnGuid);
}
/**
* 初始化所有玩家的世界UI
* @param unlockBtnGuid 按钮 GameObjectID
*/
@RemoteFunction(Client, Multicast)
private initWorldUIAllPlayer(unlockBtnGuid: string): void {
this.initWorldUI(unlockBtnGuid);
}
/**
* 客户端初始化世界UI
* @param unlockBtnGuid 解锁按钮的 GameObjectID
*/
private async initWorldUI(unlockBtnGuid: string): Promise<void> {
// 异步查找按钮
this._unlockBtn = await GameObject.asyncFindGameObjectById(unlockBtnGuid);
const worldUI = this._unlockBtn.getChildByName("世界UI") as UIWidget;
const targetUI = worldUI.getTargetUIWidget();
const buildName = targetUI.findChildByPath("RootCanvas/buildNameTxt") as TextBlock;
const buildNeeds = targetUI.findChildByPath("RootCanvas/buildNeedsTxt") as TextBlock;
buildName.text = this.gameObject.name;
buildNeeds.text = this.unlockPrice.toString();
}
/**
* 初始化解锁建筑按钮
*/
private async initUnlockBtn(): Promise<void> {
// 注意这儿spawn的 GameObjectID 是解锁按钮预制体的id,第二个参数指资源类型,这儿因为是预制体的资源所以传递GameObjPoolSourceType.Prefab
this._unlockBtn = await GameObjPool.asyncSpawn("FE08DDF04F44547200C8CF9E415D3904", GameObjPoolSourceType.Prefab);
// 初始化所有玩家的世界UI
this.initWorldUIAllPlayer(this._unlockBtn.gameObjectId);
// 设置父节点为当前对象
this._unlockBtn.parent = this.gameObject;
// 设置按钮的相对位置
this._unlockBtn.localTransform.position = this.unlockBtnLoc;
// 获取预制体下的触发器
const trigger = this._unlockBtn.getChildByName("触发器") as Trigger;
this._unlockbuildFun = (other: GameObject) => {
// 判断进入触发器的物体是否为玩家
if (other instanceof Character) {
// 判断钱是否足够
const isGoldEnough = ModuleService.getModule(PlayerModuleS).changeGold(other.player.playerId, -this.unlockPrice);
// 如果钱够
if (isGoldEnough) {
// 解绑触发器进入事件
trigger.onEnter.remove(this._unlockbuildFun);
// 回收按钮模型
GameObjPool.despawn(this._unlockBtn);
// 使用动画 显示建筑模型 & 开启碰撞
this.showBuild();
// 通知客户端金币增加基数改变
Event.dispatchToClient(other.player, "GoldGrowthRate", this.profit);
// 更改金币增长基数
ModuleService.getModule(PlayerModuleS).changeGoldGrowthRate(other.player.playerId, this.profit);
// 更新解锁建筑的信息
ModuleService.getModule(PlayerModuleS).updateBuildUnlockInfo(other.player.playerId, this.groupId, this.id);
} else {
console.error("钱不够!");
}
}
}
// 绑定到触发器进入事件
trigger.onEnter.add(this._unlockbuildFun);
}
/**
* 显示建筑
*/
private showBuild(): void {
// 定义一个 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时显示建筑
tween.onStart(() => {
// 显示模型
this.gameObject.setVisibility(PropertyStatus.On);
// 开启碰撞
(this.gameObject as Model).setCollision(PropertyStatus.On);
});
// 设置 tween 每帧更新时更新缩放
tween.onUpdate(t => { this.gameObject.worldTransform.scale = t.scale });
// 动画完成时关闭 useUpdate 减少无用调用
tween.onComplete(() => {
this.useUpdate = false;
Event.dispatchToLocal("Show_Unlock_Button" + (this.groupId + 1));
});
// 启动动画
tween.start();
}
}
import PlayerData from "../modules/player/PlayerData";
import PlayerModuleS from "../modules/player/PlayerModuleS";
@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;
/** 解锁价格 */
@Property({ group: "基本信息", displayName: "解锁价格" })
public unlockPrice: number = 10;
@Property({ group: "基本信息", displayName: "每秒带来收益" })
public profit: number = 1;
@Property({ group: "基本信息", displayName: "序号", tooltip: "这个组的第几个,默认从1开始" })
public id: number = 1;
/** 事件监听器 需要在解锁按钮回收时注销 */
private _listener: EventListener;
/** 显示当前解锁按钮组进度 */
private _curPro: number = 0;
/** 进入触发器事件 */
private _unlockbuildFun = null;
/** 解锁按钮 */
private _unlockBtn: GameObject = null;
protected onStart(): void {
// 客户端直接 返回
if (SystemUtil.isClient()) {
this.req_init();
return;
}
// 开启服务端 onUpdate 方法
this.useUpdate = true;
// 关闭碰撞
(this.gameObject as Model).setCollision(PropertyStatus.Off);
// 关闭显示
this.gameObject.setVisibility(PropertyStatus.Off);
// 监听是否显示解锁建筑按钮事件,事件名是 "Show_Unlock_Button" + 组号
this._listener = Event.addLocalListener("Show_Unlock_Button" + this.groupId, this.ensureNeeds.bind(this));
}
/**
* 服务端响应客户端初始化
* @param curGroupId 组id
* @param unlockedIds 当前组解锁的家具id列表
*/
@RemoteFunction(Server)
public res_init(curGroupId: number, unlockedIds: number[]) {
// 如果当前要解锁的组小于已经解锁完毕的组 说明这个组已经全部解锁了
if (curGroupId > this.groupId) {
this.gameObject.setVisibility(PropertyStatus.On);
(this.gameObject as Model).setCollision(PropertyStatus.On);
}
if (curGroupId === this.groupId) {
// 如果解锁列表中包含这个家居的序号,说明已经解锁了 我们将它显示出来 并且发送事件通知解锁下一个家居的解锁按钮
if (unlockedIds.includes(this.id)) {
this.gameObject.setVisibility(PropertyStatus.On);
(this.gameObject as Model).setCollision(PropertyStatus.On);
Event.dispatchToLocal("Show_Unlock_Button" + (this.groupId + 1));
} else {
this.initUnlockBtn();
}
}
}
/**
* 向服务端发送解锁请求
*/
public async req_init() {
// 等待客户端数据中心准备好
await DataCenterC.ready();
const playerData = DataCenterC.getData(PlayerData);
this.res_init(playerData.curGroupId, playerData.unlockedIds);
}
protected onUpdate(dt: number): void {
TweenUtil.TWEEN.update();
}
/**
* 验证是否满足解锁条件
*/
private ensureNeeds() {
// 满足条件就显示解锁按钮
if (++this._curPro >= this.needs) {
// 先注销 listener
this._listener.disconnect();
// 初始化按钮
this.initUnlockBtn();
}
}
/**
* 给指定玩家初始化 UI
* @param player 玩家
* @param unlockBtnGuid 按钮 GameObjectID
*/
@RemoteFunction(Client)
private initWorldUIOnlyOne(player: Player, unlockBtnGuid: string): void {
this.initWorldUI(unlockBtnGuid);
}
/**
* 初始化所有玩家的世界UI
* @param unlockBtnGuid 按钮 GameObjectID
*/
@RemoteFunction(Client, Multicast)
private initWorldUIAllPlayer(unlockBtnGuid: string): void {
this.initWorldUI(unlockBtnGuid);
}
/**
* 客户端初始化世界UI
* @param unlockBtnGuid 解锁按钮的 GameObjectID
*/
private async initWorldUI(unlockBtnGuid: string): Promise<void> {
// 异步查找按钮
this._unlockBtn = await GameObject.asyncFindGameObjectById(unlockBtnGuid);
const worldUI = this._unlockBtn.getChildByName("世界UI") as UIWidget;
const targetUI = worldUI.getTargetUIWidget();
const buildName = targetUI.findChildByPath("RootCanvas/buildNameTxt") as TextBlock;
const buildNeeds = targetUI.findChildByPath("RootCanvas/buildNeedsTxt") as TextBlock;
buildName.text = this.gameObject.name;
buildNeeds.text = this.unlockPrice.toString();
}
/**
* 初始化解锁建筑按钮
*/
private async initUnlockBtn(): Promise<void> {
// 注意这儿spawn的 GameObjectID 是解锁按钮预制体的id,第二个参数指资源类型,这儿因为是预制体的资源所以传递GameObjPoolSourceType.Prefab
this._unlockBtn = await GameObjPool.asyncSpawn("FE08DDF04F44547200C8CF9E415D3904", GameObjPoolSourceType.Prefab);
// 初始化所有玩家的世界UI
this.initWorldUIAllPlayer(this._unlockBtn.gameObjectId);
// 设置父节点为当前对象
this._unlockBtn.parent = this.gameObject;
// 设置按钮的相对位置
this._unlockBtn.localTransform.position = this.unlockBtnLoc;
// 获取预制体下的触发器
const trigger = this._unlockBtn.getChildByName("触发器") as Trigger;
this._unlockbuildFun = (other: GameObject) => {
// 判断进入触发器的物体是否为玩家
if (other instanceof Character) {
// 判断钱是否足够
const isGoldEnough = ModuleService.getModule(PlayerModuleS).changeGold(other.player.playerId, -this.unlockPrice);
// 如果钱够
if (isGoldEnough) {
// 解绑触发器进入事件
trigger.onEnter.remove(this._unlockbuildFun);
// 回收按钮模型
GameObjPool.despawn(this._unlockBtn);
// 使用动画 显示建筑模型 & 开启碰撞
this.showBuild();
// 通知客户端金币增加基数改变
Event.dispatchToClient(other.player, "GoldGrowthRate", this.profit);
// 更改金币增长基数
ModuleService.getModule(PlayerModuleS).changeGoldGrowthRate(other.player.playerId, this.profit);
// 更新解锁建筑的信息
ModuleService.getModule(PlayerModuleS).updateBuildUnlockInfo(other.player.playerId, this.groupId, this.id);
} else {
console.error("钱不够!");
}
}
}
// 绑定到触发器进入事件
trigger.onEnter.add(this._unlockbuildFun);
}
/**
* 显示建筑
*/
private showBuild(): void {
// 定义一个 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时显示建筑
tween.onStart(() => {
// 显示模型
this.gameObject.setVisibility(PropertyStatus.On);
// 开启碰撞
(this.gameObject as Model).setCollision(PropertyStatus.On);
});
// 设置 tween 每帧更新时更新缩放
tween.onUpdate(t => { this.gameObject.worldTransform.scale = t.scale });
// 动画完成时关闭 useUpdate 减少无用调用
tween.onComplete(() => {
this.useUpdate = false;
Event.dispatchToLocal("Show_Unlock_Button" + (this.groupId + 1));
});
// 启动动画
tween.start();
}
}
当前 PlayerModuleS
脚本完整代码(点击展开)
typescript
import PlayerData from "./PlayerData";
import PlayerModuleC from "./PlayerModuleC";
export default class PlayerModuleS extends ModuleS<PlayerModuleC, PlayerData> {
/**
* 改变金币方法 如果成功返回 true 不成功返回 false
* @param pid 玩家id
* @param deltaNum 改变的数量
*/
public changeGold(pid: number, deltaNum: number): boolean {
// 通过玩家id获取数据
const data = this.getPlayerData(pid);
// 要改变的如果是负数 就要判断钱是否够
if (deltaNum < 0 && data.gold < Math.abs(deltaNum)) {
return false;
}
data.changeGold(deltaNum);
return true;
}
/**
* 服务端改变金币 支持RPC调用
* @param deltaNum 要改变的数量
* @returns 是否成功
*/
public net_changeGold(deltaNum: number): boolean {
return this.changeGold(this.currentPlayerId, deltaNum);
}
/**
* 改变金币增长速率
* @param pid 要改变金币数量的玩家id
* @param deltaNum 改变的数量
*/
public changeGoldGrowthRate(pid: number, deltaNum: number) {
// 获取玩家pid的数据
const data = this.getPlayerData(pid);
data.changeGoldGrowthRate(deltaNum);
}
public net_changeGoldGrowthRate(deltaNum: number): void {
this.changeGoldGrowthRate(this.currentPlayerId, deltaNum);
}
/**
* 服务端更新建筑解锁信息
* @param pid 玩家id
* @param groupId 组号
* @param sId 序号
*/
public updateBuildUnlockInfo(pid: number, groupId: number, sId: number) {
// 获取玩家pid的数据
const data = this.getPlayerData(pid);
data.updateBuildUnlockInfo(groupId, sId);
}
public net_updateBuildUnlockInfo(groupId: number, sId: number): void {
this.updateBuildUnlockInfo(this.currentPlayerId, groupId, sId);
}
}
import PlayerData from "./PlayerData";
import PlayerModuleC from "./PlayerModuleC";
export default class PlayerModuleS extends ModuleS<PlayerModuleC, PlayerData> {
/**
* 改变金币方法 如果成功返回 true 不成功返回 false
* @param pid 玩家id
* @param deltaNum 改变的数量
*/
public changeGold(pid: number, deltaNum: number): boolean {
// 通过玩家id获取数据
const data = this.getPlayerData(pid);
// 要改变的如果是负数 就要判断钱是否够
if (deltaNum < 0 && data.gold < Math.abs(deltaNum)) {
return false;
}
data.changeGold(deltaNum);
return true;
}
/**
* 服务端改变金币 支持RPC调用
* @param deltaNum 要改变的数量
* @returns 是否成功
*/
public net_changeGold(deltaNum: number): boolean {
return this.changeGold(this.currentPlayerId, deltaNum);
}
/**
* 改变金币增长速率
* @param pid 要改变金币数量的玩家id
* @param deltaNum 改变的数量
*/
public changeGoldGrowthRate(pid: number, deltaNum: number) {
// 获取玩家pid的数据
const data = this.getPlayerData(pid);
data.changeGoldGrowthRate(deltaNum);
}
public net_changeGoldGrowthRate(deltaNum: number): void {
this.changeGoldGrowthRate(this.currentPlayerId, deltaNum);
}
/**
* 服务端更新建筑解锁信息
* @param pid 玩家id
* @param groupId 组号
* @param sId 序号
*/
public updateBuildUnlockInfo(pid: number, groupId: number, sId: number) {
// 获取玩家pid的数据
const data = this.getPlayerData(pid);
data.updateBuildUnlockInfo(groupId, sId);
}
public net_updateBuildUnlockInfo(groupId: number, sId: number): void {
this.updateBuildUnlockInfo(this.currentPlayerId, groupId, sId);
}
}
当前 PlayerModuleC
脚本完整代码(点击展开)
typescript
import MainUI from "../../ui/MainUI";
import PlayerData from "./PlayerData";
import PlayerModuleS from "./PlayerModuleS";
export default class PlayerModuleC extends ModuleC<PlayerModuleS, PlayerData> {
protected onStart(): void {
// 显示 MainUI 这里传递得事我们创建出得 UI 脚本的类 而不是导出的!
UIService.show(MainUI);
}
/**
* 客户端改变金币数量方法
* @param deltaNum 要改变的数量
*/
public async changeGold(deltaNum: number): Promise<boolean> {
return this.server.net_changeGold(deltaNum);
}
/**
* 改变金币增长速率
* @param deltaNum 改变值
*/
public changeGoldGrowthRate(deltaNum: number): void {
this.server.net_changeGoldGrowthRate(deltaNum);
}
/**
* 更新建筑解锁信息
* @param groupId 当前组
* @param sId 当前家具序号
*/
public updateBuildUnlockInfo(groupId: number, sId: number): void {
this.server.net_updateBuildUnlockInfo(groupId, sId);
}
}
import MainUI from "../../ui/MainUI";
import PlayerData from "./PlayerData";
import PlayerModuleS from "./PlayerModuleS";
export default class PlayerModuleC extends ModuleC<PlayerModuleS, PlayerData> {
protected onStart(): void {
// 显示 MainUI 这里传递得事我们创建出得 UI 脚本的类 而不是导出的!
UIService.show(MainUI);
}
/**
* 客户端改变金币数量方法
* @param deltaNum 要改变的数量
*/
public async changeGold(deltaNum: number): Promise<boolean> {
return this.server.net_changeGold(deltaNum);
}
/**
* 改变金币增长速率
* @param deltaNum 改变值
*/
public changeGoldGrowthRate(deltaNum: number): void {
this.server.net_changeGoldGrowthRate(deltaNum);
}
/**
* 更新建筑解锁信息
* @param groupId 当前组
* @param sId 当前家具序号
*/
public updateBuildUnlockInfo(groupId: number, sId: number): void {
this.server.net_updateBuildUnlockInfo(groupId, sId);
}
}
:::
当前 PlayerData
脚本完整代码(点击展开)
typescript
export default class PlayerData extends Subdata {
/** 金币数量 */
@Decorator.persistence()
public gold: number = 55;
/** 玩家金币增长基数 */
@Decorator.persistence()
public goldGrowthRate: number = 1;
/** 玩家当前解锁到的组号 */
@Decorator.persistence()
public curGroupId: number = 0;
/** 这个组当前已经解锁的建筑序号列表 */
@Decorator.persistence()
public unlockedIds: number[] = [];
/** 金币改变时触发事件 */
public onGoldChange: Action = new Action();
/**
* 修改金币数量
* @param deltaNum 改变值,为负数就是减少
*/
public changeGold(deltaNum: number): void {
this.syncToClient();
this.gold += deltaNum;
this.onGoldChange.call(this.gold);
// 保存所有数据 并且同步给客户端
this.save(true);
}
/**
* 更改金币增长基数
* @param deltaNum 改变值
*/
public changeGoldGrowthRate(deltaNum: number): void {
this.goldGrowthRate += deltaNum;
// 保存所有数据 并且同步给客户端
this.save(true);
}
/**
* 更新建筑解锁信息
* @param groupId 家具组号
* @param sId 家具序号
*/
public updateBuildUnlockInfo(groupId: number, sId: number): void {
// 判断当前组号是否与要解锁的组号相同,如果不同说明当前组全部解锁完毕了
if(this.curGroupId != groupId){
this.unlockedIds.length = 0;
this.curGroupId = groupId;
}
// 将新解锁的建筑的序号保存到已解锁数组中
this.unlockedIds.push(sId);
// 保存并同步给客户端
this.save(true);
}
}
export default class PlayerData extends Subdata {
/** 金币数量 */
@Decorator.persistence()
public gold: number = 55;
/** 玩家金币增长基数 */
@Decorator.persistence()
public goldGrowthRate: number = 1;
/** 玩家当前解锁到的组号 */
@Decorator.persistence()
public curGroupId: number = 0;
/** 这个组当前已经解锁的建筑序号列表 */
@Decorator.persistence()
public unlockedIds: number[] = [];
/** 金币改变时触发事件 */
public onGoldChange: Action = new Action();
/**
* 修改金币数量
* @param deltaNum 改变值,为负数就是减少
*/
public changeGold(deltaNum: number): void {
this.syncToClient();
this.gold += deltaNum;
this.onGoldChange.call(this.gold);
// 保存所有数据 并且同步给客户端
this.save(true);
}
/**
* 更改金币增长基数
* @param deltaNum 改变值
*/
public changeGoldGrowthRate(deltaNum: number): void {
this.goldGrowthRate += deltaNum;
// 保存所有数据 并且同步给客户端
this.save(true);
}
/**
* 更新建筑解锁信息
* @param groupId 家具组号
* @param sId 家具序号
*/
public updateBuildUnlockInfo(groupId: number, sId: number): void {
// 判断当前组号是否与要解锁的组号相同,如果不同说明当前组全部解锁完毕了
if(this.curGroupId != groupId){
this.unlockedIds.length = 0;
this.curGroupId = groupId;
}
// 将新解锁的建筑的序号保存到已解锁数组中
this.unlockedIds.push(sId);
// 保存并同步给客户端
this.save(true);
}
}