| 本帖最后由 脑的研发记录 于 2023-8-16 19:46 编辑 
 击落广播功能——单脚本(含附件)
 
 
   受论坛师傅的指点写的代码,现在分享。
 总览:
 第一部分:故事背景,文案初衷
 第二部分:代码总思路
 第三部分:代码与代码详解
 第四部分:可搭载进广播系统的拓展模块链接(不定期更新)
 
 有附件,编辑时编辑器版本:0.25.0.4
 第一部分:故事背景
 原来是做很简单的踩影子PVP小游戏,然后遇着了不会写击落广播的问题,全网浏览,求索未获,幸得论坛老师傅们出手相救,指点迷津,在下感激不尽。念无以为报,又见论坛暂无此类文案,便出此拙作,不似师傅们一语中的,多有啰啰嗦嗦,偏向新手,还请诸位师傅见谅。
 
 原来也是想着是做一点有意思的东西,然后解答一下小时候的问题,就算是多年之后的回音吧。
 念念不忘,必有回响!
 我来兑现承诺哩,每做一个游戏,就公开设计思路和关键代码!
 
 
 第二部分:代码总思路
 
 老师傅的原话:
 “1.服务器维护 map<playerid(就是唯一标识符),昵称>
 2.玩家进游戏的时候客户端过几秒获取自己的昵称 然后rpc给服务器,服务器设置好map
 3.连杀就是维护连杀信息组map,这个不用讲哈,反正每次要报连杀的时候,直接从服务器广播给所有玩家就可以了”
 这也是基本思路,
 不过遇着一点实际情况就是getPlayer()的追溯要求限制,需要另想出路:
 把这几行代码粘贴到游戏脚本里,就能发现有报错,还有废弃函数标记,鼠标悬停几秒即可详情信息
 
 
 let  a = player.getPlayerID()
 //这个是说是要废弃的接口,但是具体失效时间不明,
 let newplayerA = getPlayer(a)
 //这个是根据上面的playid来查找玩家,
 //但是
 let b = player.getUserId()
 let newplayerB = getPlayer(b)
 //这个userID的类型还是string,不是number,这样用getPlayer()代码会报错参数类型不符合而不能运行
 
 
 于是我就找一点新的素材,当时也恰好是稍微接触一点数据结构,playerID和玩家可以组成一个map,
 虽然map明确不支持Player类型,但是我只要玩家id和玩家昵称,玩家id和玩家连击,只要拿map做出这俩的查询表就行了。
 然后茅塞顿开,再回去一看原来第一句就已经这样写了。
 但觉得要rpc,写dispatchtoServer()还是觉得应该有新的办法,带宽还能再节省,代码还能再少写一点,playerID也更新一下变成UserID
 正巧那个时候在该排行榜的代码,就有了Events.addPlayerJoinedListener(),来初始化map。
 于是捯饬下来,就有了下面的思路:
 
 1.map1<userID,玩家名称>,map2<userID,玩家连击数>,都在服务器模块声明并维护更新。
 2.Events.addPlayerJoinedListener(),监听玩家进入,初始化map,
 Events.addPlayerLeftListener()监听玩家离开,释放map中对应模块内存
 就不用RPC了。
 3.报连击的时候就Events.dispatchToAllClient()广播括号里面自定义的参数就行。
 
 
 第三部分:代码与代码详解
 完整代码&啰嗦的注释
 
 
 interface deads {
 name: string
 killBy: string
 killNum: number
 }
 // 这个声明类似c语言的结构体,
 // 但是interface关键词下声明的变量还有其他特性。
 // 但是我们这里只用到了它其中的那个类似于c结构体的特性
 
 // import { RankMgr } from "../ranks/RankMgr"
 // 这是加上排行榜系统用于同步排行榜信息,
 // 但是目前在这一文案里我们不会用到这个模块下的函数
 // RankMgr.Instance.AddScore(killer,1)
 // 在第66行处的代码就是如上模块里的函数,注释掉,不影响广播
 // 与之适配的广播系统在文案末尾推荐链接里会出现
 @Core.Class
 export default class killerData extends Core.Script {
 
 protected onStart(): void {
 if (SystemUtil.isServer()) {
 let playerName: Map<string, string> = new Map()
 // 新建玩家名称“playerName”,声明为Map的string与string对应类型,然后实例化一个 Map对象。
 // 没有 new Map() 那一句编译器不会报错,但是存不住东西
 // 这个playerNAame是用于玩家名字溯源,采集到被击落的玩家的userID然后在该map里查找name
 let killsNum: Map<string, number> = new Map()
 // 连杀记录,用于查找玩家名下的连杀记录
 let onePersonsHave: Array<deads> = []
 // 队列,push进,shift出,监听端push,死亡名单数组shift后转端口的格式发送
 
 playerName.set("游戏姬","游戏姬")
 killsNum.set("游戏姬",0)
 // 这里给了一个样例,在GameUI里,点按钮会发送key,
 // 然后在这里就手动添加了一个键key“游戏姬”举一个例子,
 // 、实际使用时,key其实就发送所需的key就行,这个key的来源在后续更新帖子时加上链接补上说明
 
 // 壹,初始化map:
 // 1.Map获取玩家ID与名称然后存档
 Events.addPlayerJoinedListener((player: Gameplay.Player) => {
 setTimeout(() => {
 playerName.set(player.getUserId(), player.character.characterName)
 // set是存储,鼠标悬停,可以看到英文注释,
 // 第一个空是类似于箱子名称,player.character.characterName存进箱子里,箱子用Userid标记
 // 下面是日志检查能否输出那个存储了后缀名称为ook的玩家id
 // playerName.set(player.getUserId(), player.getUserId() + "ook")
 // let have = playerName.get(player.getUserId())
 // console.log(have)
 killsNum.set(player.getUserId(), 0)
 }, 3000);
 // console.log(killsNum.get(player.getUserId()))
 // 这一处初始化无问题
 })
 // 2.map删除离开玩家的数据,节约服务器内存,也防止内存泄露,减轻服务器压力
 Events.addPlayerLeftListener((player: Gameplay.Player) => {
 playerName.delete(player.getUserId())
 killsNum.delete(player.getUserId())
 })
 
 
 
 
 // 贰,战况收集:
 Events.addClientListener("zhanKuang", (player: Gameplay.Player, killer: string) => {
 // killsNum.set(player.getUserId(), 0)
 // 被击落者的连击数清零
 let a = killsNum.get(killer)
 
 killsNum.set(killer, a + 1)
 // console.log(killsNum.get(killer))
 // 伤害来源连杀+1
 onePersonsHave.push({
 // 存储每一个被击落的客户端发来的被击落消息
 name: player.character.characterName,
 killBy: playerName.get(killer),
 // 第一个空是类似于箱子名称,player.character.characterName存进箱子里,箱子用Userid标记
 // 现在是在get里输入Userid,然后函数运行,函数本身变成player.character.characterName,存进变量killBy里
 // 用于:存储查找到的伤害来源昵称
 killNum: killsNum.get(killer)
 // 存储查找到的伤害来源的连击数
 })
 
 // 目前不用管,先注释掉,这是拓展时接入其他模块再用的
 // RankMgr.Instance.AddScore(killer, 1)
 // 功能:向排行榜系统发送加分者,与加分,由于这部分代码在服务器服务器运行,
 // 使用背景:rankMgr也是服务器运行的代码,所以这是在服务器内部的通信,尝试采用dispatchlocal函数,验证发现可行
 
 })
 
 // 叁,战况发送
 let timer = setInterval(() => {
 if (onePersonsHave.length > 0) {
 let sendOneDeath: deads = onePersonsHave.shift()
 // 队列中取出的一个元素存进sendOneDead里
 Events.dispatchToAllClient("deadCast", sendOneDeath.name, sendOneDeath.killBy, sendOneDeath.killNum)
 // 对所有客户端发送,被击落者,击落来源,连续击落数,广播战况,到各客户端的UI脚本里接收这几个参数。
 }
 }, 1000)
 // 借助计时器,每秒都检查array数组,不为空时,广播一次战况
 }
 }
 }
 
 
 
 附件,解压粘贴到编辑器项目目录下,
 运行点击按钮即可看到广播
 
  测试文件.zip
(87.18 KB, 下载次数: 436) |