淘客熙熙

主题:初到贵宝地,灌一点游戏开发的东东 -- foundera

共:💬23 🌺4 新:
全看树展主题 · 分页 上页
家园 浅谈RSLG类游戏的人工智能

浅谈RSLG类游戏的人工智能

  今天我们来看看关于RSLG人工智能的设计方式。

  网络上的网友问到,到底在设计人工智能的时候,是如何来决定出每一行判断式的重要性。其实笔者可以很轻松的告诉各位,在设计人工智能的时候,先想想自己是如何去思考的,然后就依自己认为的重要程度来划分每一行判断式的重要性。这种设计方法的优点就是会相当的“人性化”,设计者很容易就可以在游戏中看到电脑以自己的思考方式去进行各项行动,但是相对的缺点就是若是设计者的本事很差,那么人工智能所表现出来的一定也会很差,因为这样子的人工智能完全会反映出设计者的思路。由于这种设计方式所表现出来的是设计者的思路,因此在一些国外的西洋棋游戏中,设计公司经常会和某些高段的棋手配合,让他们来提出各项的建议,使整个游戏的人工智能更加精确,这就是为什么他们会这样做的原因。

  但是现在问题又来了,西洋棋游戏可以这样作,战略游戏怎么办呢?有那一个人是真正的战略天才呢?因此笔者在这里提出一种比较法让各位能够了解设计时会碰到的问题。我们就以战略游戏为例好了,也就是目前市面上最流行的所谓RSLG。在游戏中,当一名电脑控制的角色要行动的时候,它需要考虑以下的几件事情:

行动范围内有没有可以攻击到的敌人。

移动到地理位置较好的地方。

生命是不是已经低到会死的地步。

本身是不是拥有什麽特殊的能力可以使用。

  以上是笔者粗略的将可能会发生的情况归类成四项,现在我们就一项一项的来看看。首先是第一项的行动后可以攻击到敌人,光是这一个情况我们就必需要做比较仔细的推算,像是不是只有一名可以攻击到的敌人?若是不只有一名可以攻击到的敌人时,要怎么挑选攻击的目标?以及这样的攻击是不是可以将敌人击毙?是不是要以可以在这一次就击毙的敌人为目标?以及在这一次攻击之后是不是会在下一个回合就遭到敌人的回击等等。如果先将这些情况加以判断,那么可能会归类出以下的几条判断式:

  A.若是只有一名可以攻击到的敌人,那么目标就是它。

  B.若是有数名可以攻击到的敌人,那么选择最弱的一名。

  C.若是有可以在攻击后击毙的敌人,那么它会是目标。

  D.在攻击后在多少名敌人的攻击范围内。

  还记得上篇文章中笔者曾经提过条列式以及积点式两种人工智慧的设计方式吗?现在我们就以这两种方法来看看关于这一部份的判断式重要性。以笔者主攻的眼光来看,若是有一名敌人可以被击毙,那么这名敌人一定是最重要的目标,接下来才是攻击最弱的敌人,接下来是可以攻击到的敌人,最后才去判断会进入多少敌人的攻击范围内。因此这四条判断式若是以条列式的判断法来排序的话,将会是以下的情况:

C > B > A > D

  在这样子的设计下,这些由人工智能所控制的角色将会展现出相当强悍的主攻个性,就算是这一次的攻击后可能会遭到敌方的围攻也毫不在乎。若是各位读者觉得自己并不喜欢由人工智能控制的角色会这样子行动,希望它们能够适当的避开一些会遭到敌人围攻的情况,那么判断式的排列顺序可能会变成:

C > B > D > A

  在这样的情况下由人工控制的角色不会一看到有可以攻击到的敌人时,就会像疯狗似的追着打,而会考虑一下这样子的行动会不会就遭到敌人的围攻。但是当有敌人会被击毙,或是生命相当低的时候,就会不考虑那么多了。如果各位读者觉得这样子还是不好,那么也可以将判断式的排列顺序改成如下:

D > C > B > A

  在这样的情况下,由人工智能控制的角色将会相当的小心,就算是可以将敌人击毙,但是若在下一回合有被其余的敌人围攻的可能,就不会发动攻击。

  接下来我们看看以积点式来设计的话,会是什么样子。同样的判断式用积点式来设计的话,就必需要给每一行算式不同的积点,但是同时必需要将算式做一点修正,因为在积点式中会有复数计算的情况发生,因此这些判断式会变成:

  A.可以攻击到一名敌人的位置。

  B.可以攻击到的敌人中最弱一名的位置。

  C.攻击时可以击毙敌人的位置。

  D.一名敌人的攻击范围内。

  各位读者可以看到,其中的第四条就是可能会复数计算的。在程序进行判断的时候,这一条可能会因为有多个敌人角色都合乎这个条件,而导致积点的重复计算,因此若是重复计算的次数够多了之后,反而可以将原本一些有利的情况抵消。以前面的这四条算式来举例,若是A的积点是+2、B的积点是+4、C的积点是+8、D的积点是1,那么当一个地点虽然可以将一名敌人击毙但是又会被五名敌人围攻的话,那么这个地点的重要性就会低于一个生命力较低的敌人的位置。也由于积点式的计算方式比较精密,因此人工智能所控制的角色就不会那么的死板,而会显得比较聪明。虽然条列式的作法可以用增加判断式的办法来达到相同的目的,但是比较起来就是差了些。

  由以上的这个例子中,我们可以看到不论是一种人工智能的设计方式,整个人工智能的表现都是控制在设计者的手上。条列式表现在判断式的排列顺序,而积点式表现在积点数的不同,这些都是人工智能有“人性”的地方。

--------------------------------------------------------------------------------

  额外的讨论

  网络上有网友问到,像笔者在上篇文章中拿宾果作说明,但是并没有很详细的以实例说明,所以他实在是不明白为什么在那篇文章中会说(No A) = 4 x 2 > (No B) = 7 x 1。因此现在笔者就将那一篇文章的范例拿出来详细的解说一下。

  首先各位看看以下的这个积点表,在这个表中列出了各种情况的重要性。至于为什么积点设定为这样,也就是要让积点真正的能够发挥作用:

判断式 绩点

划一个号码後能够完成一条线 31

划一个号码後在这一条线中出现四个点 15

划一个号码後在这一条线中出现三个点 7

划一个号码後在这一条线中出现两个点 4

划一个号码後在这一条线上只有一个点 1

  现在我们看看下面的这个宾果盘,上面的那些「 」符号表示这个地点的数字已经被划掉了。

宾果盘

6 11 16 21

12 17 22

3 8 13 18 23

4 9 14 19 24

