以下文章来源于腾讯游戏学院,作者腾讯游戏学院
“和学霸一起学”栏目推送游戏相关的专业课程内容,通过相对专业、体系的知识内容,帮助大家提升对游戏的认识水平和理解力。本篇内容源于由清华大学美术学院与腾讯游戏学院联合制作“游戏程序设计”系列课程,课程名称为《游戏循环及实时模拟》(讲师:兰翔)。
本文通过游戏循环概述、游戏计时机制与游戏循环驱动的主要子系统的介绍,并结合demo游戏《无尽之路》,具体讲解各子系统与子系统开发的方法。《无尽之路》基本上没有什么美术资源,大都是Unity自带的模型,所以看起来比较简陋。它设计了一个地形,上面摆了很多小球,而玩家在地面上移动,而几个AI跟在玩家身后,在这个场景中大概有两千多个小球。游戏规则也比较简单:玩家通过鼠标操作移动游戏角色,游戏角色走到哪个地方,上面的球就会掉落,玩家被砸到就会掉血。
#01游戏循环概述
各类程序及其特点
常见的程序,第一类是面向任务的被动处理软件,没有任务就可以休眠。一般包括:命令行程序、GUI(图形用户界面)程序、操作系统服务、WEB服务、MIS(管理信息)系统、ERP(企业资源规划)系统以及进销存系统和财务软件等财务系统。这类软件的主要特点,是被动处理。只有去请求一个功能时,才需要进行处理。如果不做请求,就不需要做什么,自动休眠。第二类,是自动控制软件。在给定设定值后,这类软件就会通过调节器,进行自动调节。这类软件的特点,是主动处理,不能停止运行。特别是在工业控制中,如果软件停下来,现场可能就会失控。而游戏程序,相对于上面两类软件,有自己的独特之处。游戏最典型的特点是,游戏世界是对真实世界的模拟。即使是小型游戏,比如棋牌类游戏,也是对现实世界棋牌的模拟。大型游戏,比如最典型的RPG(角色扮演类游戏),则是完完全全在模拟真实世界。此外,游戏世界和真实世界类似的地方在于,即使玩家不做任何操作,游戏世界还是会正常运转。正是由于这个特点,游戏需要主动运转,而主动运转整个世界的驱动,是在游戏循环中进行的。游戏循环的作用及层次
在游戏行业,游戏的循环被称为“心跳”。游戏循环主要有三个作用:①驱动游戏世界的运转。游戏在每一次“心跳”时,都会进行相应处理,来驱动整个游戏世界的运转。②驱动游戏中NPC的行为。NPC是非玩家角色,一般由AI控制。NPC的行为,也是靠循环驱动的。③实现游戏世界与玩家的交互。而本文所讲的游戏循环,主要指前端的程序循环。一般分成两个层次:游戏引擎循环和游戏逻辑循环,游戏循环一般是由底层的引擎循环驱动的。游戏引擎循环
3D引擎的循环,其实是在模拟真实世界的摄像。相当于在游戏世界中,有一个摄影师拿着相机在观察这个世界。所以,在3D引擎中,摄像机是最主要的概念。此外,3D引擎的概念,还包括可移动物件、场景和UI。可移动物件,指在游戏中可以动的物件。场景,通常是游戏中的游戏世界,游戏里所有的动作和事件,都在这个场景里发生。UI,通常在玩家和游戏交互的过程中会涉及。在游戏中,引擎往往服务于相机、UI系统、场景管理、游戏物件的管理和支持。引擎的主循环,主要包括上图的五个步骤。首先是游戏逻辑的循环。引擎里会有一部分去驱动游戏逻辑的循环。NPC、可移动物件位置的变动、属性的变化等都存在于游戏循环中。其次,游戏逻辑控制可移动物件的位置、旋转、缩放等变化,进行Transform更新。当游戏场景比较复杂时,可能会采用空间数据结构进行管理,涉及二叉树或者八叉树,以及相应数据结构的更新。然后,是整个场景的渲染。场景渲染结束后是渲染UI,因为UI一般是覆盖在场景表面上的。最后,是双缓冲的切换。一般在显示一帧游戏画面时,另一帧在后台就会提前写好,这样画面的显示就会比较连贯。上图左是搭建的一个场景,包含相机视角和各种物件的展示,图右是屏幕中的呈现效果,它实际上是所有3D物件在相机近裁剪面上的投影。游戏逻辑循环
游戏逻辑的循环是由引擎来驱动的,一般在引擎循环每一帧的开始或结束进行游戏逻辑的处理,不会嵌在中间。因为引擎的整个渲染需要加锁,如果在中间进行修改,可能会影响整个引擎的管线。游戏逻辑的循环主要是用来驱动游戏的各个子系统,包括网络、场景、资源、游戏对象、AI、战斗、剧情和UI等。其中,游戏对象指玩家和NPC,AI主要用来控制NPC,战斗包括技能和BUFF等信息。游戏逻辑循环一般有两类风格:一种是事件或消息驱动,类似windows的消息循环机制,更有设计特点;另一种是静态循环,依次调用各个子系统的Tick(即“心跳”),这种方式比较直白,容易理解。一般来说,游戏逻辑循环更常采用静态循环的方式。因为游戏开发是一个多人协作的过程,而且游戏开发的时间一般都比较长,可能会有人员的流动,所以保持简单和可理解,对开发人员来说是比较重要的事情,而且,简单对于产品质量的影响,比采用更好的技术可能会更大一些。而事件或消息驱动的方式,编写和调试比较复杂,代码不易理解,所以不太常用。游戏循环分类
游戏循环主要可以分为三种类型。第一类,是Windows消息主循环。以前在Windows上,都是GUI系统(GraphicUserInterface,图形用户界面),Windows是消息驱动的,所以游戏的循环是在Windows消息的主循环里。相当于每一次Windows程序处理完消息队列里的消息,就会做一次游戏的Tick。第二类,是游戏帧的回调。图中的是比较老的windows系统上的开源引擎——OGRE。它在每一帧的开始和结束有两个回调,中间是游戏引擎的循环,渲染当前的场景。两个回调,实际是执行游戏的逻辑,修改游戏世界里逻辑层的一些东西,中间通过引擎的方式表现出来。第三类,是Unity3d。它把游戏开发做的很简单,允许开发者不用再去了解很底层的东西。它使用的是一个C#语言的脚本,可以理解成一种事件驱动。但实际上Unity3d是在MonoBehaviour上,在C++里直接调用了Awake、Start、Update和FixedUpdate这四个方法,而不是用C#的event机制。上文提到过,游戏循环是在驱动各个子系统。每一个子系统基本上都有一个“心跳”,循环就是依次对各个子系统“心跳”的调用。循环如果是静态方式的话,可以很直观地看到各个子系统的调用。如果是事件驱动的方式,那么看到的是一个已经注册好的列表循环Tick,这个循环会随各子系统工作与否产生变化。比如,某些系统可能不需要工作,那它就可以从列表里移除,Tick的时候就会少Tick一些,所以性能上可能会稍微好一点。但对于一般游戏的系统,特别是对RPG游戏,大多数子系统都是要一直工作的,所以两种方式的差别很小。游戏里的时间
游戏里的时间,一般有两类:绝对时间和相对时间。关于相对时间,可以用Unity的Timescale属性来解释。这个属性默认是1,当调成2、3或4时,游戏进程就会变快,这种时间就是相对时间。游戏都是通过心跳来驱动的,心跳本质上是对游戏世界的模拟,这种模拟是基于时间的。相对时间代表了一个按照某种速度流逝的时间,时间流逝后就会产生对应的影响,所以如果把这个时间的速度加快,整个演算的速度就会变快。相对时间涉及两个概念,一个是加速播放,也称为重演。比如,在王者中,如果玩家中间掉线了,重新连接上的时候就会看到,各种动作都会加快,这就相当于重演。另一个是和重演相对应的反演,也叫时间倒流。反演最终可以无缝做到像录像一样,就是把每一帧都记下来,这样就可以随意跳到任何一帧。但反演的实现比较难,因为可能不是每一帧都能被记下,而是定期记录一个快照与基于最近快照演算相结合。假设某一帧里有一个NPC被杀死,那它就消失了。如果没有做任何记录,然后根据玩家操作往回反演的话,到这一帧时,只知道有一个NPC消失了,但是恢复回来后发现这个NPC很多的信息其实都没有。这是因为游戏演算的时候,每一帧都是对之前整个历史信息的积累,除非真的把NPC信息记下来,否则,反演的难度是很大的。所以,拥有时间倒流功能的游戏,一般来说技术上是比较先进的。其次,是帧时间和实时时间。帧时间就是每帧的时间,循环里是各种各样的Tick,一般来说,取一帧的时间跟上一帧计算,这样就能知道时间流逝了多久。游戏里的逻辑,很多时候是以每一帧时间作为步长去演算的。所以,很多时候在游戏里使用帧时间就够了。实时时间,就是真实时间,任何时候去取,取到的都是真实流逝到这个点的时间。游戏逻辑的Tick里会做很多运算,假设每秒钟需要30帧,那么每一帧最多只能有30ms的时间,但如果运算一帧需要50ms,并且在这一帧里把运算全部做完,那么帧率会下降一半,所以需要把逻辑上的一些处理,进行分帧处理。把一个操作分到若干帧中去完成,就需要用到实时时间。在运算过程中,实时