在2019年,当市场上的大多数篮球比赛都是“纯粹的射击”游戏玩法时,其中之一使玩家的眼睛闪耀。在过去的一年中,他们在微信,QQ,Toutiao和其他平台上推出后,累积球员达到了3,000W+。
迷你游戏版的出色表现增强了研发团队启动该应用程序的信心。经过将近一年的抛光,手机游戏版本的“一刻篮球”已经变得越来越成熟并积累了力量,最近在AppStore游戏体育名单中脱颖而出。
无论是迷你游戏版本还是本地版本,“单幕篮球”都使用可可创作者进行开发。本机版本和迷你游戏版本有什么区别?进行了哪些更改或优化?这次是“ Boss Crabs”,他是“一刻篮球”的主要课程,武汉萨吉(Wuhan Sagi Games)将与我们分享从小游戏到应用程序的游戏的“进化”体验和体验。
从小型游戏到应用程序,它不像移植那样简单。在迷你游戏平台上,“竞争性 +运动”是一个利基类别,具有相对较少的优秀产品。集中式平台提供了转移流量的建议,好的产品可以迅速脱颖而出。在本地平台上,如何在竞争激烈的市场中取得突破是团队面临的困难问题。
在开始开发本地版本之后,为了使游戏更具玩具并具有更好的视觉效果,项目团队从最初的4个人扩展到了数十个人。在技术层面上,我们从PVP Battle实施和优化,AI行为树的设计和难度配置,脊柱装扮系统的改进和高级技能的方面全面优化了游戏,以便在本机平台上的手机游戏版本“ One-One-trick篮球”具有更高的质量性能。
PVP战斗
我们曾经认为,在迷你游戏中进行真正的人战是一个错误的主张,令人不愉快,但是当游戏发展到42,000人时,对真正的人类战斗的呼吁越来越高。让我们重新检查PVP的主张:
传统的IO迷你游戏,由于一场游戏中的参与者数量众多,因此很难尽快玩耍,而没有数值增长,低用户粘性,而且确实没有成本效益。
“一键式篮球”着重于1V1战斗。玩家可以通过共享卡进行约会。游戏率很高,角色具有数值增长和培养元素。进行PVP是完全可行的。
由于“单块篮球”使用ECS结构并自然支持框架同步,因此我们迅速建立了框架同步解决方案。有关详细信息,请参考社区老板的这篇流行的科学文章:。
在迷你游戏平台上,我们使用Tencent Cloud在线战斗平台(Mgobe)。 “单块篮球”是Mgobe的早期合作伙伴。早在2019年底,“单骨篮球”就已经与Tencent Cloud测试合作。作为一名中年和老年程序员,已经从事迷你游戏十年了,我认为Mgobe一点也不糟糕,这确实是迷你游戏平台的最佳选择。不幸的是,Mgobe即将停止服务,我们还将连接到我们自己的战斗平台。
启动应用程序开发后,我们使用Go参考Mgobe来编写一组在线战斗平台。 API名称是一致的,并且可以调整客户端而无需进行太多更改。在编写平台时,我们专注于以下方面:
分布式加自动弹性部署:当峰突然急剧增加时,将自动部署新服务器以适应更改。峰值下降后,服务器成本将自动回收并降低。
协议压缩:表示一个字节中角色的操作指令,也代表尽可能精简的其他协议。
支持TCP和UDP:由于UDP具有冗余说明,当网络不好时,消息数据包将被抑制,并且对低端手机不太友好。因此,TCP方法仍用于低端手机连接到战斗服务器。
详细的日志记录和监视工具。
战斗不同步的问题
由于帧同步完全取决于客户端的计算,因此两个客户端的计算结果必须在游戏过程中完全一致,否则屏幕将不同步。
在迷你游戏版本的“一篮子篮球”中,因为我也是第一次进行框架同步,并且缺乏经验,因此首次启动了战斗版本的许多不同步。经过调查,大约是以下原因:
浮点数的问题:0.3+0.4 = 0.7,可以为0.7-0.4!== 0.3。尽管总是缺少舍入或保留2位小数位的操作。
参数不一致的问题:为了意识到两个角色从左侧的角度玩游戏,当我初始化战斗时,我翻转了角色数据并翻转了战斗说明,这在计算的两端都导致了不一致的参数。不一致的参数必须在计算过程中成反处理,从而导致大量的浮点数。
战场重置的问题:在上一场战斗之后,一些数据没有清除和干净,并带入了下一场比赛,导致不同步。
不一致的操作令问题:谁首先击球?
解决方案如下:
从逻辑上讲,房主总是在左边,租户在右侧,但是房客会翻转UI以满足其在视觉上的要求。
优化战前和战后数据清洁逻辑。
检查逻辑错误。
这里还有另一个非常重要的问题:如何找出在线操作期间玩家不同步?
由于帧同步取决于客户端的计算,因此服务器无法知道播放器是否不同步,因此脱离Sync的故障排除只能依靠客户端日志来分析。我的方法是:
重新连接和框架追逐处理
有两种重新连接的情况:
该线在游戏中被打破和重新连接。
游戏异常退出后重新连接(战场需要恢复)。
当客户端收到帧消息时,它将保存当前的帧号。断开线路连接后,由于服务器不会停止发送框架广播,因此在重新连接后将在中间断开框架。目前,游戏逻辑无法继续。 SDK必须发送请求以填充框架接口,检索缺失的帧消息,将其插入框架队列,然后将其转发到逻辑层以驱动游戏。填充框架的侧面将滞后在屏幕上的另一侧。目前,需要框架追逐操作,并且计算在逻辑层加速,并且框架队列被干净地处理,并且图片自然会赶上。追逐帧时,您可以在1 dt之内处理所有这些框架,也可以在每个DT中处理几秒钟的数据,以便CPU可以呼吸并在屏幕上实现快进效果。
在游戏重新启动中追逐帧的过程与中间的追逐帧基本相同。不同之处在于,您需要恢复战场并从第一帧开始。在“单牌篮球”中的一场比赛中,一场比赛的1到2分钟,没有压力再次从头开始追逐帧。
PVP其他经验
人工智能行为树
该应用程序版本与迷你游戏的AI开发一致,并且仍然是行为tree3。
设计思想
该角色在球场上有三个州:进攻/防守/没有球。行为树设计围绕这三个状态旋转:
判断你能做什么?技能/布局/扣篮/突破/射击(角色使用有限的状态机)。
判断与篮球的距离(在不同距离进行拍摄的可能性不同)。
我应该在哪个阶段采取行动?
接近对手。
是否抓住球。
对手跳了起来。我应该跟随跳跃并挡住射门吗?
球在空中吗?如果您在那里,您是否争夺球?
球在地上吗?现在去接它!
总体而言,行为树的设计类似于流程图,只需很好地设计所有分支即可。
难度控制
在迷你游戏中,以经典的11分游戏玩法为例,我创建了10种难度级别的行为树。每个行为树之间的差异极小。主要区别在于AI的处理延迟和行为的进入概率。
在该应用程序上,计划需要对难度进行更详细的控制。如果有100级困难,则必须根据原始方法创建100种行为树……这显然是不现实的。解决方案是在AI的每个特定行为的入口上设置卡片,并组合配置表以控制行为的概率和延迟。
脊柱刷新系统
多个字符和多个西服打扮想法
在迷你游戏平台上,我们指的是Cocos Creator示例中的装饰实现,也就是说,在脊柱插槽上使用附件来代替另一个脊柱上的插槽上的附件。
我们的用法略有不同。所有字符,初始皮肤和西装皮肤都在同一脊柱文件中。使用时,我首先阅读当前脊柱的getRuntimedata()以获取相应西装皮肤的实时数据,然后在西装插槽中取附件以替换当前角色皮肤相同插槽的附件。它太长了,所以我会发布代码:
changeCloth (skinName: string, slotName: string): any {
let spine: sp.Skeleton = this.node.spine
let skeletonData = spine.skeletonData.getRuntimeData()
let skin = skeletonData.findSkin(skinName)
const slot = spine.findSlot(slotName)
const slotIndex = skeletonData.findSlotIndex(slotName)
const attachment = skin.getAttachment(slotIndex, slotName)
slot.setAttachment(attachment)
}
但是,随着越来越多的字符,脊柱变得越来越难以维护。 50个字符 + 20组在一个脊柱中。导出需要将近10分钟,并且输出图也很大,需要分页。
我想到将50个字符分为50个刺,这带来了另一个问题:脊椎中有动画,50个刺需要将动画复制50次。每次添加动画时,都必须将其同步到所有刺。艺术表明您想呕吐血...
扎根大脑后,我想过使用结合新皮肤来实现它的方法:
底座包含动画定义和基本的三色皮肤(黑色皮肤/白色皮肤/黄色皮肤),但没有面部图;
Skin.Sine配备了角色的脸部和基本外观;
suit.sine是集合定义。
发布代码片段演示:
export default class SpineUtil {
static async setSkin (spine: sp.Skeleton, heroId, skinId, collocation = {}) { // posType 1头饰 2上衣 3裤子 4鞋子 5手部 6腿部
const skeletonData = spine.skeletonData.getRuntimeData()
const baseClothesData = await AssetLoader.loadResAsync(`spine/clothes/c_${heroId}/c_${heroId}`, sp.SkeletonData)
const baseClothesDataRuntimeData = baseClothesData.getRuntimeData()
const baseClothesSkin = baseClothesDataRuntimeData.findSkin('c_' + skinId)
if (!baseClothesSkin) return
let newSkinName = 'newSkin' + heroId + skinId
for (let pos in collocation) {
newSkinName += '_' + collocation[pos]
}
const newSkin = new sp.spine.Skin(newSkinName)
const { SkinColor } = app.db.actor.GetActorById(heroId)
const findSkin = skeletonData.findSkin(['white', 'yellow', 'black'][SkinColor - 1])
newSkin.copySkin(findSkin)
// 使用默认外观
for (const skinEntry of baseClothesSkin.getAttachments()) {
const slot = !cc.sys.isNative ? skinEntry.slotIndex : baseClothesSkin.getEntrySlot(skinEntry)
const name = !cc.sys.isNative ? skinEntry.name : baseClothesSkin.getEntryName(skinEntry)
const attachment = !cc.sys.isNative ? skinEntry.attachment : skinEntry
this.addAttachment(SKIN_PART, newSkin, slot, name, attachment)
this.addAttachment(ARM_PART, newSkin, slot, name, attachment)
}
... 省略部分代码
if (skeletonData.skins[skeletonData.skins.length - 1].name === newSkin.name) {
skeletonData.skins[skeletonData.skins.length - 1] = newSkin
} else {
!cc.sys.isNative ? skeletonData.skins.push(newSkin) : skeletonData.addSkin(newSkin)
}
spine.setSkin(newSkinName)
}
}
您可能会注意到,本机API与JS不同,因为本机中的某些方法和属性未导出,例如New Sp.spine.skin将在本机上报告一个错误,因此我们已修改了脊柱C ++下的脊柱运行时库。
其他一些脊柱技巧
COCOS Creator 3.6将完全优化脊柱性能和装饰,预计该版本将于今年中期发布。由发动机组
好吧,我暂时写这篇文章。如果我继续写作,我会真正毕业(实际上姐姐C敦促我写作)。如果将来有时间和精力,我将添加一些开发经验,以在论坛上分享。欢迎来到论坛上发布和交流:
本文是“ Cocos Chartral Comparie 4 Call for Papers”的参与者。提交已经结束。感谢您的热情参与!我们在本期中添加了“流行作品”选择。单击文章的末尾[读取原始文本],访问论坛,投票给您喜欢的作品!
过去令人兴奋
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请联系本站,一经查实,本站将立刻删除。如若转载,请注明出处:http://www.tzsbc.com/html/tiyuwenda/7433.html