游戏引擎开发中需要注意的循环结构
我们在游戏设计和开发中,尤其是引擎开发中,逻辑循环是一个重要组成部分,循环决定了游戏的基础逻辑和运行方式,在不同的开发环境和语言下,对于循环的释义甚至相差甚远,那么我想和大家分享的是在Silverlight游戏开发中,循环的设计方式和做法。
以下内容来自以往的游戏开发经验,可能在其他语言中的相关文章更加详细,谨在这里讨论有关在Silverlight游戏开发中的应用。
在传统的开发观念中,无论任何开发环境,它都逃不出While,代码一般是这样:
while (true)
{
if (GameExit() == true)
break;
else
GameLoop();
}
这个代码方式只是一个模型,可以将其理解成为一个不停在检查状态的状态机。
那么在Silverlight游戏开发中,我们是否也可以这样应用呢?道理上差不多,目前来说大部分的做法只是实现了一个根(Root)部,这方面深蓝色右手以及很多其他朋友都有了各种各样的解决方案,有兴趣的朋友可以找他们的文章,无论是用线程(Thread)、故事板(Storyboard)、Rendering、DispatcherTimer,都是一个好的循环体的开始。
对于游戏而言,尤其是网络游戏,我们将面临着大量的交互,这些交互可能来自用户,也可能来自自身的游戏逻辑,就如最现实的问题是,当一个游戏的同屏幕呈现100个以上的角色的时候,游戏是否因此而“卡”住,在早期的时候,我陷入了一个误区,呈现足够多的角色就是最大的性能体现(3.0呈现600个角色不卡),现在看来却是不然,因为单纯的角色呈现,有几百个不算什么,在一个整体游戏的执行时候,它是否还能保持足够的流畅才是最重要的,因为战斗逻辑、界面逻辑、场景管理器无时不刻在占用着系统资源,并且此时的角色也绝非几张图片那么简单,他们身上的装备、部件、特效都将成为游戏开发者的负担。
在此种情况下,优化循环过程相当重要,作为团队经验积累,今次拿出来大家一起研讨,有什么好的想法和建议欢迎一起交流一下:)下面的五种循环设计模式名字自己乱起的不好,还请见谅。
那么好,我们设计一个场景:有一个OBJ内部有一个LogicLoop的方法,内部实现了最简单的它会切换动画帧,并且向一个方向走,走到底会从头继续走。而场景中有非常多的OBJ。
一、自身式循环
自身式循环比较容易理解,比如一个精灵控件,自己内部实现一个循环,来不停的检测和执行逻辑,开发者都不需要去单独做什么,只需要new出来它们自己就会执行逻辑了,这种方式非常便捷和方便,开发起来也相对容易,互相之间没有任何关系,此时需要借助单例之类的设计模式来解决互相的结合问题。图示如下:
很显然,我们自身逻辑有一个最大的问题是独自的性能占用,如果一个场景(不是同屏)有几百个这样的循环时,那么游戏各个线程就会吃掉大量的CPU,尤其是用Thread、Storyboard、DispatcherTimer的时候。
二、链条式循环
自身逻辑存在各自的循环消耗问题,那么有没有办法将各自的循环逻辑统一到一个循环中呢,如果学过数据结构,我们可以透过链表的形式来做,基本的原理是将各个循环体放入到一个大循环中,然后从第一个开始执行循环逻辑,只执行一次,然后下一个,到底以后回来继续执行,模型如下:
这是一种常见的处理方法,能够大大降低系统消耗,而且C#提供了迭代器等好用的遍历,使得我们结合面向对象的思路更方便。示意代码如下:
public class obj
{
public virtual void OnLogic();
}
List<obj> ObjList = new List<obj>();
public void OnLoop()
{
foreach (var item in ObjList)
{
item.OnLogic();
}
}
链条式循环最大的优点是将所有的独立循环全部集中到一个大循环逻辑中,需要注意的有一个问题,那就是动态处理,因为游戏当中的物体生成和销毁是非常频繁的,正在循环的时候发生了集合改变,那么就危险了,我们的做法有两种,分别是数组转换和回收判断,数组转换非常容易,将集合拷贝到一个数组中,然后循环数组的各个元素;回收判断是通过标识将物体,在合适的时机摘出到一个回收列表中,然后在安全的时机清理。
链条式循环的优点可以创造一个游戏的RootHead,将所有的元素加入到这个RootHead当中,创建一个主循环然后遍历即可,当然了,你需要通过基类的方式来达到目的。
这是一种好的方式吗?在某种情况是的,它能解决性能损耗,当然了,要是内部实现的逻辑过于复杂,有的时候可能还得借助一下另外线程。在游戏产品中,有一个更加直接的需求,那就是级别层次问题,也就是说,有的循环体是系统级别,而有的可能只是一张图片,那么游戏的循环到底有多大才能包容一切,比如场景管理中的那么多场景物体,如果有逻辑循环就直接加入到这个大循环中吗?在游戏运行时,有一些循环在特定的时候是不需要使用的,或者不需要执行的,也为后续开发造成了障碍,所以,在我们的MMOROG引擎中,最多应用是下面的这种循环模式。
三、子树式循环
子树循环顾名思义,使用树状结构来处理循环逻辑,我们实际应用中还有可以分为:活动子树式循环和固定子树循环,为了方便讲解,主要讲固定子树循环的模式。
我们知道在一个游戏中,有很多的系统,比如场景系统、战斗系统、队伍系统、公会系统、聊天系统……N多系统,它们自己内部是否有一个循环呢?如果从直观角度上,上述系统可能不需要循环,但是事实不然,比如队伍系统,可能为了完成组队、移动等行为,专门有一个循环来处理判断逻辑,虽然这个逻辑很简单,再比如公会系统是否有每10秒钟刷新一下公会列表的需求……
如上图所示,我们利用子对象的方式创造一颗树,然后逐一进行遍历,在执行过程可以使用迭代,也可以使用递归,不管那种方式,对于子树而言没有太大的区别,但是对于性能而言,我们可以做一些有趣的优化,当一个系统关闭的时候,它在树中就不执行了——具体用什么方法,看情况而定,无论是拆枝还是逻辑判定都行。我们得到的效果是,关闭的子树下面的元素也不会执行循环,多么简单,比链条式的容易多了,一断全都断。
子树式循环在系统级别非常常用,对于那些比较频繁的更换的逻辑比较实用,比如特效动画、地图系统等等,具体的算法和操作在《数据结构》中有明确的答案,可以在其中找到想要的东西:)。
四、区间式循环
区间式循环严格意义上是循环中的一个判定方式,而不是实现模式,原理是将游戏系统各个部分拆分开,挂入不同的循环结构中,如果说链条式和子树式是一种Object集中,区间式可以说是一种Objects集合打散,释义图如下:
区间式在大系统级别,可以分拆最消耗性能的部分,到另外一个线程(或循环结构)中完成循环,比如说战斗系统、地图处理、场景管理器,而场景管理器下也可以带入一个区间式循环,将场景分割,然后对一个区域范围的物体进行处理(如果想想上面的图是否可以做成一个二维数组呢?),对于超出区域范围内的循环逻辑完全视而不见就行了,否则的话,要处理一片大场景中的N多个角色,无论是在自身、链条、子树都会一笔不小的开销。
区间式最大的优点是加强了范围判定,如果写的好,还可以多重结合,使用二维(三维也行)数组完成各个需要循环逻辑的分配,将不需要的拆分出去,这里的算法可能稍微有点意思,类似哈希和List的结合,要注意的是当一个物体(OBJ)从区间1到区间2的时候,会发生什么事情:)
五、组合式循环
其实组合式循环是一个非常偷懒的部分,因为组合的是前四种而已,在游戏开发中,并不是上述的那种方式最好,而是因地制宜,什么样的模式满足什么样的需求,不能只是单纯为了达到高效而高效,更加要注意未来开发的顺利程度,避免返工。
如上图所示,我们可以很清楚的分析不同循环方式在不同的环境下的应用:
自身式循环比较适合界面,因为比较固定,而且复杂逻辑不多,当然了这只是在Silverlight的UI当中比较适用,其实主逻辑就是一个很大的自身循环,Root的循环方式就是一个自身式循环。
链条式循环比较使用与第二级的游戏系统,将系统全部串起来,以达到快速遍历目的,但是在系统的下一级,就是子树式循环,系统元素全部在一个系统下,下面的子树中也可能会出现链条式,很显然是一种最频繁的组合方式。
区间式循环主要是应用在场景系统,可能需要一个链条循环或者子树循环带动,具体情况需要看游戏的设计模式,如果单个场景(比如地图)是使用单例的方式,那么使用链条式循环带动循环逻辑比较合适,如果单个场景是通过new出来的,那么使用子树方式来切换衔接更加容易明了。
以上是我们在做MMORPG时候的一些小小经验总结,上述中我们用的最多是组合式循环(废话,组合式全包了),但是对于一些小型的游戏,建议还是不要设计这么复杂,对于大型的网络游戏而言,程序设计这部分的重要性非比寻常,最后,看过很多这样或那样的说法,网页游戏对于性能是不行的,我想大部分的性能问题并不是技术本身,而是开发者没有将一个游戏本质思考清楚,什么时候我们用什么方法可以达到什么目的,能为各位开发者多能从中找到一点灵感,那为我自己的想法也有了一个交代。
心动了吗?还不赶紧动起来,打造属于自己的游戏世界!可能有些同学因为感到学习困难,一头雾水,都知求学不易,我不希望那些想学习的同学因为看不见对岸而放弃大好前程,为方便想学习的有志之士更容易的获取知识上的交流,进一步学习,特此建立了一个C/C++学习群,里面不仅有众多学友相互勉励学习,还有很多大家发的相关资料,还请持续关注更新,更多干货和资料请直接联系我,也可以加群710520381,邀请码:柳猫,欢迎大家共同讨论。