本文作者为明星团队汉家松鼠游戏工作室的CEO成功(CG),他将于11月10日在深圳举办的第四期腾讯游戏学院品鉴会上,分享汉家松鼠旗下《汉家江湖》、《江湖X》等游戏从立项、研发到上线的实战经验,并谈谈独立小团队该如何长线经营。

这是一篇严肃的联机游戏开发入门介绍,本文所述代码开源,文末可获得地址。

关于游戏的实时联机对战,目前是很多游戏开发者研究课题,也延伸出了很多概念,如“状态同步”、“帧同步”,目前很多游戏开发框架也提供了这样的开发一些理念和组件,比如unity的The High Level API等等。

本文不希望传授如何用框架、服务端引擎等来搭建一套商业级的框架。而是希望从最基本的网络通信原理开始,一点点的进行朴素的分解和搭建,旨在从原理上概述联机游戏的设计思路以及抽象于计算机网络的通信框架如何设计和构建。

我个人编写这套DEMO包括完整的客户端和服务器,也是用于调研工作室游戏《汉家江湖》后续的实时联机部分如何搭建和开发,并且后续作为我们自己的一个实时联机通信框架的测试程序。

实际完成的DEMO如上,每个玩家是一个点,只有一个操作:使用左下的虚拟摇杆进行方向控制。在移动过程中不断喷射子弹,命中对方即可扣血,在规定时间内比谁的击杀的人数多。由于是一个简单的DEMO,我们不在美术上做太多东西(请原谅我的五毛钱PS技术),主要是为了讲解程序如何来设计。

工具部分,客户端我们使用unity开发,服务端我们直接从0开始写一个基于socket的服务器。

我们使用状态同步来做这个DEMO(理论上帧同步机制是更加适合这种IO类游戏的,我们为了逻辑清晰简单,先用状态同步来做,后续有时间我再补帧同步的方案。)

状态同步

什么叫状态同步?

简单的来理解就是所有的数据在服务端进行计算和校验,客户端将操作上发到服务器,服务器不断的告诉客户端计算结果,由客户端进行展现。

客户端逻辑结构

客户端的整体框架逻辑非常简单,unity是典型的单线程编程,我们只需要在FixedUpdate里出来所有的消息队列即可。

其实按照socket的非阻塞模型来说,我们客户端也可以不使用接收线程而使用纯粹的单线程。我们在这里不再赘述,再次重申,我们只是很简单粗暴的一切为了编程简单易于理解。

客户端维护一个消息队列的原因是,我们每次收到的数据需要顺序的执行。在unity中我们所有改变UI或者界面相关的逻辑需要写在主线程中。所以我们使用一个消息队列来进行传递,每次FixedUpdate时依次处理所有的队列中的消息。然后处理客户端应该发送的指令。需要注意的是,由于消息队列被两个线程同时访问,作为临界区数据,需要加线程锁。

服务端逻辑结构

我们这里通信协议使用tcp(大家实现也可以使用udp,都类似),那么服务端是一个典型的处理连接、处理请求并分发数据的逻辑结构。我们为了编程简单,也先不顾效率的每一个客户端连接我们起一个专门的接收线程,然后统一分发处理,逻辑结构如下:

使用单线程来处理整个游戏的主逻辑,是一个典型朴素且最简单的编程思想。当然,这里可能会存在瓶颈问题,所以具体中间有很多可以优化的点,比如一些物理计算实际可以拆分成多线程或者跟进一步使用GPU、比如接收客户端数据可以使用非阻塞模型或者线程池……这里不再多做说明。

其实我们抽象一下,以上整个框架实际上适用于任何类型的联机游戏。对于网络连接层,我们需要很清楚几个概念:

每一个客户端连接,在服务端维护一个session,这个session主要处理与客户端的通信(收发数据)以及该客户端的一些临时状态(我们这个游戏没有)。

通信协议

服务端维护了一份完整的数据结构,在本游戏就是当前游戏中剩余时间、一共有多少个玩家、玩家的HP、玩家们所在位置和移动方向及速度、一共有多少颗子弹、子弹的移动方向和速度……