10 15 25

  现在各位看看这个宾果盘,有两条已经有三个点的线以及两条有两个点的线,因此现在应该要划那一点比较好呢?我们就用上面的那积分表来计算每一个点的重要性,可以得到以下的数值:

  o 03:9+1=10

  o 04:9+1=10

  o 06:3x2=6

  o 08:3+1=4

  o 09:3+1x2=5

  o 10:5+3=8

  o 11:3+1=4

  o 12:5+1=6

  o 13:5+3+1x2=10

  o 14:3x2=6

  o 15:5+1=6

  o 16:3x2=6

  o 17:5+3x2=11

  o 18:3+1=4

  o 19:3+1=4

  o 21:3x2+1=7

  o 22:5+1=6

  o 23:3x2=6

  o 24:3x2=6

  o 25:5x2+1=11

  从这些算式中,我们可以看到若是划下17或是25将可以赚得最多的积点,因此这两个数字就是我们现在要考虑的。接下来再以这两个数字在宾果盘上的重要地位来判断,25的位置要比17要好,因此就可以决定划下25这个数字。这种计算方式就是积点式的典型算法,人工智能会将整的盘上的所有数字都做一番推算,然后才选出最好的一点。

  网友还问到,在上一次的文章中笔者曾经说过积点式的人工智能在进行判断的时候为什么会花掉比较多的时间。以下来说明:

  各位读者可以想想看,若是每一格的计算需要花掉一分钟的话,那么在积点式的判断中,每一次不论如何的行动,都需要花掉相同的时间。而条列式的判断方式会因为当时判断的位置不同而略有不同。或许各位读者会说,不过是几个步骤而已嘛,时间上能够差多少?但是各位不要忘了,在这篇文章中笔者只不过是举例,所以看起来判断式好像不多,但是在真正游戏中的人工智能却不只是这么短短几行的判断。试着想想看,若是每一行判断式是十秒钟,那么当判断式一多了之后会是怎么样呢?

  这一次再向各位读者解释人工智能的运算方式,希望各位读者都能够看得懂。

家园 飞行射击游戏中的碰撞检测

飞行射击游戏中的碰撞检测

在游戏中物体的碰撞是经常发生的,怎样检测物体的碰撞是一个很关键的技术问题。在RPG游戏中,一般都将场景分为许多矩形的单元,碰撞的问题被大大的简化了,只要判断精灵所在的单元是不是有其它的东西就可以了。而在飞行射击游戏(包括象荒野大镖客这样的射击游戏)中,碰撞却是最关键的技术,如果不能很好的解决,会影响玩游戏者的兴趣。因为飞行射击游戏说白了就是碰撞的游戏――躲避敌人的子弹或飞机,同时用自己的子弹去碰撞敌人。

  碰撞,这很简单嘛,只要两个物体的中心点距离小于它们的半径之和就可以了。确实,而且我也看到很多人是这样做的,但是,这只适合圆形的物体――圆形的半径处处相等。如果我们要碰撞的物体是两艘威力巨大的太空飞船,它是三角形或矩形或其他的什么形状,就会出现让人尴尬的情景:两艘飞船眼看就要擦肩而过,却出人意料的发生了爆炸;或者敌人的子弹穿透了你的飞船的右弦,你却安然无恙,这不是我们希望发生的。于是,我们需要一种精确的检测方法。

  那么,怎样才能达到我们的要求呢?其实我们的前辈们已经总结了许多这方面的经验,如上所述的半径检测法,三维中的标准平台方程法,边界框法等等。大多数游戏程序员都喜欢用边界框法,这也是我采用的方法。边界框是在编程中加进去的不可见的边界。边界框法,顾名思义,就是用边界框来检测物体是否发生了碰撞,如果两个物体的边界框相互干扰,则发生了碰撞。用什么样的边界框要视不同情况而定,用最近似的几何形状。当然,你可以用物体的准确几何形状作边界框,但出于效率的考虑,我不赞成这样做,因为游戏中的物体一般都很复杂,用复杂的边界框将增加大量的计算,尤其是浮点计算,而这正是我们想尽量避免的。但边界框也不能与准确几何形状有太大的出入,否则就象用半径法一样出现奇怪的现象。

  在飞行射击游戏中,我们的飞机大多都是三角形的,我们可以用三角形作近似的边界框。现在我们假设飞机是一个正三角形(或等要三角形,我想如果谁把飞机设计成左右不对称的怪物,那他的审美观一定有问题),我的飞机是正着的、向上飞的三角形,敌人的飞机是倒着的、向下飞的三角形,且飞机不会旋转(大部分游戏中都是这样的)。我们可以这样定义飞机:中心点O(Xo,Yo),三个顶点P0(X0,Y0)、P1(X1,Y1)、P2(X2,Y2)。中心点为正三角形的中心点,即中心点到三个顶点的距离相等。接下来的问题是怎样确定两个三角形互相干扰了呢?嗯,现在我们接触到问题的实质了。如果你学过平面解析几何,我相信你可以想出许多方法解决这个问题。判断一个三角形的各个顶点是否在另一个三角形里面,看起来是个不错的方法,你可以这样做,但我却发现一个小问题:一个三角形的顶点没有在另一个三角形的里面,却可能发生了碰撞,因为另一个三角形的顶点在这个三角形的里面,所以要判断两次,这很麻烦。有没有一次判断就可以的方法?我们把三角形放到极坐标平面中,中心点为原点,水平线即X轴为零度角。我们发现三角形成了这个样子:在每个角度我们都可以找到一个距离,用以描述三角形的边。既然我们找到了边到中心点的距离,那就可以用这个距离来检测碰撞。如图一,两个三角形中心点坐标分别为(Xo,Yo)和(Xo1,Yo1),由这两个点的坐标求出两点的距离及两点连线和X轴的夹角θ,再由θ求出中心点连线与三角形边的交点到中心点的距离,用这个距离与两中心点距离比较,从而判断两三角形是否碰撞。因为三角形左右对称,所以θ取-90~90度区间就可以了。哈,现在问题有趣多了,-90~90度区间正是正切函数的定义域,求出θ之后再找对应的边到中心点的距离就容易多了,利用几何知识,如图二,将三角形的边分为三部分,即图2中红绿蓝三部分,根据θ在那一部分而分别对待。用正弦定理求出边到中心点的距离,即图2中浅绿色线段的长度。但是,如果飞机每次移动都这样判断一次,效率仍然很低。我们可以结合半径法来解决,先用半径法判断是否可能发生碰撞,如果可能发生碰撞,再用上面的方法精确判断是不是真的发生了碰撞,这样基本就可以了。如果飞机旋转了怎么办呢,例如,如图三所示飞机旋转了一个角度α,仔细观察图三会发现,用(θ-α)就可以求出边到中心点的距离,这时你要注意边界情况,即(θ-α)可能大于90度或小于-90度。??罗嗦嗦说了这么多,不知道大家明白了没有。我编写了一个简单的例程,用于说明我的意图。在例子中假设所有飞机的大小都一样,并且没有旋转。

/////////////////////////////////////////////////////////////////////

//example.cpp

//碰撞检测演示

//作者 李韬

