网络游戏开发的灵魂-数据结构与算法

时间:2022-06-01 20:01:33
一、游戏程序的灵魂——算法
  本系列文章的主题是网络游戏的程序开发,那么,程序是什么呢?一条著名的公式给了我们答案:
  程序=数据结构+算法
  程序语言(如C++)是一种工具,而算法是程序的灵魂。
  数据结构和算法在游戏程序中应用得很广,可以说无处不在。而且一般游戏对程序的效率要求很高,因此能否成为出色的游戏程序员很大程度取决于能否编写出高效的算法。以《魔兽世界》为例,在游戏中,猎人的宠物跟敌人一“碰”上,战斗便一触即发。在程序里是利用碰撞算法来判断是否有碰撞发生的。当然,像《魔兽世界》这类游戏不需要精确的碰撞检测,只要很简单的碰撞算法就能应付了,但像一些飞行模拟游戏就需要精确的碰撞检测了;现在换一个场景,你在如画的游戏世界里休闲地散步,很不幸,一只妖怪盯上了你,而且它足够聪明,它会选择一条最短的路径向你杀过去。为什么这只妖怪那么聪明?这就是A*(读A星)算法的魔力,A*算法是一种寻找最短的路径的寻路算法。这类例子在游戏中比比皆是,大家在玩游戏时可以多加留意。
  目前为止,被设计出来而又应用广泛的算法有很多,如贪婪算法、遗传算法和一些常用的排序算法等等。下面,向大家介绍游戏中用得最多的算法——排序算法。
  我们知道,在游戏场景里,在近处的游戏角色会遮挡住远处的角色。要做到这一点,一种方法是可以把所有的角色按它们的坐标值进行排序,然后以远到近把玩家“放”入到游戏场景里。常用的排序算法有四种:选择排序、冒泡排序、插入排序和快速排序。以下我们仅以最简单的冒泡排序法为例来加深大家对算法的理解。
  对一组有N个元素的数组,我们把它的每一个元素想象成一个泡泡。从最底部的元素开始,将各相邻的元素进行比较,若下面的元素大于上面的元素时,则把它们的值进行交换,即较大的元素将“往上冒泡”。如此类推,我们将要进行N-1次的“冒泡”。 template < class T >
void bubbli_sort( T a[], int n )
{
int i,j,last_pos;

i = n - 1;

while( i > 0 )

{
last_pos = 0;

for( j = 0; j < i; j++ )

{
if( a[j+1] < a[j] )
{
T temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
last_pos = j;
}
}
i = last_pos;}}

二、游戏数据的大管家——数据结构
  
数据结构是什么呢?这是一个很抽象的概念,没有统一的名词解释。你可以把它理解为数据在计算机中的组织形式。经典的数据结构,在网络游戏中都能得到体现。下面我们为大家举例并介绍。
  1. 道具包管理——线性表
  
在任何网络游戏中,你的道具包里也会放着许多珍贵的道具。在程序中需要把这些数据组织起来,方便管理。使用线性表可以简单方便地做到。线性表是一组元素以线性的结构组织起来,如( e1,e2,…en)。线性表一般分为数组与链表两类。
  数组里的元素以连续的内存空间存放,因此可以用内存地址检索到对应的数据元素,访问元素很方便。但如果要进行插入/删除数据元素,就要做一些内存移动了,效率比较低。而链表的数据元素存放在任意的物理内存位置,相邻的元素以指针作为“链扣”串连起来。如单向链表,元素 e1有一个指针指向它的后继节点 e2,这个指针就是它们之间的“链扣”。当进行插入/删除数据元素时,只要改变相应的“链扣”就可以,不需要做内存移动,效率相对于数组要高。

  2 .任务管理——队列与堆栈
  我在开始玩《梦幻西游》时,在城里走了几圈就接了满身的任务。这让我很烦恼,不知从哪个任务开始做起,这时我想到了队列。队列是一种“先进先出(first-in-first out, FIFO)”的数据结构。就好像是在银行里排队,排在前面的先服务。每次接到任务就把该任务压进任务队列里,要做任务时就从任务队列里取出一个任务,这样哪个任务先接到就先做哪个任务。
