本帖最后由 空伊伊 于 2023-11-8 17:33 编辑
本帖最后由 空伊伊 于 2023-11-8 17:32 编辑
模块管理和数据中心
目录
为什么要使用模块管理①
模块管理的使用步骤②
模块管理的各功能介绍③
为什么要使用数据中心④
数据中心的使用步骤⑤
数据中心的各功能介绍⑥
为什么需要模块管理?
(现在分别通过 不使用模块管理 以及 使用模块管理 的方式来实现上图中的游戏功能)
1.不使用模块管理编写游戏逻辑
游戏脚本
const Req_Atk = "Req_Atk"
const Be_Atk = "Be_Atk"
const Req_SendProp = "Req_SendProp"
const GetProp = "GetProp"
export class GameScript {
/**玩家的背包数据 */
public client_myBagData = ["攻击道具", "物品1", "物品2"];
public init(): void {
// 客户端初始化
if (SystemUtil.isClient()) {
// 监听被攻击事件
Event.addServerListener(Be_Atk, (attacker: Player) => {
// 从背包中取出一个道具
let prop = this.client_myBagData.pop()
// 派发发送道具事件
Event.dispatchToServer(Req_SendProp, attacker, prop)
})
// 监听获取道具事件
Event.addServerListener(GetProp, (prop) => {
this.client_AddProp(prop)
})
}
// 服务端初始化
if (SystemUtil.isServer()) {
// 监听请求攻击事件
Event.addClientListener(Req_Atk, (attacker: Player, targetPlayer: Player) => {
// 派发被攻击事件
Event.dispatchToClient(targetPlayer, Be_Atk, attacker)
})
// 监听请求发送事件
Event.addClientListener(Req_SendProp, (sender: Player, attacker: Player, prop) => {
// 派发获取道具事件
Event.dispatchToClient(attacker, GetProp, prop)
})
}
}
/**客户端请求攻击targetPlayer */
public client_ReqAtk(targetPlayer: Player) {
if (this.client_checkAtkProp()) {
Event.dispatchToServer(Req_Atk, targetPlayer)
}
}
/**检查背包里否有攻击道具 */
public client_checkAtkProp(): boolean {
let index = this.client_myBagData.indexOf("攻击道具")
if (index != -1) {
this.client_myBagData.splice(index, 1);
return true
} else {
return false
}
}
/**给自己的背包增加一个道具 */
public client_AddProp(prop) {
this.client_myBagData.push(prop)
console.log(JSON.stringify(this.client_myBagData))
}
}
调用
@Component
export default class GameStart extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
// 创建一个游戏脚本
let game = new GameScript()
// 初始化游戏脚本
game.init()
if (SystemUtil.isClient()) {
// 五秒后攻击一次其他玩家
setTimeout(() => {
let otherPlayer = Player.getAllPlayers().find((v) => v != Player.localPlayer)
game.client_ReqAtk(otherPlayer)
}, 5000);
}
}
}
可以看到,不使用模块管理的话,代码会有以下几个明显的缺点:
1.代码需要书写大量的客户端与服务器之间的通信事件(Events相关的代码重复出现次数过多)
2.难以区分出客户端与服务端
3.多个功能耦合在一起,一个脚本的代码量过多,导致后续维护困难
2.使用模块管理编写游戏逻辑(未使用数据模块)
背包模块(客户端)
/**
* 背包模块(客户端)
*/
export class BagModuleC extends ModuleC<BagModuleS, null>{
/**玩家的背包数据 */
public client_myBagData = ["攻击道具", "物品1", "物品2"];
/**检查背包里否有攻击道具 */
public checkAtkProp(): boolean {
let index = this.client_myBagData.indexOf("攻击道具")
if (index != -1) {
this.client_myBagData.splice(index, 1);
return true
} else {
return false
}
}
/**给自己的背包增加一个道具 */
public net_addProp(prop) {
this.client_myBagData.push(prop)
}
/**从自己的背包中移除一个道具 */
public removeProp(prop) {
let index = this.client_myBagData.indexOf(prop)
if (index != -1) {
this.client_myBagData.splice(index, 1);
}
}
}
背包模块(服务端)
/**
* 背包模块(服务端)
*/
export class BagModuleS extends ModuleS<BagModuleC, null>{
/**给targetPlayer添加道具prop */
public addPropByPlayer(targetPlayer: Player, prop) {
this.getClient(targetPlayer).net_addProp(prop)
}
}
战斗模块(客户端)
/**
* 战斗模块(客户端)
*/
export class FightModuleC extends ModuleC<FightModuleS, null>{
/**请求攻击targetPlayer */
public reqAtk(targetPlayer: Player) {
// 获得背包客户端模块
let bagModuleC = ModuleService.getModule(BagModuleC)
if (bagModuleC.checkAtkProp()) {
// 如果有攻击道具,通知服务端发起攻击
this.server.net_ReqAtk(targetPlayer)
}
}
/**被attacker攻击 */
public net_BeAtk(attacker: Player) {
let bagModuleC = ModuleService.getModule(BagModuleC)
let prop = bagModuleC.client_myBagData.pop()
// 通知服务端发送道具
this.server.net_SendProp(attacker, prop)
}
}
战斗模块(服务端)
/**
* 战斗模块(服务端)
*/
export class FightModuleS extends ModuleS<FightModuleC, null>{
/**服务端发起攻击 */
net_ReqAtk(targetPlayer: Player) {
// 服务端通知目标执行被攻击
this.getClient(targetPlayer).net_BeAtk(this.currentPlayer)
}
/**服务端发送道具prop给targetPlayer */
public net_SendProp(targetPlayer: Player, prop) {
// 获得背包服务端模块
let bagModuleS = ModuleService.getModule(BagModuleS)
// 给targetPlayer添加道具
bagModuleS.addPropByPlayer(targetPlayer, prop)
}
}
调用
@Component
export default class GameStart extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
// 注册模块(作用相当于游戏脚本里的初始化)
ModuleService.registerModule(BagModuleS, BagModuleC, null)
ModuleService.registerModule(FightModuleS, FightModuleC, null)
if (SystemUtil.isClient()) {
// 五秒后攻击一次其他玩家
setTimeout(() => {
let otherPlayer = Player.getAllPlayers().find((v) => v != Player.localPlayer)
// 获取战斗模块(客户端),调用其攻击方法
ModuleService.getModule(FightModuleC).reqAtk(otherPlayer)
}, 5000);
}
}
}
可以看到,使用模块管理后,代码得到了以下改善:
1.客户端和服务端分开编写,避免前后端代码难以区分的问题
2.不再需要来回监听和派发事件,只需要在方法前面加上net_即可完成通信事件的调用
3.代码由原来的一个脚本拆成了两个模块,四个class,降低了耦合度,方便多人开发与管理
3.总结
模块管理帮助我们区分开了服务端和客户端,让代码的调用域变得清楚明了。同时模块管理为我们封装好了事件调用,让我们不用再编写大量的Events代码。此外,模块管理的一个更大作用:“数据同步”,我会在下一节数据中心相关内容里进行讲述。
(如果你是代码编写的新手,可能会觉得使用模块管理后反而代码量增多,逻辑也变复杂了。其实不然,当你制作的游戏复杂度提高之后,你就能体会到模块管理的好处了。)
下一节:模块管理的使用步骤②