/////////////////////////////////////////////////////////////////////

//限于篇幅,这里只给出了碰撞检测的函数

//define/////////////////////////////////////////////////////////////

#define NUM_VERTICES 3

#define ang_30 -0.5236

#define ang60 1.0472

#define ang120 2.0944

//deftype////////////////////////////////////////////////////////////

struct object

{

float xo, yo;

float radio;

float x_vel, y_vel;

float vertices[NUM_VERTICES][2];

}

//faction/////////////////////////////////////////////////////////////

//根据角度求距离

float AngToDis(struct object obj, float angle)

{

float dis, R;

R = obj.radius;

if (angle <= ang_30)

dis = R / (2 * sin(-angle));

else if (angle >= 0)

dis = R / (2 * sin(angle + ang60));

else dis = R / (2 * sin(ang120 - angle));

return dis;

}

//碰撞检测

int CheckHit(struct object obj1, struct object obj2)

{

float deltaX, deltaY, angle, distance, bumpdis;

deltaX = abs(obj1.xo - obj2.xo);

deltaY = obj1.yo - obj2.yo;

distance = sqrt(deltaX * deltaX + deltaY * deltaY);

if (distance <= obj.radio)

{

angle = atan2(deltaY, deltaX);

bumpdis1 = AngToDis(obj1, angle);

return (distance <= 2 * bumpdis);

}

ruturn 0;

}

//End//////////////////////////////////////////////////////////////

  上面程序只是用于演示,并不适合放在游戏中,但你应该明白它的意思,以便写出适合你自己的碰撞检测。游戏中的情况是多种多样的,没有哪种方法能适应所有情况,你一定能根据自己的情况找到最适合自己的方法。

 

家园 游戏鼠标操作的思考

游戏鼠标操作的思考 出 处:GameRes.com

  常规的游戏都是在进行屏幕更新的时候再进行鼠标绘制,一旦FPS降低,鼠标的控制将非常的困难,这点相信大家都遇到过,这次想和大家讨论的就是如何让鼠标操作更贴切,更顺畅。问题如何解决?相信我们每天在使用Windows,Windows在磁盘操作,或者CPU繁忙的时候都能保持鼠标的操作顺畅,我们的游戏完全可以学习Windows的做法来让我们的游戏鼠标操作更为方便。

  我们先来说说Windows实现鼠标的做法,按照我的观察,Windows的鼠标应该是通过多线程来实现,而且鼠标线程的优先级非常之高,并且鼠标在屏幕上是使用局部更新,知道了原理,我们就可以开始动手实现我们的游戏鼠标了。首先,我们必须创建出我们可爱的鼠标线程,让我们的鼠标拥有较高的优先级,这样才能最大限度让鼠标灵活,参考如下代码:

  // 我们的鼠标线程

  DWORD WINAPI ThreadProc( LPVOID lpParameter )

  {

    ...  // 实现鼠标更新的部分代码

  }

  // 创建我们鼠标线程,详细请参考多线程编程文章

  thread = CreateThread(NULL, 0, ThreadProc, 0, 0, &g_dwMouseThread);

  // 提高线程的优先级

  SetThreadPriority(thread, THREAD_PRIORITY_TIME_CRITICAL);

  这样,我们的线程就创建完毕了,接下来我们来看看如何进行屏幕的局部更新,我们的游戏(2D)一般都是运行在DirectDraw的环境下,要进行屏幕的局部更新我们需要对主表面进行直接操作,可能你会想到,万一我们在进行鼠标更新的同时,游戏主线程也在对主表面进行如页面翻转等操作,一但这样,这将产生无法预料的结果,为了避免这种情况的产生,我们还必须进行双线程的同步处理,说了那么多废话,来看看我们如何实现:

  // 用来标记鼠标进程是否进行

  bool g_bMouseThreadRun = true;

  // 用来处理同步,此类属于MFC线程处理部分,具体请参考MSDN

  CCriticalSection critsection;

  // 鼠标处理实现线程部分

  DWORD WINAPI ThreadProc( LPVOID lpParameter )

  {

    // 进行鼠标线程的内部循环处理

    while(g_bMouseThreadRun)

    {

      // 为了能进一步节省鼠标线程所消耗的系统资源

      // 我们使用了DirectInput的鼠标事件响应操作

      // 具体实现参看DirectInput的鼠标处理部分

      DWORD dwResult = WaitForSingleObject(g_hMouseEvent, INFINITE);

      if(dwResult == WAIT_OBJECT_0) // 有鼠标事件发生

      {

        // 重新获得鼠标的位置信息

        // 此处非常重要,用来作为与屏幕刷新的同步处理

        // 这里将线程锁定,使主线程无法进行屏幕刷新操作

        critsection.Lock();

        if(检测鼠标位置是否有更新)

        {

          // 恢复原鼠标位置的图象 bakbuffer <- save back image

          // 保存鼠标将绘制部分的背景图象 save back image -> bakbuffer

          // 在新位置绘制鼠标 draw mouse image

        }

        // 释放线程

        critsection.Unlock();

      }

    }

  }

  完成了我们的鼠标线程,接下来我们来设计我们的屏幕刷新部分,此处要注意两个问题,一个是与鼠标进程一样的同步问题,还有就是实现对局部更新时的背景图象保存缓冲的更新,一但屏幕刷新,背景难免会产生变化,那此时我们在鼠标线程中所保存的局部图象数据(bakbuffer)将是无效错误过时的数据,所以,我们必须对局部图象保存数据进行与背表面(BackSurface)进行匹配处理,并且将鼠标图象绘制到屏幕上,参看如下:

  // 屏幕刷新函数

  HRESULT Present()

  {

    // 同上,为了满足同步需要

    // 如果有其它线程调用了Lock(),那此处将处于等待状态

    // 直到其它线程Unlock(),此函数才将返回。

    critsection.Lock();

    // 对鼠标线程所保存的局部图象数据(bakbuffer)

    // 进行与背景的匹配操作 save BackSurface image -> bakbuffer

    // 绘制鼠标到屏幕上,防止鼠标被背景覆盖

    ... // 屏幕刷新部分

    // 恢复线程锁定

    critsection.Unlock();

  }

  关于线程的释放,这里我们只需要简单的将g_bMouseThreadRun这个全局变量设置为false,这样线程就能自动退出,不过注意,为了防止鼠标线程还未退出,主线程已经将部分关键数据释放造成错误,最好能让主线程停止一会,以便鼠标线程的正确退出。

  最后,我们来谈谈这种方法的利弊,优点很明显,可以让鼠标操作更为顺畅,缺点,使编程复杂化,而且多少会影响些主线程的性能。说了那么多,有兴趣的朋友可以去下载我的HoHo游戏引擎,里面有全部原代码,还有附带的实现例子。

家园 游戏Loading画面的实现

游戏Loading画面的实现

当游戏在必须调入大量的资源或者复杂的初始化时,这就需要用到Loading画面和用户耐心的等待...

