[开发者心得] 击落广播功能——单脚本(含附件)

[复制链接]
1195 |3
脑的研发记录 发表于 2023-8-16 11:08:56 | 显示全部楼层 |阅读模式
本帖最后由 脑的研发记录 于 2023-8-16 19:46 编辑

击落广播功能——单脚本(含附件)

image.png
受论坛师傅的指点写的代码,现在分享。
总览:
第一部分:故事背景,文案初衷
第二部分:代码总思路
第三部分:代码与代码详解
第四部分:可搭载进广播系统的拓展模块链接(不定期更新)

有附件,编辑时编辑器版本: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, 下载次数: 85)
回复

使用道具 举报

汽汽汽汽水 发表于 2023-8-16 13:07:11 | 显示全部楼层
耶 感谢分享
回复

使用道具 举报

脑的研发记录楼主 发表于 2023-8-16 13:14:14 | 显示全部楼层

一起进步啦
回复

使用道具 举报

叽里咕噜小胡桃 发表于 2023-8-23 15:24:08 | 显示全部楼层
加油加油加油!
回复

使用道具 举报

热门版块
快速回复 返回顶部 返回列表