我们在游戏设计和开发中,尤其是引擎开发中,逻辑循环是一个重要组成部分,循环决定了游戏的基础逻辑和运行方式,在不同的开发环境和语言下,对于循环的释义甚至相差甚远,那么我想和大家分享的是在Silverlight游戏开发中,循环的设计方式和做法。
以下内容来自以往的游戏开发经验,可能在其他语言中的相关文章更加详细,谨在这里讨论有关在Silverlight游戏开发中的应用。
特别提示:如果你的《数据结构》学的不好,不要看,你会吐饭,如果你的《数据结构》学的很棒,不要看,你会吐血。
在传统的开发观念中,无论任何开发环境,它都逃不出While,代码一般是这样:
- while (true)
- {
- if (GameExit() == true)
- break;
- else
- GameLoop();
- }
那么在Silverlight游戏开发中,我们是否也可以这样应用呢?道理上差不多,但是只是实现了一个根(Root)部,这方面深蓝色右手以及很多其他朋友都有了各种各样的解决方案,有兴趣的朋友可以找他们的文章,无论是用线程(Thread)、故事板(Storyboard)、Rending、DispatcherTimer,都是一个好的循环体的开始。那么我们是否到底在游戏产品(或者叫成品更加贴切)的角度考虑更加深层的细节。
对于游戏而言,尤其是网络游戏,我们将面临着大量的交互,这些交互可能来自用户,也可能来自自身的游戏逻辑,其他的都好说,最现实的问题是,当一个游戏的同屏幕呈现100个以上的角色的时候,我们的循环是否因此而“卡”住,在早期的时候,我陷入了一个误区,呈现足够多的角色就是最大的性能体现(能呈现600个角色不卡),现在看来却是不然,因为单纯的角色呈现,有几百个不算什么,当在一个整体游戏的执行时候,它是否还能保持足够的流畅,因为我们的战斗逻辑、界面逻辑、场景管理器无时不刻在消耗着系统资源,而此时的角色也绝非几张图片那么简单,他们身上的装备、部件、特效都将成为游戏开发者的负担。
在此种情况下,优化循环过程相当重要,作为团队经验积累,今次拿出来大家一起研讨,看看是否这么回事,有什么好的想法和建议欢迎一起交流一下:)
针对于在一个游戏整体下的部分,用到循环最多应是动画,对此,我总结了在Silverlight游戏设计中能应用的五种循环方式,这些方式在传统的游戏开发中是非常常见的,只不过没有人愿意拿出来分享,两个原因:第一、太简单,讲出来怕笑话,第二、太神秘,我们要将其封装起来,这样才能忽悠人,下面的五种循环设计模式名字自己乱起的不好,还请见谅。
那么好,我们设计一个场景:有一个OBJ内部有一个LogicLoop的方法,内部实现了最简单的它会切换动画帧,并且向一个方向走,走到底会从头继续走。而场景中有非常多的OBJ。
一、自身式循环
自身式循环比较容易理解,比如一个精灵控件,自己内部实现一个循环,来不停的检测和执行逻辑,开发者都不需要去单独做什么,只需要new出来它们自己就会执行逻辑了,这种方式非常便捷和方便,开发起来也相对容易,互相之间没有任何关系,此时需要借助单例之类的设计模式来解决互相的结合问题。图示如下:
很显然,我们自身逻辑有一个最大的问题是独自的性能占用,如果一个场景(不是同屏)有几百个这样的循环时,那么游戏各个线程就会吃掉大量的CPU,尤其是用Thread、Storyboard、DispatcherTimer的时候。
二、链条式循环
自身逻辑存在各自的循环消耗问题,那么有没有办法将各自的循环逻辑统一到一个循环中呢,如果学过数据结构,我们可以透过链表的形式来做,基本的原理是将各个循环体放入到一个大循环中,然后从第一个开始执行循环逻辑,只执行一次,然后下一个,到底以后回来继续执行,模型如下:
这是一种常见的处理方法,能够大大降低系统消耗,而且C#提供了迭代器等好用的遍历,使得我们结合面向对象的思路更方便。示意代码如下:
- public class obj
- {
- public 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时候的一些小小经验总结,上述中我们用的最多是组合式循环(废话,组合式全包了),但是对于一些小型的游戏,建议还是不要设计这么复杂,对于大型的网络游戏而言,程序设计这部分的重要性非比寻常,最后,看过很多这样或那样的说法,网页游戏对于性能是不行的,我想大部分的性能问题并不是技术本身,而是开发者没有将一个游戏本身思考清楚,什么时候我们用什么方法可以达到什么目的,而很多的开发者期望也这个方便,那个也好,一味的追求万事俱备只欠东风的美好环境,殊不知,所谓的拿来思想只会使自己退步,期望各位开发者多能从中找到一点灵感,为自己的提升有个交代:)。
本文转自nowpaper 51CTO博客,原文链接:http://blog.51cto.com/nowpaper/712448