实现思路如下,当开始等待时,起动一个线程接管画面的绘制,同时用一个全局变量控制绘制的百分比,至于绘出来的是一个弹匣在装子弹(命运战士的load画面)还是一个被点燃导火索的炸弹(盟军敢死队III)就完全由你决定了,设全局变量 g_iPrecent的最大值为100,如果你够有耐心的话,你可以load完一幅图片替他++一次,这就完全靠你的感觉,或者你可以先TimeGetTime在调试框内输出Load这些资源需要的时间...然后分配这个分成了100份的时间段...

下面演示一个很简单的颜色块进度条

UINT ThreadProc(LPVOID param )

{

SHAREDATA *pData=(SHAREDATA*)param;//从参数里选择数据指针

while(g_bShowLoadPrecent)//此变量判断是否load完毕

{

(*(pData->pDisplay))->DrawRect(CRect(262,370,762,398));//方块

(*(pData->pDisplay))->FillColorRect(CRect(262,370,pData->iLoadPrecent*5+100,398),RGB(0,255,0));//显示load的百分比

(*(pData->pDisplay))->PrintText(480,375,"LOADING???",RGB(255,0,0));

(*(pData->pDisplay))->Present();

Sleep(10);

}

return 0;

}

家园 国外专家谈游戏制作 

国外专家谈游戏制作 瞬间工作室 李波

  目 录

  1 戏剧手法在游戏中的应用

  2 电影语言在游戏中的应用

  3 游戏剧本设计

  4 多人在线游戏

--------------------------------------------------------------------------------

  1998年5月14日,Intel公司在香格里拉饭店组织的发展商会议,由国外一些专家们进行技术讲座。其中交互式软件剧本设计课程,对计算机游戏软件设计的艺术和技术进行讲述。

  此次课程的主讲是美国South Peak Interactive公司的Lee Sheldon先生。他从事影视剧本创作20余年,后转为从事游戏设计工作,可以说是经验丰富,对于电影以及戏剧的表现手法在游戏中的应用到了得心应手的地步。

  在讲座中,他主要谈到了以下几个方面:

戏剧语言在电子游戏中的应用

电影语言在游戏中的应用

游戏剧本设计

简单说明多用户上网游戏

另外,我还问了Sheldon先生一些问题,具体附录在后。

  注:下文的记述是根据Sheldon先生的原意加上我的理解和发挥写成的。

1、戏剧手法在游戏中的应用

  1.1 开始设计游戏时如何确定游戏的主题

  设计一款游戏,应该注意到具有一般、共同主题的游戏剧本适用于不同的文化背景的游戏者们。比如爱情主题、战争主题等等。容易引起游戏者们的共识与共鸣,对于游戏在不同地区的推广是有好处的。

  如果游戏题材比较老旧的话,就应该试图从一个全新的角度来阐述一个古老的故事;或以全新的观念来诠释古老的题材;或以全新的体裁制作古老的题材。做到旧瓶装新酒或者是新瓶装旧酒,不能给游戏者乏味或雷同的感觉。要让游戏者在不同的方面领略到新意。

  1.2 如何推动游戏的过程向前发展

  在戏剧中,有两个重要因素是推动故事情节的动力:障碍与冲突。

  具体应用到游戏中,可以将障碍变成为在游戏过程中,需要游戏者解决的难题;冲突变成为游戏者前进的阻碍,迫使游戏者根据自己目前的状况,想出有效的解决办法。再具体的说明就是障碍是谜题,冲突是战斗。在RPG游戏中,这两种因素应用最为广泛。

  恰当的为游戏者设置障碍和冲突,是游戏者有不断克服困难前进的动力,从而带动故事情节向前发展。

  1.3 故事的讲述方式

  故事的讲述方式有两种:倒叙法和正叙法。

  倒叙法是先将游戏者所处的环境给定,先使游戏者处于事件发生后的结果之中,然后再让游戏者回到过去,去自己发现事件到底是怎样发生的,或者阻止事件的发生。最典型的例子是AVG游戏《MYST》。

  正叙法就是普通的方式,故事随着游戏者的遭遇而展开,游戏者对一切都是未知的,一切都等待游戏者自己去发现,去创造。一般的游戏都采用这种方式。

  1.4 如何设定游戏的主人公

  主人公是游戏的灵魂,只有出色的主人公才能使人流连于故事世界中,才能演绎出出色的故事。因此,成功的设定出一名主人公,游戏就有了成功的把握。

  游戏中的主人公不一定非要是一名善良、优秀的人不可,也可以是邪恶的,或者是介乎与正邪之间的。

  通常邪恶的主人公比善良的主人公更容易使游戏成功。主人公如果能够邪恶得让人虽然厌恶他,但却不能放弃他,让人想看看他到底能够做出什么、或能够遭遇到什么、或下场是什么,就比善良的主人公更好的抓住了游戏者的心。比如电影《沉默的羔羊》中的那名博士,或者是游戏《玛尔寇的复仇》中的玛尔寇。

  还要注意一点的是,主人公的设计不要脸谱化、原形化,不要流俗。主人公如果没有自己的独特个性、独特形象,是不可能使游戏者感兴趣的。

  1.5 游戏中的叙述角度

  一般游戏中,最常用的是两种叙述角度,也可以称之为视角--即第一人称视角和第三人称视角。

  第一人称视角是以游戏主人公的亲身经历为叙述角度,屏幕上不出现主人公的形象,是游戏者有“我就是主人公”的感觉,从而更容易使游戏者投入到游戏中。

  第三人称视角是以旁观者的角度观看游戏的发展,虽然说是“旁观者清”,但在游戏者的投入感上,不如第一人称视角的游戏。

  第一人称视角的游戏比第三人称视角的游戏编写难度大。欧美国家的RPG一般全部是采用的第一人称视角来进行游戏设计的。比如著名的《魔法门》系列。

  其实我还是比较偏好第三人称视角的游戏,在第三人称视角的游戏中也可以利用不同的办法来加强游戏者的投入感,比如主人公的名字自己输入、自己挑选脸谱等。从游戏的表现效果来看,第一人称游戏也有它的局限性。

  1.6 游戏中的情感与悬念

  游戏中的情感因素非常重要,只有人的本性才可以触动人,使游戏者沉醉于这个游戏。作为游戏设计者,首先应该保证自己的设计能够感动自己,才可以说是成功的开始。一部作品如果连作者自己都没有感觉,怎么能够想象它能够打动其他的游戏者呢?

  游戏中另外的一个重要因素是悬念。悬念--是游戏中带有紧张和不确定性的因素,不要让游戏者轻易猜出下一步将要发生些什么。加入适当的悬念可以使游戏更吸引人。比如:在一个箱子中放有游戏者所需要的道具,但箱子上加有机关,在开启的同时会爆炸。游戏者不知道箱子中放置的物品是什么,但通过提示,他知道这件物品会对他有帮助。可是他也知道打开箱子会有危险--同样,他也不知道危险是什么。如何即打开箱子有没有危险就成为了他所要解决的问题。这样就在制造悬念的同时,也给游戏者制造了一个难题。

  游戏者在游戏中由于并不知道游戏内核的运行机制,因此对于自己的动作结果有一种忐忑不安的期待。在所有的游戏中,游戏者总是通过经验实现对不可预测性的抗争。

  从不可预测性上看,游戏可以分为两种类型:一种称为技能游戏,另一种称为机会游戏。前一种游戏的内部运行机制是确定的,不可预测性的产生的原因是游戏设计者故意隐藏了运行机制,游戏者可以最终通过对游戏运行机制的理解和控制(即某种技能)解除这种不可预测性。而后一种游戏中游戏本身的运行机制具有模糊性,具有随机因素,不能通过完全对游戏机制的了解消除不可预测性,游戏动作产生的结果是随机的。

  悬念以及由悬念所引起的期待在游戏中至观重要。在游戏中,不能使游戏者的期待完全落空,这样将使游戏者产生极大的挫折感;也不能使游戏者的期待完全应验,这将使游戏失去不可预测性。应该时而使游戏者的期待变成精确的结果,使其增强信心,获得欢乐;时而抑制游戏者的期待,使其产生疑惑,疑惑的时间越长,悬念的情绪就越强烈,建立起来的悬念紧张度越大,由解决引起的情感上的解脱感就越强。

  悬念产生的价值不在其本身,而在于随之而来的解脱。悬念及其解除过程实际上与焦虑、释放过程是相对应的。

  1.7 游戏的节奏

  首先应该明确指出的是游戏中的时间观念与现实中的时间观念有所区别。游戏中的时间由定时器控制。定时器分两种:真实时间(实时)的定时器和基于事件的定时器。

  实时的定时器就是类似C&C和DOOM的时间方式。

  基于事件的定时器是指回合制游戏和一般RPG和AVG中的定时方式。

  也有的游戏中轮流采用两种定时方式,或者同时采用两种定时方式。比如《红色警报》中一些任务关的设计。

  在即时类游戏中,游戏的节奏直接由时间来控制,但在其他游戏中,真实时间的作用就不是很明显,这就需要用其他的办法。

  在游戏中,尽量让游戏者控制游戏的节奏,而尽量少由设计者来做。设计者控制游戏节奏的方式应该是让游戏者难以察觉的。

  比如:在AVG游戏中,调整游戏者活动空间(即ROOM)的大小;调整活动范围的大小(即世界);调整谜题的难度;调整游戏中工具的种类,都可以起到改变游戏节奏的作用。

  在ACT游戏中,可以采用调整敌人的数量;调整敌人的生命值等办法。

  在RPG游戏中,除了可以采用与AVG游戏中类似的手法以外,还能调整事件的发生频率;调整游戏中敌人的强度等办法。

  一般来讲,游戏的节奏应该是越来越快,越接近游戏的结尾部分,就越是游戏者感到自己正逐渐加快步伐接近游戏的真正尾声。就好象侦破一件案件一样,开始千头万绪,随着逐渐的深入调查,逐渐的排除,越到后来,案情就越明朗化。

  另外,决不要使游戏显得冗长。过于罗嗦的进行一个事件的描述会使游戏者失去继续进行游戏的兴趣,要不断的给游戏者以新的挑战和刺激。

  1.8 游戏的风格要一致

  在一款游戏中,从头到尾保持一致的风格是很重要的。风格一致包括人物与背景的一致,游戏风格定位的一致等等。在一般的游戏中,如果不是游戏剧情的特殊需要,不要使人物说出超过当时历史时期的语言,要注意时代特征。