我们称整个以上数据为一份全量状态,它描述了整个游戏二手QQ当前的情况。

最朴素的思想就是服务端不断的将整个全量状态分发到每个客户端,这样客户端就只用管显示就行了。但实际的开发过程中会发现这样飞快就会达到性能瓶颈(因为发送的数据量太多,网络IO吃不住。而且也可能因为不断的要生成全量数据快照,CPU的计算量也非常大。)所以需要优化,接下来我们具体探讨一下通信协议如何来做。

通信协议是客户端和服务端共同约定的一个数据结构,其包含了双方可以发送并对方可以识别处理的数据包。

在设计网络协议的过程中,我们需要有一个的分层概念,我们更多的只需要来关心业务逻辑,也就是具体发送什么样的数据,底层的话这里我使用protobuf(性能最高的开源序列化、反序列化库)。

我们使用protobuf将数据结构序列化为二进制数据,并且通过socket来进行发送,接收方收到后使用protobuf反序列化为业务协议,提供给上层逻辑代码进行解析。所以实际上蓝色部分都是使用开源库来进行的,我们只用关注实际的游戏业务协议(绿色部分)。

我们拆解一下实际的业务协议,如下:

客户端 -> 服务器:
1、加入游戏
2、角色行动+开火

服务器 -> 客户端:
1、新玩家加入游戏
2、某个玩家被击中/击杀
3、玩家移动+开火
4、全量状态同步(用于游戏进行到一半有玩家加入,发送给他当前对局的整体情况)
5、时间流逝

所以实际上我们游戏的编程,就是在客户端和服务端互相生成并处理以上数据包的过程。对应前面的流程图就是所有的消息队列处理和生成。由于具体和游戏内容相关,各位有兴趣可以看代码,这里不再多做描述。

代码结构

另外,我需要更进一步说一下关于代码结构的一些思考。

由于服务端和客户端实际上有大量的数据结构交换,我认为一个比较好的方式是一份代码两边使用。所以我服务端也是使用C#编程开发,将一个脱离框架的dll(主要是业务通信协议和各种两边使用的数据结构、常量和计算工具)同时分发给两边使用。

具体可见服务端代码中的ShootGameServer.SharedData,这份代码同时会生成dll到客户端Unity的/Assets/Plugins目录下。

另外关于网络层的适配,我整个抽象了出来,所以大家如果有兴趣的话,可以使用UDP重写或者自己来编写底层的通信链路(下图橙色部分)。其TCP实现位于ShootGameServer.SharedData.Network.Impl

其中服务器我写了一个通信链路的集成单元测试,位于:

开源代码中通信链路KCP部分代码我尚未集成完毕,大家可以忽略。

未来的一点点计划

目前我们工作室计划针对性的开发一套业务无关的网络链路层框架,主要实现的功能是“开房间”-“加入游戏”-“游戏”-“结束”的一套基于云服务分布式调度的管理框架。

通俗来说就是可以开发 实时联机的IO类游戏、百人联机的吃鸡类游戏、MOBA类游戏这种高实时性互动性要求的游戏。

我们计划将各个模块性能消耗优化到极致,并且未来提供多种高度封装易用的编程模型。

此模块我们会先在自己的内部的游戏项目中实践使用,未来考虑开源+分享出来。或者提供一套便捷易用的SDK,供外部使用。

