[开发者心得] 跑酷对抗类试水——踩影子PVP的实现思路与开发

  [复制链接]
1153 |3
脑的研发记录 发表于 2023-8-18 09:55:54 | 显示全部楼层 |阅读模式
本帖最后由 脑的研发记录 于 2023-8-26 12:29 编辑

跑酷对抗类试水——踩影子PVP的实现思路与开发小结

所用编辑器版本0.25.0.4

总览
壹,试水背景
贰,设计思路
叁,玩家的标记代码
肆,组合功能链接

没有工程附件的哦,代码给的差不多了,可以自己搓一个试试。

壹,试水背景
是参与游戏制作训练营之后,做了一点简单的跑酷游戏,觉得还是不够尽兴,跑酷元素还可以结合其他元素。于是就翻了翻一些结合例子,跑酷X解密类型,跑酷跑酷X社交类型,如233乐园的“团结一心合作解密”,如跑酷XRoguelike,在其他软件商店可下载的《Kinja Run》,跑酷XRPG,《原神》(应该行???)
当时也是在想着做一点有意思的尝试,加了一点消失地板,后来在维护游戏平衡的时候,考虑到玩家就在消失地板反复横跳啥的,然后其他玩家会掉下去。就觉得不行,但是好像有点意思,这某个关卡的消失地板的难度还会收到玩家影响。后来是想着做一点长远的打算,多少做一点多人竞技(但是技术有限于是还在蹒跚学步阶段)。后来是想起很小的时候玩的游戏,踩影子,就是大马路上两个人想办法踩到对方的影子。就着一个消失地板,一个踩影子,念叨念叨,然后觉得虽然影子不能检测,但是踩方块也可以实现靠近不走就掉落的效果。于是就安排起来,正好也为办PVP游戏做一点阶段过渡和思考落地的试水尝试。

贰,设计思路
玩法机制:借用跑酷技能去努力踩到别人脚下的方块,让别人掉下去。
增强参考:如果能有点FPS的感觉也挺好。设计一点障碍物,方便合作,伏击。
盘问AI得到的建议:“记得在游戏设计中考虑合作与竞争之间的平衡,鼓励玩家一起合作,同时也为个人技能和策略的发挥提供机会。”
具体实现:
(实际上是先有了第1.条的实现和消失方块验证,然后结合新的建议,和新的要求,再有了下面第2,3条的优化。)
1.设置四个职业,这四个职业在速度,跳跃高度,跳跃次数上进行限制(说白了就是快不快,高不高的问题)。然后就是无论选哪个职业都能独当一面,也能在团队合作中中流砥柱。希望是大家玩的开心就行,快乐之余有所启发更好。
2.设置不同功能的瓦片,给个人技能加强或削弱。
(目前力不从心,只是做了用消失方块贴出的平坦图,先让玩家了解踩落机制,其他再说)
PS.技能主要指的是,“速度”,“跳跃”,这俩属性,要不要新开其他的这里考虑到跑酷“要用最少的按钮实现丰富的按动组合”就没有再加上别的东西。
3.设置不同障碍物,使玩家不得不按某些方向移动,或提供策略实现机会,在特定场景中,玩家本身也成为障碍物,迫使对手绕道。让道途中踩到的瓦片就会实现策略效果。

叁,玩家的标记代码
主要是玩家被击落的溯源问题:找到谁击落的玩家。然后再参考FPS的团战,混战,抢点的模式,分别出一些其他地图,通过地图设计进行加强或削弱不同模式下的职业属性。
这里主要讲击落溯源,就是谁击落谁的问题。
先继续讲小故事:原来是先有了抢点这个想法,还没考虑溯源问题,然后每个消失方块会被标记,标记者分数增加,然后回到消失方块的溯源上,有开始头疼起来。不过好在所用元素比较有限,就是念叨念叨,抢点的代码能不能放在溯源里面,然后每个方块标记了,标记标记,玩家还得知道谁击落了自己,然后上传击落者,肯定存着击落玩家这个变量,然后继续念叨念叨“标记标记”,这两三天就一会儿过去了。然后人也休息好了,就灵感乍现了“玩家存储击落者不就是标记玩家?”,玩家被消失方块标记,就行了。

标记的简单思路(马后炮式回顾)
1.玩家标记消失方块,方块有个变量存储进入方块的玩家
2.消失方块标记玩家,玩家有个变量存储离开方块时方块的主人

关键函数语句(当时复制粘贴最多的几行代码,这样然后才试出来合适的位置):
let master = char.getUserId()
dispatchLocal(“sendKiller”,killerID)