2、电影语言在游戏中的应用

  2.1 铁的法则 ― 摄影机不能跨越轴线

  见下图示例:

图1 摄影机不能跨越轴线

  当摄影机拍摄两个物体时--比如说是两个面对面对话的人,物体之间的连线称之为轴线。当在摄影机在机位1先拍摄物体2后,下一个镜头应该在机位2的位置拍摄物体1,使物体在屏幕上的方向是相对的,这样即便在镜头剪辑以后再播放,也不会造成方向上的混乱。严禁在机位1先拍摄物体2后,到机位2a拍摄物体1,这样就使人物在屏幕上“一顺儿”了,这是拍摄时的大忌。换句话说,拍摄时严禁跨越轴线。

  如果要跨越轴线,也不是不可以,那就一定要让观众能够看见摄影机的移动过程,不要将绕行的过程剪辑掉。这些手法一般在游戏的过场动画中会有所应用。

  2.2 电影中的对话

  对话在电影中占据了非常重要的位置。一定要保证各人有各人说话的风格,使每个人的性格和特点在对话中表现出来,同时,游戏的主题要在对话中得以体现。对话是体现主人公性格特点的最佳方法。对话不要单调呆板,要尽量夸张一些,也有必要带上一些幽默的成分。游戏毕竟是娱乐产品,让游戏者得到最大的享受和放松才是它最突出的功能。如果不是题材被严格限定于正正经经的严肃题材的话,不妨适当的放松对话的设计尺度,不必完全拘泥于时代和题材的限制。

  对话对于体现游戏中各个人物的个性起着至观重要的作用。无论是在戏剧、电影还是游戏中,各人性格在对话的内容上体现得最为突出。

  2.3 剪辑在游戏中的应用

  很多原先从事影视创作的人员,非常喜欢在游戏中利用剪辑的手法来衔接游戏中的各个场景。其实在游戏中,除了特殊需要,剪辑手法很少应用到实际制作中。因为游戏总是跟着主人公的遭遇来发展的,很少有数线并行的情况发生。不过对于交代剧情和展示全局,剪辑是不错的选择。

  2.4 视点在游戏中的应用

  同戏剧部分一样,在电影的手法中也有第一人称视点和第三人称视点。要注意一点的是,在同一部游戏中,不要做视点之间的切换--即一会儿用第一视点,一会儿用第三视点,这样会造成游戏者的困惑和游戏概念的混淆。

  目前有很多游戏设计就是犯了在游戏中切换视点的毛病,尤其是在游戏中的过关演示动画或游戏中交代剧情的动画中,才用了与游戏中不同的视点。最常见的是游戏全部以第一人称视角进行,但过场全部是第三视角的。