也许你不喜欢这样方式,你想做一个赏金猎人,哪个任务报酬多的就先做哪个任务。这样普通的队列就满足不了你的需求了,你需要的是优先级队列。优先级队列在插入元素时,优先高的元素插入队列前面。把任务的报酬设成是优先级数据,那么你每次在任务队列里取出任务时,就能保证这个任务是你现接的任务里报酬最高的。
  堆和栈在概念上不大一样,这里所说的堆栈是指栈(stack)。栈是一种“先进后出”的数据结构。就好像是叠盘子,叠在最上面的盘子最先拿来使用。队列和堆栈可能是使用频率最高的数据结构了。在匹配表达式应用中,堆栈发挥了巨大的作用。还有经典的汉诺塔问题,如果没有堆栈的帮助,你可不知道要搬碟子搬到什么时候了。这里我想举一个Word的例子,这个例子比较形象。谁都会有失误,在Word里发生了误操作一点也不需要惊慌,因为Word有“撤消”的功能,可以撤消你之前的操作。把用户的操作压入栈里,要撤消操作时就从栈弹出最近发生的操作。这样可以很方便地实现这个功能。

  3.
游戏菜单设计——树
  
树跟以上介绍的数据结构不一样,树是一种非线性的数据结构。树的实现与应用都要比线性数据结构复杂,C++标准库也没有实现树这种数据结构。但其实树的应用很广泛,很常用到,但是由于它的复杂性,很多程序员都回避使用。如果能有效地使用树,你的程序效率将会得到很大的提高。如图1是一棵简单的树的逻辑结构。
网络游戏开发的灵魂-数据结构与算法
  树跟线性数据结构不同,它的每一个节点可以有0个或多个后继。在图1这棵树中,节点A是整棵树的根,它没有前驱节点。B、C、D是A的孩子,B、C、D这几个节点是兄弟。
  树的一个比较形象的应用是游戏中的UI(User Interface用户界面)。在UI里的菜单一般是分级的,如在主UI里可能会有“进入游戏”、“选项”、“退出游戏”这几项菜单,在“进入游戏”里又会有“创建新角色”和“使用旧角色”两个菜单项,而进入“选项”后会有“音频”、“视频”、“游戏设置”等等。使用树来管理这些菜单是很合适的做法。
  树在应用时分很多种类型,我们在这里只介绍二叉树。二叉树对后继节点进行了一项约束——每个二叉树的节点最多只能有二个后继节点。红黑树是一种特殊的二叉树,C++标准库的set和map就是用红黑树实现的,因此它们的排序查找效率很高。
  由于树是一种非线性的数据结构,因此它对元素的遍历并不像线性数据结构那么简单。二叉树一般常用的有三种遍历算法:前继遍历、中继遍历和后继遍历。前继遍历先访问父节点,然后是左节点,右节点;中继遍历先访问左节点,再到父节点,最后是右节点;后继遍历先访问左节点,再到右节点,最后是父节点。
  4 .地图数据管理——图   
  图是一种复杂的数据结构,相对于树要复杂一些。跟树一样,图是一种非线性的数据结构,C++标准库同样没有实现图。图就像一个网一样,各个节点用边线连接起来,每条边线有一个权值,可以理解成两个节点间的距离。图2是一个图的数据结构。研究图对游戏开发很有意义,游戏地图是图的应用的一个很好的例子。游戏中的地图一般会被分成一块块的格子,每一块都会有一个坐标值。那么可以使用二维数组把地图的数据记录起来,这是最简单的方法。但大家都知道,鱼与熊掌是不能兼得的,这种方法只能用在很小型的地图,而较专业的做法是使用图记录地图数据,图的每一个节点对应的是地图的一个坐标。
网络游戏开发的灵魂-数据结构与算法
  图的标准搜索算法有两种:广度优先搜索与深度优先搜索。广度优先搜索从开始节点的周围节点一层一层地往外搜索;深度优先搜索以开始节点为始点对一条路径搜索到尽头后再搜索另一条路径,直到所有的路径都搜索完为止。
  图论是一门很大的课题,有兴趣的读者可以找相关的专业书籍进行深入研究。

三、正确的学习途径
  学习数据结构与算法是很枯燥乏味的,但不要想找什么捷径,只有理解后反复上机练习、练习再练习!使用过STL(C++标准模板库)的读者可能会问:既然已经有人帮我们实现了各种数据结构与算法,为什么还要自己去学习呢?我们要学习的是蕴涵在数据结构与算法中的思想。我们要理解的是“为什么要这样做”,而不只是停留在“怎样做”,只有理解了这些思想,才能写出更加合理、高效的程序。
  学习数据结构从最简单的出发,你需要花一个星期的时间在数组线性表,它的难点主要是在插入数据和删除数据;链表比数组要难一点,但单向链表的话也是只要一个星期的时间学习,然后再花一个星期学习双向链表向强化练习;队列和堆栈是很常用的数据结构,你要花两个星期的时间好好理解一下。然后就是树了,从二叉树开始学起,大概要两个星期的时间;最后是图,也是最难的,但有了之前的知识作基础,相信你也只要两个星期的时间去学习。数据结构学习得差不多了,加以应用吧,最后花一个月的时间好好研究一下各种算法。