如何从零开始开发一个实时联机游戏?相关推荐

  1. 如何从0开始开发一个实时联机游戏

    这是一篇严肃的联机游戏开发入门介绍,本文所述代码开源,文末可获得地址. 关于游戏的实时联机对战,目前是很多游戏开发者研究课题,也延伸出了很多概念,如"状态同步"."帧同步 ...

  2. 视频教程-用Java从零开始开发一个物联网项目-物联网技术

    用Java从零开始开发一个物联网项目 多年的产品设计和开发经验,带领团队完成多个知名产品.历任多家大型公司的Java架构师,对知名框架的源码均有深入研究.拥有IT一线开发.教学10多年的实战经验,能充 ...

  3. 如何开发一个扫雷小游戏?

    如何用C#开发一个扫雷小游戏? 十分自豪的说,计算机编程就是变魔术,每一个coder都是一个魔术师. 初学C#的时候,我相信很多人都和我一样,学会了基本语法,掌握了基本的数据结构,也见过了不少微软提供 ...

  4. react的导出是怎么实现的_从零开始开发一个 React

    这个是从零开始开发一个 React 系列的第七篇.想要访问之前的内容可以点击下方的链接进行访问: 最简单的实现,包括 vdom 结构,createElement,ReactDOM.render 增加 ...

  5. 从零开始开发一个大型网站

    从零开始开发一个大型网站 更新:前端代码已全部由TypeScript进行重写 这是本人第一个从零开始开发一个大型网站(前后端+部署代码),是一个内容分享社区,详细信息见github.目前还是开发中后期 ...

  6. 开发一个Canvas小游戏 实现一个游戏“引擎”

    前言 这个游戏其实在三四年前就写了,中间还重构过好几次,之前都是用简单的面向对象和函数式编程来写,游戏中的元素关系到还是分的挺开,但是游戏的渲染,运算等逻辑分的不够清晰,整个逻辑基本都是自顶向下的流水 ...

  7. 用python开发一个推箱子游戏

    好的,为了开发一个推箱子游戏,你需要了解一些基本的编程概念,如变量,循环,条件语句和函数. 首先,你需要定义游戏场景,即箱子和人物所在的空间.你可以使用二维数组表示游戏场景,每个元素都代表一个格子. ...

  8. Android-IM从零开始开发一个即时通讯项目

    Android-IM从零开始开发一个即时通讯项目 https://www.jianshu.com/p/dca480006691 关于聊天室项目 聊天室项目,也被称为即时通讯(IM). 其原理是服务器是 ...

  9. 如何从零开始开发一个小程序

    如何从零开始开发一个小程序 开始 申请账号 开发设置 开发工具 新建小程序 阅读文档 模版语法 项目架构 开始编写 基础语法 wx:for循环 wx:if判断 页面导航 自定义组件引用 样式修改 单行 ...

最新文章

  1. 5、Linux-Mac配置环境变量
  2. uboot流程——命令行模式以及命令处理介绍
  3. Linux命令 比较文件
  4. Dockerfile中通过ENV指定动态参数在RUN时传递参数(部署后台jar包时指定端口为例)
  5. 织梦dedecms如何对列表添加判断语句
  6. 【Nginx】判断URL中是否存在某个参数Parameter
  7. mysql5.6 mysqld safe_mysql程序之mysqld_safe详解
  8. android通过用户名密码访问服务器获取信息_MySQL ------ 管理用户对数据库的访问控制(GRANT 与 REVOKE)(二十九)...
  9. 基础——ASP.NET页面的生命周期
  10. 15条常用的视频音频编辑脚本命令(mencoder/ffmpeg等)
  11. Java中序列化的好处及意义
  12. CSS3: Media Query实现响应式Web设计
  13. python特征工程意义_python数据挖掘--特征工程篇(附代码)
  14. Bootstrap输入框组中可以使用的元素
  15. URLClassLoader
  16. 网页版结题报告html没了,[转载]我的结题报告
  17. Veu表达与v-model
  18. php文本框代码_php怎么用代码给文本框输入值
  19. Win7和Win10操作系统优劣对比,看完你就懂了!
  20. 【从饮水机到名人堂之c语言】操作符详解(1)

热门文章

  1. 计算机语言中空下划线,2017-7-31 Shell脚本编程基础
  2. php 枚举类型比较,java 枚举类比较是用==还是equals?
  3. navicat连接mysql闪退_Navicat连接MySql8.0的各种问题及解决方法
  4. TwoSum,从O(n^2)到O(nlogn)再到O(n)
  5. Kubernetes 编写自定义 controller
  6. ansible基础-Jinja2模版 | 过滤器
  7. 32位汇编第七讲,混合编程,内联汇编
  8. ionic安装插件常用命令
  9. Mac 录制视频,并转为GIF格式
  10. 【转载】Linux系统挂载NTFS文件系统