3、游戏剧本设计

  3.1 游戏的类型(简单举例)

  1)即时战略游戏

  2)DOOM类游戏

  3)RPG

  4)AVG

  5)混合类型:融合若干游戏类型的游戏,最具前景的游戏。很可能以后所有的游戏类型全部要由这种游戏类型所代替。

  3.2 游戏设计中的一些诀窍

  3.2.1 定时器的作用

  在游戏中,定时器的作用是给游戏者一个相对的时间概念,使游戏的向前发展有一个参考系统。在游戏设计中,可以将两种定时器混合使用,但不能造成玩家的困扰。

  3.2.2 界面的设计

  在游戏中,界面应该设计得尽量的简单,易于游戏者理解,要尽量体贴玩家。多采用图象的、符号式的界面设计,少采用单调、呆板的文字菜单方式。而且也不一定是菜单式的,要更新界面设计的观念。

  3.2.3 游戏中的真实与虚构

  游戏者在玩游戏时,主要是可以体验不同于生活的历程,得到心灵上的解放。所以游戏的世界可以是虚构的,但游戏中的人物、感情等东西则必须是真实的。游戏的本质核心要贴近生活,但游戏的题材可以是各种各样的。

  3.2.4 设计道具

  道具的设计要注意合理。不可能将一辆坦克装到自己的背包中去。另外注意的是思考要全面。比如在游戏中,游戏者需要将一枚钉子钉进墙壁中,那么他需要一把铁锤,这也是游戏设计者设计的难题之一。可是如果这是在旁边有一块石头可以捡起来,在现实生活中,我们是可以用石头钉钉子的,那么在游戏中,你也应该允许游戏者使用石头在钉子上,而不能在游戏者使用石头在钉子上时,出现“喔,你不能这样使用”的提示,从而必须让游戏者按你所设计的方式进行游戏,这是不合理的。如果你的设计非让游戏者找铁锤不可,那么你就不要给他石头。

  有一点要值得游戏设计者十分重视的是:你的任务是尽量帮助游戏者,而不是百般刁难他们。

  3.2.5 RPG游戏设计的误区

  RPG游戏中最常见的两个误区是:死路和游荡。

  死路指游戏者将游戏进行到一定程度以后,突然发现自己进入了死路,没有可以进行下去的线索和场景了。通常出现这种情况是因为游戏设计者没有做到设计全面,没有将所有游戏的可能流程全部设计出来,而游戏者又没有按照游戏设计者所规定的路线前进,从而造成了在游戏过程中的死路。

  游荡指游戏者在广阔的地图上任意移动而难以发现将游戏进一步发展下去的线索和途径。这种现象在表现上很类似于死路,但两者有本质的不同。解决游荡的方法是在故事发展到一定程度的时候,就缩小世界的范围,使游戏者可以到达的地方减少;或者使线索再更加明显,给予更多的提示,让游戏者能够轻松的找到自己的目标。

  3.2.6 游戏的交互性与非线性

  交互性指游戏对游戏者在游戏中所做的动作或选择有反应。举个很简单的例子,当一名英雄到达一座城镇中后,城中没有人知道他,但当他解决了城镇居民所遇到的难题后,他在城镇中应该就成为了一名知名人士。居民们见到他以后会有反应。还有例子就是,当主人公帮助了一名NPC后,这名NPC以后见到主人公的态度应该有所不同。更加完善的设计是给主人公加上某个参数,使他一系列的所作所为,最后影响到游戏的进程和结局。

  非线性指游戏应该是开放时的结构,而不是单纯的单线或是单纯的多线制。即游戏的结构应该是网状,而不是线状或是树状。即游戏中的分支之间允许互相跳转,不是单纯的树状。

线状结构 线状结构 网状结构

  3.2.7 游戏中的奖励和隐藏设计

  游戏进行到一定程度要给游戏者一些奖励,比如漂亮的画面、精彩的过场动画甚至是有用的道具等在游戏过程中得不到的东西。与奖励类似的是游戏中的隐藏设计,比如Cheat Mode、无敌密码还有隐藏关卡、隐藏人物等设计。这些设计非常有意思,也很有必要加上,但是要注意这些设计不要影响游戏的正常进程,毕竟它们是属于噱头的范畴。

  3.2.8 关于游戏中的死亡

  要让游戏者从自己的死亡中学到东西,死亡这种惩罚措施才是有益的。

  3.2.9 游戏中的对话

  游戏中的对话种类分3种:

  1)无对话游戏:例如MYST

  2)有限对话游戏:例如Diablo

  3)自然对话游戏:

  对话的设计要带有情绪性,才能明确的让游戏者做出他想做的选择。

  如果游戏者选择对话的一个,那么其他话题将消失,以后的话题是有他所选择的第一个引发的,不要让游戏者重复的看见以前的话题。

  好的游戏设计者应该能够写出出乎游戏者意料的对话。

  对话不要单调的重复,一般要有50句左右的常用无意义的对话,它们之间互相组合,才可以是游戏者不觉得对话单调。不要遇到某个NPC的话总是:“你好”、“你好”,而尽量要做到每次不同。

  3.3 游戏设计者与其他部门人员之间的合作

  3.3.1 如何合作

  设计者超前的设计如果能够与编程人员合作恰当,能够创造出惊人的效果和成功的游戏 在编程人员水平不足的情况下,双方要相互协调,达成一致的结果,能够让双方都能接受。

  组员中每个人对于游戏都有自己的想法,设计人员要充分采纳建议中好的部分,但一定要有一个明确的主设计思想和一个能够为整个游戏负责的人,当出现不一致的情况时,他可以有最终决定权。

  3.3.2 游戏制作的关系

图3:游戏制作的关系图

  游戏制作的三方面可以是一个稳固的三角形,这个三角形可以沿中心旋转,三方面任何一方都可以处于顶点位置,即以任何一方作为游戏制作的侧重点,都有可能制作出一款好游戏。

4、多人在线游戏(MUD)

  4.1 理想的MUD结构

图4:理想的MUD结构

  图中的大圈表示整个游戏世界,小圈表示有意思的事情的发生地点,其他的空白地方代表世界中的原野、城镇、乡村……游戏者在世界中不受限制,可以任意移动,随意触发事件。事件与事件之间没有必然的联系,但如果某件事件发生了以后,会对整个世界造成影响,比如其他地方的人们知道了事件的发生,从而对游戏者改变态度。

  4.2 混沌理论

  所谓混沌理论应用在游戏中,指事件接近随机的发生,但实际上是有规律可循的。

  Lee Sheldon先生对此有他自己的一个理论:“糟糕的一天”理论。

  比如,以下事件发生对你是有影响的:

  1 领带皱了

  2 和同事吵架了

  3 被领导斥责

  4 没有中午饭吃

  5 钱包丢了

  6 车子坏了

  …… ……

  这些事情可以在你的任何一天发生,但你又不知道它们什么时候发生,将要发生什么,但一旦它们发生了,肯定会对你的情绪有影响,而且发生的件数越多,对你的情绪影响就越大。当它们发生的次数足够多的时候,你就会认为你度过了“糟糕的一天”。在游戏中的情形也类似。游戏者不知道他将会遇到什么,如果遇到一定数量的事件,就可以让游戏者有感觉了。

  结 语

  目前,国内的游戏设计者们基本全部是从玩家们成长起来的,大家对游戏制作热情有余而经验、理论不足。所以我认为象这种讲座对我们游戏设计者来讲,是非常有必要的。由国外有经验的游戏设计者从理论到实际向我们讲述游戏的设计,对我们提高游戏的设计水平大有裨益。

  实际上,游戏设计是一门非常特殊的艺术,它要求就业者有较高的综合素质。不仅要有创造力、文学功底,甚至对于电影、戏剧、历史、文化等都要有一定程度的根底,最好还对编程和美术有一定的了解,这样才能成为一名成功的游戏设计人员。目前国内业界流传着这样的话:“会编程的当程序员,会画画的当美工,什么都不会的当策划。”这在一定程度上反应了目前大陆游戏制作人员的情况,也指出只有游戏设计是非专业人员在从事这一份工作。我们在痛心的同时,一方面通过自己的努力,自己摸索前进;一方面也在迫切的寻找任何可以提高我们专业水平的机会。