当时开发遇到的问题(不解决游戏就做不成的那种):
如何实现占领,标记?
代码顺序问题:先占领方块还是先被方块标记?
如何通过代码顺序来实现:踩到别人脚下方块,别人被标记?
如何实现追逐时反击判定:就是说不能被广播出的击落者永远是被追逐的一方。
原来是无论进入离开,都是要有上面提到的两个函数。但是会有玩家离开方块挺远,自己踩到消失方块掉落,但是击落判定给了其他人。
后来改着改着,发现是加了延迟,本身会导致玩家相互标记,延迟时间控制着玩家相互标记的距离,只要小于这个距离,就有“偷袭踩别人脚下,然后快速撤回,被偷袭的玩家反应不及时掉落”。撤回的时候也标记方块。感觉没毛病。

实际解决方案
(原来是胡乱试试,最后行了,回头总结发现原来是尝试范围小,一共就几种组合,确实穷举没毛病)
穷举法:
进入离开X占领X被标记
2x2x2种组合
一共八种。
代码5分钟,bug两小时
然后这样穷举了4天,大概累积16h(一坐一上午,盯着屏幕看,启动服务器,关闭客户端,一二三四五)
最后结论:
进入方块时占领,离开方块时被标记。
后来考虑到,只要掉落下去,就一定会离开方块,所以离开方块被标记没问题。
占领方块,分先占领被标记,先标记再占领两种,都试过了,然后又在这两个组合里测试比了比,反正就是进入方块的时候进行占领,出了方块不占领,就能保证永远是最新进入该消失方块的玩家可以踩到别人脚下方块。离开方块被标记,可以解决多个玩家同时在一个消失瓦片上,大家互不标记,不会有被标记,这样不是掉落的那种离开方块,就不会有远程击落bug。(人和人离着几个瓦片,但是自己不小心掉落的,却给对面加大分的bug

消失块的改装代码:
不知道如果消失块可以被投掷是不是就能做别的小游戏了,但是FPS好像有现成的射线检测函数,用消失块这个思路做FPS,内存消耗会不会太大?

@Core.Class
export default class TemporaryFloorTS extends Core.Script {

    private masterid:string;
    //设置脚本在属性面板中可以填入地板消失的时间
    @Core.Property({ displayName: "恢复时间", group: "必填" })
    public HideTime: number = 1000;

    //设置脚本在属性面板中可以填入地板恢复的时间
    @Core.Property({ displayName: "恢复时间", group: "必填" })
    public RecoveryTime: number = 1000;

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {

        const trigger = this.gameObject as Gameplay.Trigger;
        const mesh = trigger.getChildren() as Gameplay.Mesh[];
        //if (SystemUtil.isClient()) {

        //修正2023.8.26,碰撞检测是双端,不是客户端,否则物体可见性消失,但碰撞不消失
            trigger.onEnter.add((chara: Gameplay.Character) => {
                if (chara instanceof Gameplay.Character) {

                    if (this.masterid != chara.player.getUserId()) {
                        this.masterid=chara.player.getUserId()
                       //上文提到的函数
                    }

                    setTimeout(() => {
                        mesh.forEach(element => {
                            element.setVisibility(Type.PropertyStatus.Off, true);
                            element.setCollision(Type.PropertyStatus.Off, true);
                        });
                    }, this.HideTime);
                }
            });
            //当角色离开触发器时,地板在指定时间后恢复
            trigger.onLeave.add((chara: Gameplay.Character) => {
                if (chara.characterName == getCurrentPlayer().character.characterName) {
                    // 控制全部客户端消失,但在当前客户端发送存档,

                    // Events.dispatchToServer("saveKiller", (chara.player, this.masterName))
                    Events.dispatchLocal("saveKiller", (this.masterid))
                    //上文提到的函数
                    console.log("who send message")
                }
                {
                    setTimeout(() => {
                        mesh.forEach(element => {
                            element.setVisibility(Type.PropertyStatus.On, true);
                            element.setCollision(Type.PropertyStatus.On, true);
                        });
                    }, this.RecoveryTime);
                }
            });
        //}

        //修正2023.8.26,碰撞检测是双端,不是客户端,否则游戏中只有物体的可见性消失,而碰撞不消失
    }
}


一点单双端的理解说明:
注意
进入时的判断
if (chara instanceof Gameplay.Character)
玩家离开时的判断:
if(chara.characterName == getCurrentPlayer().character.characterName)

这个修改来源于一个bug,运行测试,开了两个客户端,然后发现其中一个玩家离开的时候日志打出标记信息,而别的玩家日志也同时打出标记信息。
后来翻了翻关键词intanceof,然后翻译了一下代码,然后就发现方块被玩家标记,是所有玩家世界里的这个方块同时都被标记,但玩家被方块标记,只能是当前离开该方块的玩家被标记,其实和想象的一样,但是默认变了,原来预制体里的intanceof是意味着所有玩家可以执行标记逻辑,而参考了教程中的死亡复活一节,才明白特定if(chara.characterName == getCurrentPlayer().character.characterName)是控制当前玩家被标记。原理:如果不加限制,默认所有客户端都执行。为了在默认中调整,参考教程代码的调整方式:逻辑是所有客户端都拿自己的玩家名字,去和这个chara.chracterName去比对,只有名字相同的,也就是说这个客户端的玩家正是这个碰撞盒检测到的,就在这个客户端内部执行逻辑,其他客户端不受干扰。
(2023.8.18第一稿发布,2023.8.26该段文案翻新)
这个时候的dispatchLocal发送给的是当前玩家的属性,也就是自己发送给自己,而不是自己发送给别人。

也了解到,双端同步其实指的是每个玩家都复刻了一个相同场景,然后其中某个场景里的某个方块被标记了,其他所有场景里的这个相同方块也被标记了。
image.png

优化
原来是dispatchToServer,标记同步到服务器,后来发现这样rpc占用太大,于是就产生dispatchLocal,只在被击落,碰到死亡块的时候往服务器上传标记。
于是就有了死亡块的改装。
后来也新增了cilentKiller脚本,来作为标记信息流集中的地方。以后再拓展只要在里面加上接口就行。
目前cilentKiller脚本负责接收消失方块的标记信息,接收死亡块的通知,上传玩家标记信息,接收广播消息,调用UI显示广播。

clientKiller参考代码


import boardcast from "./boardcast";
@Core.Class
export default class clientKiller extends Core.Script {
    public killerid: string
    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {
        if (SystemUtil.isClient()) {
            Events.addLocalListener("saveKiller", (masterid: string) => {
                this.killerid = masterid
                console.log("killerid is : " + this.killerid)
            })

            Events.addLocalListener("readKiller", () => {
                let murderid = this.killerid
                Events.dispatchToServer("zhanKuang", murderid)
                //   RankMgr.Instance.clientReqAddScore(1)
                setTimeout(() => {
                    this.killerid = null
                }, 400);
            })
            UI.UIManager.instance.show(boardcast)
            UI.UIManager.instance.hide(boardcast)
            //这两句用于解决bug,不先显示一次,UI文字的改动就不显示。就是说进入游戏的玩家接收到第一次广播时,UI显示不正确。
            Events.addLocalListener("show", () => {
                UI.UIManager.instance.show(boardcast)
            })
        }
    }

    /**
     * 周期函数 每帧执行
     * 此函数执行需要将this.useUpdate赋值为true
     * @param dt 当前帧与上一帧的延迟 / 秒
     */
    protected onUpdate(dt: number): void {

    }

    /** 脚本被销毁时最后一帧执行完调用此函数 */
    protected onDestroy(): void {

    }
}


新功能方向:
除了最多的消失方块,其他的加速减速红蓝方块,参考机关预制体的脚本代码就行。
其他什么颜色的形状的方块,
还有第一次进去会立即被击落的黑色方块,然后再从黑色变回和普通消失方块一样的绿色,也变成一样的普通消失方块。
站上去会隐身的黄色方块,但是第二个玩家进去,该方块就会和普通消失方块一样消失。离开方块隐身消失,或者隐身时间只支持2s。



新模式方向:
混战其实就是PVP个人战,作为初步测试。
然后

团战,比击落总数。
抢点,类似塔防,站到某点一段时间就加大分,分数够了就赢。
参考FPS的各种模式应该可。

肆,组合功能链接

击落广播:击落广播功能——单脚本(含附件) 口袋方舟论坛|面向全年龄的UGC互动内容平台与交流社区 (ark.online)

适配排行榜:getUserId接口的排行榜翻新,附加隐藏显示按钮——用Replicated制作排行榜 口袋方舟论坛|面向全年龄的UGC互动内容平台与交流社区 (ark.online)






回复

使用道具 举报

口袋方舟 发表于 2023-8-18 14:06:04 | 显示全部楼层
回复

使用道具 举报

苍苍 发表于 2023-8-18 14:13:29 | 显示全部楼层
讲得好详细
回复

使用道具 举报

天黑路滑人心杂 发表于 2023-8-18 18:13:34 | 显示全部楼层
很棒!
回复

使用道具 举报

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