家园 实现一个非线性的故事 

实现一个非线性的故事 

  目 录

  1 故事内容

  2 故事开始

--------------------------------------------------------------------------------

在这篇文章中,我将通过一个故事来讨论如何做到非线性。

1、故事内容

  故事的背景是欧洲中世纪的一个国家,玩家扮演一名骑士,有一个邪恶的巫师绑架走了国王的继承人,你的任务是去打败他,夺回王子。

  首先让我们整理一下在这个游戏中所用到的角色。我们有玩家扮演的骑士,他的随从,他的伙伴,王子,邪恶的巫师,以及大量为巫师效力的鬼怪。其它的一些角色并不需要被提及,因为他们基本上只是下角料,与故事的发展没有关系。

2、故事开始

  所有的故事都需要一个开头,这个故事的开头是,做为主角的你与你的随从在国王城堡中一个庭院中,你们都刚刚听到王子被绑架的消息,并决定马上出发去拯救王子。

  这就是这个故事发展的原因。直到现在为止,玩家对这个故事还了解得不够多,有很多的情节隐藏在后面,玩家有充分地可能去经历,去做一些令人振奋的事,这种模糊的希望使玩家能够沉浸下去。

  故事中所涉及的事件,都是有关联的,这就可以做成分支,使玩家可以自由地选择。为了在游戏中实现一个非线性的故事,你并不一定非要非线性的游戏操作方式。因为故事中不同情节的发展依赖于玩家的具体选择。但我们可以想像到游戏操作方式的非线性将会带给玩家更多的自由空间。

  2.1 第一个选择

  在开始寻找王子的过程后,玩家马上发现王子是被巫师手下一个巨大的妖怪给带走了。这个妖怪并不能够被一般的方法杀死,传说只有用一把神秘的宝剑,才可以杀死怪物。玩家必须作出选择:

A:先去获取这把神秘的宝剑(可能会失去救回王子 宝贵时机)

B:不去拿剑,直接去救王子(那么不可能去杀死怪物)

  依赖于玩家在这处的选择,我们需要为故事将来的发展设置二个不同的标志。如果做A,巫师将有足够的时间派遣他的手下去协助看守人质,玩家拿剑返回后将不得不与大量的怪物战斗来作为补偿。做B,玩家将会有一个更困难的任务,就是要想出计策去把怪物锁在牢笼内。

  就象你已看到的,这个游戏仅仅刚开始,但是我们已经能够来改变故事的发展过程了,当玩家玩到了一定的阶段,他会发现先前做的选择会如何地影响后来情况的变化。这会使玩家觉得这种选择有意义,合理。这种前后关联的因果关系也是游戏给玩家快乐的一个重要部分。但如果这种关联设计得不合理,那么会使玩家感到迷惑,破坏游戏的沉浸感。

  2.2 第二个选择

  在离开国王的城堡后,玩家来到一座已被破坏的城镇上,看到巫师的军队正在攻击的一个骑士,而他正是主角以前的一个老朋友,解决了敌军后,得知巫师的军队将要对国王的城堡发动突袭。这时,玩家必须决定:

A:让他返回城堡里来警告国王即将到来的袭击(那么老朋友可能会因再遇到敌军而死亡)

B:让他加入进自己的队伍中,来共同对抗巫师(老朋友可能会在这个过程中出力不少)。如果选了B,那么玩家要面对一个困难的任务 ― 在敌军袭击城堡前去阻击敌军。如果玩家不能有效地牵制住敌军,那么城堡会遭到凶猛的攻击,许多无辜的人会死亡。而作为的补偿是得到了老朋友的帮助,使他在战胜巫师上更有把握。

  这个选择是灰色的,因为其中的一个选择会导致多个事件的发生,也使故事产生了更多的分支。不过这个选择的设计很好,因为设计者用人类的道德来使玩家陷身其中,他们必须做出一个抉择来影响游戏中这些无辜的人,如果任务失败了,很多人会死亡,但如果成功了,很多人将幸免于难。这种紧密的联系使玩家沉浸到游戏中去了。

  2.3 第三个选择

  玩家已经成功地完成牵制敌军任务,准备进入巫师的地堡,这已经接近最终和巫师摊牌的时候了。战胜巫师手下一个头目后,玩家可以把它关起来,或把它杀死。这个小妖怪会试着与玩家做交易,如果饶它一命或把它放出来,它将会帮助玩家来摧毁巫师而且把王子送回城堡。如果玩家决定释放它,小妖怪会遵守诺言,不过结局会改变。如果玩家并不想去杀死巫师,想自立为王,那么另一个选择是杀死头目。这种决定虽然比较细小的,但需要慎重地考虑。在这种情况下,它会对最终的结局产生破坏。你给了你的玩家一个惊异的选择,而它本不应该出现在这种情况下。通过这个选择,主角变成了一个反面的角色,象换了一个人,这个分支选择让玩家感到突兀,而且不合情理

  结论

  我们看到了实现故事改变的三种不同方式,不过还有更多的改变游戏的方式。就我所知道的,《银翼杀手》(WestWood Studios)可能有三个不同特长和性格的主角,玩家根据自己的偏爱来选择主角。这可能是实现非线性的一种好的方式。而不是象上面提到的给玩家多个的选择点。研究非线性故事的实现方法将持续许多年。除了游戏以外,其它媒体所不能实现非线性,非线性将把视频游戏带入主流社会,并证明它的存在价值。我坚信这一点。

家园 2D飞行射击中简单的跟踪算法

2D飞行射击中简单的跟踪算法

  首先,本文讨论的是很简单的算法。高手莫入。

  在飞行射击游戏中,恐怕没有一个游戏不用到跟踪算法的。比如在玩街机的时候,是不是经常挂于BOSS的“跟踪弹”?

  这是怎样实现的呢?很简单,只要有高中的一点数学知识就行了。

  首先回忆几个三角函数:sin、cos、tan(tg)、arctan(arctg)。

  sin(x) ― 对边/斜边。在1,2项限为正,3,4项限为负。

  cos(x) ― 邻边/斜边。在1,4项限为正,2,3项限为负。

  tan(x) ― 对边/邻边。在1,3项限为正,2,4项限为负。

  考虑到游戏里面的坐标系如下所示:

图1

  假设敌人子弹的坐标为slug.x,slug.y,子弹的速度为slug.speed(全部是double型)。上面的三角形的斜边就代表子弹的速度,则子弹每次移动的时候座标的改变为:

  slug.x+=slug.speed*cos(theta); slug.y+=slug.speed*sin(theta);

  在敌人子弹向你发射过来的时候,首先要计算子弹位置与你所在的位置所夹的角度theta。

  简单计算就是:

  double deltax=player.x-slug.x;  // 注意,是以主角位置为起点,在上图中表示就是x1-x0

  double deltay=player.y-slug.y;  // y1-y0

  为了防止在相除的时候分母为0,做一个判断,使分母近似为0,究竟是负的近似还是正的近似呢?这就需要比较子弹和你的Y坐标谁大谁小了。

  if(deltax==0)

  {

    if(player.y>=slug.y)  // 子弹需要下移

      deltax=0.0000001;

    else          // 子弹需要上移

      deltax=-0.0000001;

  }

  同理,对deltay作判断:

  if(deltay==0)

  {

    if(player.x>=slug.x)  // 子弹需要右移

      deltay=0.0000001;

    else          // 子弹需要左移

      deltay=-0.0000001;

  }

  现在对角度所处的项限作判断:

  if(deltax>0 && deltay>0)

    angle=atan(fabs(deltay/deltax));    // 第一项限

  else if(deltax<0 && deltay<0)

    angle=π-atan(fabs(deltay/deltax));   // 第二项限

  else if(deltax<0 && deltay<0)

    angle=π+atan(fabs(deltay/deltax));   // 第三项限

  else

    angle=2π-atan(fabs(deltay/deltax));  // 第四项限

  其中π取3.1416926…………(呵呵,别忘记近似哦)。好了,现在已经得到正确的方向了,可以计算子弹坐标了。

  slug.x+=slug.speed*cos(theta);

  slug.y+=slug.speed*sin(theta);

  这样,每次子弹移动之前做一下判断,重新计算角度,怎么样?“跟踪弹”出来了吧?

家园 解拆《傲世三国》的图形文件

解拆《傲世三国》的图形文件 出 处:程序人生

  《傲世三国》的图片资源都按555与565格式分别存放在555与565两个目录下,图片都是以资源的形式存放在DLL中的,因此我们可以很方便的导出这些资源。粗略的观察了一下这些文件,发现许多都是以xbm开头的,可以肯定这些是图片文件无疑。从观察的结果看,所有这些图片都有一个64byte的文件头。下面我选用了555\res02.dll中的30450资源来进一步观察。文件头如下:

  78 62 6D 00 00 00 00 00 00 00 00 00 00 00 00 00

  00 00 00 00 5A 00 00 00 50 00 00 00 10 0F 01 00

  1F 7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00

  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

  根据我的经验推测,文件头的数据结构大体是这样的:

  struct FILE_HEAD {

    BYTE ID[20];    // 图片文件标识="xbm..."

    DWORD Width,Height; // 定义图片的宽和高

    DWORD ColorMode;   // 这个值在555时为10f10,在565时为11010,推测应该是定义颜色模式的

    DWORD MaskColor;   // 定义透明色的值

    DWORD AlphaChannel; // 当图片带Alpha通道时,该值为1。

    DWORD ImageOffset;  // 当图片带Alpha通道时,该值为图片数据在文件中的偏移量。

    BYTE Reserved[20]; // 暂时还没有发现这些值被使用

  }

  接下来的数据:

  40 01 00 00 42 01 00 00 44 01 00 00 46 01 00 00

  48 01 00 00 4A 01 00 00 4C 01 00 00 4E 01 00 00

  50 01 00 00 52 01 00 00 54 01 00 00 56 01 00 00

  58 01 00 00 5A 01 00 00 5C 01 00 00 5E 01 00 00

           …… ……

  这些数据排列很有规律,一共80个,应该不是图像数据,明眼人一看便知,这些是每一行图像数据在文件中的偏移量,当读入内存的时候这些值就会被加上图像数据的首地址,从而成为指向每一行图像数据的指针。这些对我们解图片用处不大,解的时候可以跳过这些数据。接下来是真正的图像数据:

  B4 00 B4 00 B4 00 B4 00 B4 00 B4 00 B4 00 B4 00

  B4 00 B4 00 B4 00 B4 00 B4 00 B4 00 B4 00 B4 00

  B4 00 B4 00 B4 00 B4 00 B4 00 B4 00 B4 00 B4 00

  B4 00 B4 00 B4 00 B4 00 B4 00 B4 00 B4 00 B4 00

  B4 00 B4 00 B4 00 22 00 06 00 6A 35 8A 35 49 31

  2C 00 0A 00 25 31 E9 51 C9 4D 67 3D C3 20 56 00

           …… ……

  从刚才的偏移量数据看出,每行的数据量都不相同,可以肯定是压缩的数据。根据我的经验判断,这些图片使用了最简单的只压缩透明色的RLE压缩算法。这个算法对于我们来说是很熟悉的,早在很多年以前(写闪电战机的时候)我就开始使用了,下面简单介绍一下。

  第一个WORD数据0xB4,表示连续透明点的长度是180(怎么是180?哦,应该再除2才对),所以应该画90个透明像素点(注意:图片宽度也是90,因此第一行就已经画完了),接着转下一行,又是0xB4,接着转下一行……。下面看这行数据:

  2200 0600 6A35 8A35 4931 2C00 0A00 2531 E951 C94D 673D C320 5600

  0x22表示画17个透明像素点,0x6表示画3个像素点,像素数据跟在后面0x356A、0x358A、0x3149,接着再是透明点、像素点……直到一行画满为止。至此,这类图片格式就全部解拆完了,。

  下面,我们再来看看带Alpha通道的图片格式,这类格式和前面一种差不多,只不过在文件头后面插入了一个Alpha通道,Alpha通道的数据是不压缩的,每个点为一个Byte宽度,因此,只要跳过Width*Height的数据就和上面的处理方法一样了。

  由于时间有限,暂时我就只发现了这两种图片格式,可能还有其他没有被发现的图片格式,就靠大家去发现了。所有的数据结构也只是本人主观臆测,未必正确。另外程序我也写出来了,但是我不打算拿出来了,一来考虑到版权保护的问题,二来是想锻炼一下各位的动手能力,如果你有兴趣的话,看了上面的介绍,应该很容易写出解图片的程序。

全看树展主题 · 分页 上页


有趣有益,互惠互利;开阔视野,博采众长。
虚拟的网络,真实的人。天南地北客,相逢皆朋友

Copyright © cchere 西西河