DYNRIO

Jerry's Interactive Design Jerry的互动设计

星期日, 一月 03, 2010

The Django Book中文版

GAE开发的必备资料之一

in reference to:

"The Django Book"
- The Django Book (view on Google Sidewiki)

星期三, 九月 27, 2006


look web2.0- myspace(1)



2006-09-01 19:33:20



对于产品设计的有用的要点

1myspace用户出生时间段85-95

2年轻人的语言促成了他们自己的文化圈,同时也是隔离成人的壁垒

3年轻人关注的电子设备:电脑、手机和itune



默多克预言:未来生活方式是“MySpace生存”

人类社会经历着一场前所未有的变革,人们的生活方式、尤其是沟通交流方式,从来不曾如此怪异。而站在这场变革最前沿的,是一群生于1985后的青少年,多媒体手机、Ipod、笔记本电脑、DV……他们周身都是各种电子产品,他们通过这些玩意来记录现实生活、延续虚拟生活,他们已经模糊了两者的界线;即使就坐在身边,还是喜欢用IM进行交流;他们更注重建立和维护网上形象;他们无法专注于一件事情,“多重任务处理”模式让他们更为自在……他们就是所谓的MySpace一代。




“我的地盘”对这些青少年来说,不仅仅是一个交友平台,而是他们生活的核心,在现实世界中不愿展示的东西,他们在MySpace都毫无保留地呈现出来。因为这里是父母或上司无法涉足的自由地带。




但丰富而直白的生活直播,不仅招来了朋友,也引来了色狼和居心叵测的商业间谍。于是,色情狂、FBI侦探、家长请来的网络探子和张扬无畏的e代人,在MySpace这片虚拟领地中上演着一幕幕网络无间道。




大亨预言

未来生活方式是MySpace生存

传媒大亨默多克说:“谁都看得出,这就是未来真实的生活方式。”默多克所讲的生活方式,就是他斥资5.8亿美元收购的交友网站??MySpace.com??所营造的一种全新的网络化、数字化生存。


默多克之所以对MySpace有着浓厚兴趣,是因为他认为,科技会改变整个编辑、出版人的固有模式。现在是人们控制内容甚至渠道的年代。而传统媒体最容易流失的受众是年轻人,这批受众是接受科技最活跃的一群。所以先要捕捉这一庞大的社群,为未来铺路。


这一群年轻人正被数字时代的各种新奇玩意包围:博客、拍客、P2P、mashup,手机照相让每个人都可以成为狗仔队。

MySpace称王

MySpace成为美国第一大网站

今年7月12日,全球各大IT媒体都对这则新闻进行了报道??“互联网流量测评厂商Hitwise表示,社交网站MySpace.com超过Yahoo和Google已经成为美国第一大网站。”这是一个值得关注的信号,世界最大的互动交友网站,超越了世界上最大的传统门户网站和世界上最大的搜索引擎网站。这预示着,世界互联网的核心将转向个人门户,而一个“大众信息时代”已经到来。




创建于2003年的MySpace网站,完全面向30岁以下的年轻人,主要提供博客、即时通讯、邮件、音乐、图书、电影、视频、相册、论坛、分类、交友、聊天室、招聘等服务,几乎涵盖了所有WEB2.0的交流工具与传播手段。其本质就是一个典型且成熟的个人门户网站。


任何人都可以通过MySpace聊天、上载手机摄录、MP3、DV片断等任何好玩的事物,共同评论最火的乐队、最热门的电影和最关注的社会话题。


目前MySpace已经有2000万会员,每天又有10万人加入,浏览页数每月62亿。更好的是内容由会员提供,内容成本基本为零;但目前广告量奇低,不过用户都有参与性。这个网站最超前之处并不是把人与产品或把人与资讯、把眼球与广告连接,而是把人与人连接。根据目前这些内容的受欢迎程度及下载热度,默多克认为MySpace带给他的终极价值为“创造流行热潮的威力”(The
Power to make hits)。这是所有媒体都关注的话题,因为能够带来注意力。



e代突起

“85~95”生人是MySpace一代

多媒体手机、Ipod、笔记本电脑、DV……他们周身都是各种电子产品,他们通过这些玩意来记录现实生活、延续虚拟生活,他们已经模糊了两者的界线;即使就坐在身边,还是喜欢用IM进行交流;他们更注重建立和维护网上形象;他们无法专注于一件事情,“多重任务处理”模式让他们更为自在……




他们就是典型的MySpace一代。



周六下午7点,大学新生亚当斯打开MySpace.com网站,登录一个朋友的博客,在上面找出当晚聚会的地点,然后通过即时通信软件和几个朋友聊起天来。这时,亚当斯的男友在线上告诉她一家电脑零售店的信息。她一边聊一边更新自己的贴图博客,又借电脑扬声器听iTune音乐。这种网上生活的场景,也正是千百万美国年轻人正在经历的共同体验。美国《商业周刊》由此写道,时下的年轻人是“MySpace的一代”。



早在2002年,克里斯?德沃夫和汤姆?安德森收购了互联网域名MySpace.com,并于2003年推出全新的交友平台,网站的所有内容都由用户提供,这种将社会化和个性结合起来的新体验,吸引了青年人在网站上加载照片、阅读乐队新闻、交流他们认为“酷”的东西以及体验流行文化。MySpace甚至引起部分年轻人生活习惯的变化,比如见面不再问“你的电话号码是多少”,而是更热衷于问“你的MySpace是什么”。




形成所谓的MySpace一代并不是单靠一个交友网站就能够办到的。随着科技的发展,各种便携式电子产品陆续涌现,人们能够随时随地记录生活,并随时随地把它公布开来,与所有人分享,也能随时随地观察别人的生活,对其进行评论和交流。技术发展使内容制作成本趋零,网站提供交流平台,两者的完美结合,成就了MySpace一代。




由美国《洛杉矶时报》近日针对12岁~24岁之间的美国年轻人进行的一项调查显示,大多数接受调查者反映,各种各样的电子产品是他们生存中不可或缺的一部分,尤其是电脑。在“沙漠孤岛惟一携带物品”这道选择题中,电脑高居榜首。其次是手机,因为,对这些少年和20来岁的年轻人来说,手机也已经成为生存必需品。




这次调查结果的另一个显著特征是,大多数这个年龄段的青少年,都非常喜欢同时干几样事情,而不是专著于做一件事。17岁的高中生纳撒尼尔?约翰逊就是一个典型,他非常喜欢电脑,因为电脑允许他同时做好几件事情:“你能同时打开5、6个任务窗口,上载手机照片、查看电子邮件、聊天,同时还能看电影。”约翰逊还说,他的朋友大部分都这样做。




不过人的精力毕竟有限,你当然可以边做家庭作业,边在网上冲浪、聊天,边通电话和听音乐,但是假如这些同时涌来,你就会手忙脚乱,约翰逊发现自己最多只能同时做好3件事情。




和其他接受调查的对象一样,约翰逊不能够在安静的环境里做家庭作业。对他来说,家庭作业和重金属摇滚是不可分割的。这就是典型的“多重任务处理”少年,他们感觉自己太忙了,不能在一个时间段只做一件事情,其实,他们也讨厌在同一时间只做一件事情。




而在国内,所谓的MySpace一代,非常好辨认,MySpace翻译过来就是“我的地盘”,是不是特别耳熟?没错,就是这群叫嚣着“我的地盘我做主”的人,你叫他们新新人类也好、80后也好、e代人也好、拇指一族也好,虽然使用手机、电脑等电子产品不是他们的专利,但闲得每天往网络上上传手机短片和张扬的个性,很容易把这些MySpace一代和其他单纯的恋物者(仅仅是喜欢各种电子产品而已,但绝不通过YouTube和别人分享短片。)区分开来。




网络凶猛



就像把你剥光了扔在大街上



技术的发展是任何人都无法阻挡的。电脑和网络对青少年的影响的确相当惊人。年轻一代对网络的依赖已经远远超过了对电视机的依赖。他们正经历着人类历史上从来不曾有过的变革,人们之间的沟通交流从来不曾变得如此怪异:不隔着一条线就无法交流;而隔着一条线就以为远隔千里万里,永远不会相遇。于是,个人癖好、旅游艳遇、家庭住址、体貌特征统统上传了和全世界分享,以为无比安全,实际上和“赤裸裸”地呈现给街对面的色狼一样危险,而要命的是,为了证明自己够酷够猛,正有不少年轻人对着摄像头大脱特脱。




随着有关性猎手潜伏在MySpace上的报道不断出现,Myspace发现更可怕的是被人认为不安全。为了保护青少年,MySpace还制定了一些基本的安全政策,如14岁以下的青少年不得在网站上建立网页;14岁到15岁的孩子建立网页时会自动被设置成隐私级,只能让其指定的朋友浏览。今年6月份,MySpace又宣布了一项安全措施,要求18岁以上的用户必须知道14岁到15岁的用户的全名或电子邮件后方可与其联络。但现在的问题是,用户在MySpace上的年龄是自己报告的。因此,如果有较年轻的用户要把自己说成是年龄较大的用户或反之,MySpace要想阻止他就很难。




自己不传播就万事大吉啦?人是社会的人,你永远无法想象,别人会上传什么东西,因为,操作起来实在是太方便了。拥有了各种高科技电子产品,人人都是狗仔队。




你被人家网上查询、侦讯过吗?这一现象在美国是家常便饭,在法国也是“方兴未艾”。把你的姓名打到查询网站上然后查询,结果往往让人惊恐不已。你参加什么协会活动、你在什么请愿书上签过名、你在网上论坛留下的帖子,甚至还有你的相片……总之,你的一部分私生活完完全全暴露在网络上。




查询网站的效率实在太高了,这引发了许多问题。网虫们的抱怨越来越多,他们的隐私权得不到保障。法国信息自由委员会也对乱发信息现象提出关注,人权联盟的负责人杜比亚纳则说:“有关因特网确实要做许多教育工作,要告诉人们它的好处,也要告诉他们它带来的风险。”




保险商会用查询网站查询投保人健康状况的信息,警察和宪兵会利用它为自己的调查工作服务,招聘公司和企业人事部门的领导会利用它查询候选人的个人经历。年轻的马德约就吃过这方面的亏。




他生活在巴黎地区,在一个反对欧盟宪法的请愿书上签了名。后来,他一打上自己的名字,查询结果上出现的第一网站是绝对的反欧盟网站,他的名字赫然在上,这使他很担心:“如果一个雇主查到这个结果,他还会愿意雇佣我吗?”








look web2.0 - myspace(2)



2006-09-01 19:27:29




有位专家说:“越来越多的人抱怨,自己的相片还出现在从前工作过的公司的网站上。尽管各种措施和技术的限制,现在还是很容易在网上获得许多个人信息。如果你要在网上发表什么言论或什么信息,那就根本不可能把自己掩藏起来。因特网能暴露人们的历史而让人的生活失去平衡。”



有位退休者去年圣诞节前安装了因特网,他很得意地向孙子显示这一成果。小家伙在网上敲入了奶奶娘家姓,结果老头差点没背过气去,网上有很多献给她太太的网站,把他老婆的历史暴露得一览无余:老太太在上个世纪70年代曾出演过多部色情片。




但这还不是最糟糕的,可怕的事情还多着呢。比如有色情网站向网虫们提出新的服务项目:如果他们想报复从前的女友,可以在网站上公布她们的裸照,甚至色情照片。




17岁的珊德拉更是遭了厄运。2003年12月,她和几个朋友去一个水上乐园玩,结果在换衣服时被人偷拍了,偷拍者又把录像带卖给美国的色情网站在“窥视”栏目上展示。




女中学生觉得非常倒霉:“我本来很规矩的,结果人们把我放在色情网站展示,这是我的脸、我的背包、我的毛巾,所有认识我的人都能认出来……以后如果不在家里先换好泳衣,我再也不敢去游泳池了。”水上活动中心的负责人则表示无能为力:“现在摄像机越来越小,我们是防不胜防。”




法律专家托迪约总结说,一旦你在网上传播信息,所有网虫就都可以接触这些信息。由此可以通过信息出现的个人、职业和协会的不同网站,重构你的身份。


托迪约说,有一位妇女参加网上论坛讨论,多次提到她有残疾的儿子,结果几个星期后,她在网上查询到了自己的所有言论,这意味着没有直接参加讨论的人也可以接触到这些信息,这会影响到她的名誉。总之,在网上发布信息时一定要小心。比如,我要设一个个人网站,我要对它做出一定的限制,只是在一段时间内可以让人造访。如果为了庆祝我的生日,我做一个短期网站,我要在网上提到某个人或用他的相片,一定要跟他说明,问他愿意不愿意。




托迪约说,一般情况下,在网上发表信息时一定匿名。另外,你最好多设几个电子信箱,一个用于个人信件,其他用于参加网上论坛讨论。最后,你要经常问自己:我在网上传播了什么信息?为什么要传播它们?



谍影隐现



网络007筑起反色情防火墙



青少年习惯将自己的详细资料登在MySpace.com上,有时还贴上性感照片。



虽然这些内容是给朋友看的,但他们想不到一些心怀叵测的成年人正躲在虚拟空间的某个角落,伺机向他们伸出魔爪。洛杉矶警察局网络侦探保罗?毕晓普举过这样一个例子,他和几位同事伪装成14岁的女孩刚刚把资料贴在MySpace上,立刻就被成年男子的电子邮件“淹没”了。毕晓普侦探提醒父母们说:“对于涉世未深的青少年来说,虚拟空间潜伏的危险丝毫不比现实世界小。”




一些统计数字也证明了这种担心。据美国联邦调查局估计,在任一时间全球都有2万名恋童癖在网上游荡。“针对儿童犯罪研究中心”所做的一项调查发现,在10~17岁的美国青少年当中,1/5的人曾在网上被陌生人引诱过。但孩子们警惕性普遍不高,他们很少会将这些情况报告父母或警察。




在美国政府中,最早开始打击网络儿童色情的部门是联邦调查局(FBI),它在1995年就发起了“纯真图像行动”。1998年,司法部又实施了“打击针对儿童的互联网犯罪计划”。按照该计划,美国成立了45个跨部门特遣分队,2005年逮捕的嫌犯达到1597人。




此外,司法部还投资数千万美元,在全国培训了5万多名网络侦探。国土安全部2003年也开展了“捕猎者行动”,与联邦调查局、财政部特勤处等机构密切合作,逮捕了6900多名犯罪嫌疑人。




詹姆斯?麦克拉夫林是这些“网络侦探”中的佼佼者,3月的一天,在新罕布什尔州基尼市一座不起眼的写字楼里,麦克拉夫林打开电脑,进入一个聊天室。他知道,注册进入“雅虎老爹男孩性爱聊天室”的成年男子许多都是恋童癖者。麦克拉夫林以一个14岁男孩“亚当”的身份进入聊天室,很快吸引了一个名叫鲍勃的34岁俄勒冈州男子的注意。通过网络摄像机,注册用户可以看到鲍勃只戴着一副眼镜和一枚婚戒,此外一丝不挂。




“亚当”向鲍勃发去一条带性暗示的信息,这是青少年中间常用的一句话,麦克拉夫林掌握有几十句这种“童言稚语”。鲍勃很快“咬钩”了。你来我往交谈了几句后,鲍勃突然问“亚当”是否与男人做过“那件事”。亚当回答说没有,但他愿意尝试一下,并让鲍勃发来几张其他男孩子的裸体照片。鲍勃很快照办了。“亚当”给他回复了一张14岁男孩的照片,当然不是他自己的。麦克拉夫林知道,用不了多久自己就可以给鲍勃戴上手铐了。




迄今,麦克拉夫林已将400个色狼绳之以法,他成为美国最成功的网探之一。他在互联网上通常使用的两个身份是“亚当”和“布拉德”。这两个虚拟人物都是14岁的少年,长着一头淡黄色头发;喜欢游泳和骑自行车,在聊天时表现得“童言无忌”,坦承已经开始手淫,并不介意与成年男子“胡闹一番”。




在麦克拉夫林看来,互联网的普及已经改变了色情的本质。青少年不再偷偷摸摸地翻看父母的《花花公子》,他们可以几乎不受限制地在网上浏览露骨的色情内容,或者进入聊天室,结识一位陌生的成年人。突然间,孩子不经意的好奇心就会演变成父母的噩梦。




麦克拉夫林发现,人们在虚拟空间里似乎更易暴露黑暗的一面。在被他逮捕的400人中,不乏有体面职业的人士,其中包括警官、空军上校、牧师、社会工作者和教师。这些人不惜冒着身败名裂的风险,向和自己孩子一般大的未成年人发出露骨的性暗示。警方往往能从他们的电脑中搜查出大量不堪入目的图片或视频文件。为取得孩子们的信任以达到自己不可告人的目的,这些人甚至把自己的照片、住址、工作单位与职务、手机和办公室电话等告诉对方。这些最后都会成为给他们定罪的证据。




麦克拉夫林之所以能假扮14岁少年与恋童癖者聊天而不被对方怀疑,主要原因是他能“像毛孩子那样讲话”。



网上色狼现在越来越谨慎和狡猾。他们在聊天时通常先试探对方的身份,例如他们会在谈话过程中突然问你对某个当红青春偶像是否了解,如果回答不上来他们就迅速消失。




为提高网络侦探的办案能力,近两年来美国司法部开始聘请一些在校中学生给特工或警察们上课,教给他们最新的网络俚语和流行趋势,使他们在办案时能够更好地伪装自己。这个创意来自一位联邦调查局特工的灵感。一天他看女儿在电脑上给朋友发即时消息,大部分内容自己都看不懂,他突然意识到联邦调查局现有的培训远远不够,于是把中学生请进了联邦调查局课堂。




在课堂上,这些稚气未脱的青少年教特工和警察如何像少男少女那样交流,要他们阅读《年轻人》等时尚杂志,告诉他们当今最红的偶像都有谁,哪些服装店最受青少年欢迎。




怎样写即时短信也是网络侦探的必修课,其中的一些缩写你在任何辞典中都查不到的,例如“l2m”既可以指“听音乐listen to
music”,也可以指“想见面love to meet”。“pos”曾让许多侦探大惑不解,原来它的意思是“parent over
shoulder??父母站在我身后呢”。侦探们还学到青少年网络交往的许多重要规矩:初次见面聊天时绝不要说“你好”,青少年不喜欢这种假惺惺的客套;在即时消息里也不要使用正确的语法,不要区分大小写,这与青少年的叛逆精神不相符。




虚拟攻防



监控与反监控,父母输在起跑线



以前,孩子的心事写在带锁的日记本里,和恋人的交往是响一下就断的电话铃以及隐晦的小纸条,小弟兄间的秘密是某个下午某人家的黄色录影带。这些行为根本无法瞒过父母,因为他们也是过来人。但现在,一切都不同了,使用MySpace网站,彻底改变了年轻人交流和娱乐的方式。父母不知道孩子在那个高科技盒子前在做什么,无法判断他们在写作业还是在共同分享一段很刺激的小电影;不知道孩子们在不动声色的情况下已经定好了明天的约会;原本以为孩子在给手机充电,实际上他是在上载更衣室里的照片。




“孩子们现已成为内容创造者。以前,解决互联网问题的办法就是防止他们接触不适宜的内容,但现在要解决这一问题没那么容易了,”美国全国学校董事会协会的教育技术主管安?李?弗林说道,“学校被这类技术的爆炸式增长弄得不知所措。”




MySpace允许每位用户创建个人简介网页,并可以运用照片、视频、音乐和图片,便捷地进行个性化设置,其他用户可以自由访问这些网页。用户可以在网页上讲述他们的生活,并且能将他们的个人简介网页与其他人建立链接,创建“朋友”圈。在社交网站大受欢迎的同时,危险也随之而来,不仅有些色狼四处寻找猎物,还有一些青少年通过张贴恶意信息威吓他人。




“许多(媒体)报道谈到或展现了父母的怀疑和忧虑,他们担心孩子们会在MySpace上面随意张贴个人联系信息,并透露在吸毒、酗酒和性方面的冒险经历,”法拉戈先生说道。他提供的服务可以让父母选择搜索标准,比如未成年饮酒、吸毒、性行为、性图像、犯罪、虚报年龄,或者透露街道地址和电话号码。大学录取官,甚至潜在的雇主也可能会关注这些可以获得的信息。




社会各界呼吁社交网站引进年龄核查技术,但对于那些年龄太小的用户来说,他们还没有任何身份记录,比如驾照,因此实施起来非常困难。



于是,美国佛罗里达两名法律专业学生发现了一个商机:开一家在线侦探社,向父母们报告他们的孩子在网上干了些什么,然后从中收取费用。




其实父母们可以免费获得他们想要的信息,但大多数父母要么没有时间,要么不具备监控孩子活动的技能。后者是关键所在,因为孩子们每天都在MySpace.com等在线社交网站上消磨掉好几个小时。许多成年人都是“数字时代的移民”,而他们正抚养着“数字时代的土著”,默多克在去年新闻集团收购MySpace时如此描述这一代沟。




“我们只不过比很多父母更熟悉这类网站,”Profilescreener.com的联合创始人保罗?法拉戈(Paul Farago)说道。Profilescreener.com于今年推出。



(信息时报)





ABOUT GOOGLE & WEB2.0




2006-08-22 18:42:41





如果google真的很好,那我们可以在里面生活么?

当然不能。google带来的海量结果。除了增加咨询并不会带来个人的心智提高。



只有某些精英个人整理出来的book才可以做到这一点,并支持人类社会的进步。



嗯。既然google在做平台这件事上已经很好了,就不需要有google第二了。

把网站做成一个“精英”,提供“自己的”思想。

而web2.0恰恰可以为这个“精英”提供source和听众,哈哈。打倒原始人的XX




UI word(1)



2006-06-08 17:43:50 



UI行业常用名词及缩写定义

[2006-1-29]

出处:ChinaUI

作者:白鸦









- 什么是 UI :

UI的本意是用户界面,是英文User和 interface的缩写。

- 什么是 GUI

Graphics User Interface 图形用户界面

有时也称为WIMP=Window/Icon/Menu/Pointing Device 窗口、图标、菜单、指点设备

- 什么是 HUI

Handset User Interface 手持设备用户界面

- 什么是WUI

Web User Interface 网页风格用户界面

- 什么是用户界面设计:

在人和机器的互动过程(Human Machine
Interaction)中,有一个层面,即我们所说的界面(interface)。从心理学意义来分,界面可分为感觉(视觉、触觉、听觉等)和情感两个层次。用户界面设计是屏幕产品的重要组成部分。界面设计是一个复杂的有不同学科参与的工程,认知心理学、设计学、语言学等在此都扮演着重要的角色。用户界面设计的三大原则是:置界面于用户的控制之下;减少用户的记忆负担;保持界面的一致性。




- 什么是IA:

information Architect 信息架构

- 什么是UX:

user experience用户体验

- 什么是HCI:

human computer interaction人机交互

- 什么是CHI:

computer-human interaction人机交互

- 什么是UCD:

user -centered design用户中心设计

- 什么是UPA:

usability professionals' association 可用性专家协会

- 什么是AI:

Adobe Illustrator ;目前最权威的矢量图绘制软件,*.ai是它的格式文件。

IA在指一项工作的时候是 Information Architecture, 指从事这项工作的人的时候是 Information
Architect

- 什么是CD:

CorelDraw

强大矢量图绘制软件。别以为是音乐光碟呀!

- 什么是PS:

在这里不是指游戏机,是目前最强大的图形编辑软件Adobe photoshop。

- 什么是ID

Industry Design 工业设计哦,不是Identity(身份证明)

ID 就是identification Proof or evidence of identity, 确认身份的证据或证明

- 什么是MMI

Man Machine Interface人机接口,MMI是进行移动通信的人与提供移动通信服务的手机之间交往的界面。包括硬件和软件。

- 什么是Design House

业内称手机设计公司为Design House。

- 什么是Blog:

Blog,是Weblog的简称。

Weblog,是Web 和Log的组合词。Web,指World Wide
Web;Log,原义是“航海日志”,后指任何类型的流水记录。Weblog 是在网络上的一种流水记录形式。

Blogger或Weblogger,是指习惯于日常记录并使用Weblog工具的人。



- 什么是RSS?

它是什么:站点用来和其他站点之间共享内容的简易方式(也叫聚合内容)。 RSS使用XML作为彼此共享内容的标准方式。

它代表什么:Really Simple Syndication (或RDF Site
Summary,RDF站点摘要)例如:一些免费的软件能够让你阅读那些RSS使能的站点,比如 NewsIsFree 和
Amphetadesk。

它有什么用处:让别人容易的发现你已经更新了你的站点,让人们很容易的追踪他们阅读的所有weblogs。

- 用户优先级定义:

这是需求文档里的常用词,按先后顺序把要做的事情摆好,根据项目进度或突发情况可以删剪一些排在后面的小功能,也就是优先级低的功能。如果优先级为一,那就是有多大困难都要做得事情了。(比较直白)

- iso的后缀是什么

虚拟光驱,winiso可以,winrar3.0以上的版本也可以打开

ISO 是指 International Organization for Standardization 国际标准化组织

- 什么是pop

广告里是标题广告的意思,类似商场里面打出的一些促销标语。软件里就是弹出的意思。

简单的说有流行字体的意思

指的是销售点广告!

POP-point of purchase advertising的缩写。译为"购买时点广告"/店头广告,是一种促销广告。

- 什么是模版

模版在这里指的是网页模版,是当网站中有许多页面版式色彩相同的情况下,将其定义为模版,并定义其中部分可编辑,部分不可编辑,那么在利用模版制作其他页面时就会很方便,不易出错。

- 什么是图标

图标是具有明确指代含义的计算机图形

桌面图标是软件标识,界面中的图标是功能标识。

广义??具有指代意义的图形符号,具有高度浓缩并快捷传达信息、便于记忆的特性。应用范围很广,软硬件网页社交场所公共场合无所不在,例如.....男女厕所标志.....各种交通标志......狭义??计算机软件方面的应用,包括:程序标识、数据标识、命令选择、模式信号或切换开关、状态指示……狭狭义??.ico/.icl文件....

- 什么是demo

演示

demo是样本,用于演示目的.



- 什么是RIA Rich Internet Applications 富因特网应用程序

- 什么是SPI Software Process Improvement 软件过程的改进

- 什么是SPA Software Process Assessment 软件过程评估

- 什么是SCE Software Capabili Evaluation 软件能力评鉴

- 什么是UML Unified Modeling Language 统一建模语言

- 什么是RUP Rational Unified Process 软件建模过程

- 什么是CMM Capability Maturity Model for Software 软件能力成熟度模型

- 什么是IEC International Electrotechnical Commission 国际电工委员会

- 什么是ITU International Telecommunication Union 国际电信联盟

- 什么是W3C World Wide Web 万维网联盟

- 什么是IETF The Internet Engineering Task Force 互联网工程任务组

- 什么是OASIS Organization for the Advancement of Structured
Information Standards 结构信息标准化促进组织

- 什么是DCMI Dobin Core Metadata Initiative 都柏林核心元数据先导计划

- 什么是IRG Ideographic Rapportuer Group 汉字特别授权工作组





UI word (2)



2006-06-08 17:42:55



DB = Date Base 数据库

DNS = Domain name Server 域名服务器

PDA = Personal Digital Assistant 个人数字助理

BCS = Ballistic Computer System 弹道计算机系统 [军]

ERP =Enterprise Resource Planning 企业资源计划

SAP = Satellite Automation System 卫星自动控制系统

OS = Operating System 操作系统

OSF = Open Software Foundation 开放软件基金会

OO = Object Oriented 面向对象

OOA = Object Oriented Analysis面向对象分析

OOD = Object Oriented Design面向对象设计

OOP = Object Oriented Programming面向对象程序设计

JAR = Joint Application Requirement 合作应用程序需求

JAD = Joint Application Design 合作应用程序设计

BUG = Business User Group 业务用户团队

CASE = Computer-Aided Software Engneering 计算机辅助软件工程

UIMS = User Interface Management System 用户界面管理系统

DSC = Decision Support Center 决策支持中心

SMOP = Small Natter Of Progamming Units 小型程序单元

注:1SMOP = 半个程序设计日(4小时);1周 = 32个程序设计小时。

FUPRID因子:用户界面问题可按FUPRID分类。

F = Feature 功能特性

U = User interface 用户界面

P = Performance 性能

R = Reliability 可靠性

I = Installability 可安装性

D = Documentation 文档化

以下不是严格的定义,只是提供可进一步理解的阐述。

特性指的是满足任务需求的充分的领域和用户界面功能。

用户界面指充分的形象(外观)、行为和为了完成用户任务而进行的用户交互(感觉)。

性能包括批处理或者整个后台任务的评估。

可靠性被解释成产品的基本质量,术语称之为零缺陷行为。

可安装性指的是设置和安装的容易程序,这与软件的初次使用有关。

文档化包括所有形式的用户协助和性能支持的文档化。无论是硬拷贝形式还是在线帮助的形式,都要求有文档提供有关产品使用的支持信息。

手机方面的:MMI=Man Machine
Interface人机接口,MMI是进行移动通信的人与提供移动通信服务的手机之间交往的界面。包括硬件和软件。

OS = Operating System 操作系统

OSF = Open Software Foundation 开放软件基金会

WUI = Web User Interface 网页风格用户界面

HUI = Handset User Interface 手持设备用户界面

OO = Object Oriented 面向对象

OOA = Object Oriented Analysis面向对象分析

OOD = Object Oriented Design面向对象设计

OOP = Object Oriented Programming面向对象程序设计

GUI有时也称为WIMP=Window/Icon/Menu/Pointing Device 窗口、图标、菜单、指点设备

JAR = Joint Application Requirement 合作应用程序需求

JAD = Joint Application Design 合作应用程序设计

BUG = Business User Group 业务用户团队

CASE = Computer-Aided Software Engneering 计算机辅助软件工程

UIMS = User Interface Management System 用户界面管理系统

DSC = Decision Support Center 决策支持中心

SMOP = Small Natter Of Progamming Units 小型程序单元

注:1SMOP = 半个程序设计日(4小时);1周 = 32个程序设计小时。



OOA = Object Oriented Analysis面向对象分析

评估用户需求和需要解决的问题。针对用户问题的静态、动态和功能等,输出会提供相应的概念设计。

分析对象的属性(attribute)、关联(association)、生命周期(lifecycle)和相关的域规则(domain
rule)产生出的产品为域产品(domain model)

OOD = Object Oriented Design面向对象设计

将OOA的结果转化成与现实需求更贴切的系统和对象设计

利用一系列相互协作的软件对象来进行软件设计,兼顾应用需要经典的OO概念,如多态(polymorphsim)、接口(interface)、消息(messaging)和继承(inheritance)。

工件为 包图 (package diagram) ,类图 (class diagram ), 交互图 (interaction
diagram)

OOP = Object Oriented Programming面向对象程序设计

使用实现工具,将OOA和OOD的输出转化成产品软件

F = Feature 功能特性

U = User interface 用户界面

P = Performance 性能

R = Reliability 可靠性

I = Installability 可安装性

D = Documentation 文档化

以下不是严格的定义,只是提供可进一步理解的阐述。

特性指的是满足任务需求的充分的领域和用户界面功能。

用户界面指充分的形象(外观)、行为和为了完成用户任务而进行的用户交互(感觉)。

性能包括批处理或者整个后台任务的评估。

可靠性被解释成产品的基本质量,术语称之为零缺陷行为。

可安装性指的是设置和安装的容易程序,这与软件的初次使用有关。

文档化包括所有形式的用户协助和性能支持的文档化。无论是硬拷贝形式还是在线帮助的形式,都要求有文档提供有关产品使用的支持信息。

ID 就是identification Proof or evidence of identity, 确认身份的证据或证明

蓝芽(BlueTeeth) 是由Intel、Nokia、Ericsson、IBM及Toshiba
组成的SIG小组制订,目标为制定一套短距离无线射频连接技术的标准,Bluetooth于99年5月正式发表,蓝芽计划在十到一百公尺范围内,建构起专属个人的无线局域网络,蓝芽技术使上、下传速度均为432Kbps,但采非对称式时,上传为56Kbps、下传则提高为721Kbps,未来则可提升为2Mbps,提高计算机及行动电话的通讯能力。蓝芽以更高的频率(2.4GHz)取代红外线,传输速率更高、应用更广,可以符合未来宽频网络时代的要求。

  未来蓝芽最大的发展目标是在所有移动式装置皆能收发信息,这些产品包括:

  移动电话,笔记本计算机,PDA,数字相机、耳机、键盘、鼠标及投影机,以太网络、调制解调器,家电产品:电视、游乐器、录放机、音响、冰箱、微波炉等。

  几乎目前所有和我们生活息息相关的数字化产品,未来都在蓝芽的计划范围内。而简单的说,蓝芽就是让有线的装置无线化的技术。

  GSM全名为:Global System for Mobile
Communications,全球移动通讯系统,GSM是欧洲开发推出的数码移动电话网络标准,目的是让全球各地有一个共同的移动电话标准,让用户可以在全世界范围内都能通话.GSM
系统包括 GSM 900:900MHz、GSM1800:1800MHz 及 GSM-1900、1900MHz 。

  GSM的特点包括了:防盗能力优秀、网络容量大、号码足够应付、通话相对清晰、稳定无干扰、接收信号灵敏等等。目前世界上两大GSM系统为GSM
900、GSM1800,采用不同频率。特性方面,GSM900频谱较低,波长较长,穿透力较差,但传送的距离较远,手机发射功率较强,待机时间较短;而GSM1800频谱较高,波长较短,穿透力佳,但传送的距离短,手机发射功率较小,待机时间较久

  GPRS General Packet Radio
Service宽频移动数据服务,最大功能在于将以往环状(circuit)网络在架设新的核心网络设备后,转变为分封(packet)的网络,使用的是一种频道分享的技术。以往在环状网络时,一条通讯道只能提供两个人对话,但是在分封网络时,一条通信道却可以同时提供给多个人对话,充分有效的利用系统的容量。

  CDMA
GPRS最高的传输率可以达到115Kbps,当然实际上初期很难达到如此高,首先在手机端,初期业者做的手机只能达到28.8kbps的速率,通过不断改善未来将达到100Kbps以上的速率,如目前日本已经达到64kbpas的技术,预计年内可以达成128kbps的目标。

  HSCSD在通过GPRS的建设后,理论上可以提供到115Kbps以上的传输速率,但是初期由于手机下载速率只有28.8kbps,因此用户看到的仍是以文字为主,难以提供影像与多媒体的传输。

  GPRS具有的优势包括:改善无线电频道的使用状况。提供低成本,品质稳定的服务给客户。快速的联结。
GSM及GPRS同时存在而互不干扰。可与其它IP (网际网络协议)及X.25网络资料相联结。

  CDMA (Code Division Multiple Access) 分码多任务访问是在无线通讯上使用的技术,CDMA
允许所有的使用者同时使用全部频带,并且把其它使用者发出的讯号视为噪声,完全不必考虑到讯号碰撞 (collision) 的问题。

  CDMA的优点包括:

  1. CDMA中所提供的声音编码的技术,其质量比目前的GSM好,而且可以把你讲话时周遭的环境杂音降低,使通话更清析。

  2. CDMA可以减少手机间的干扰,并且可以增加用户的容量,减少手机功率,降低电磁对人体伤害。

  3. CDMA可减少通话时信号中断的纪律。

  4. CDMA的频宽可以传输影像,

  5.CDMA有良好的认证机制,可以防止被人截听以及防止盗打。

  目前CDMA系统正快速发展中,主要采行的公司集中在北美,我国也有相关的业务开展,预计CDMA为21世纪初无线通信技术的主流之一。

  WAP (Wireless Application Protocol)
无线应用通信标准协议,可以把网络上数据传递到移动电话或其它无线装置上。
WAP是由Ericsson、Nokia、Motorola等通信大厂在1997年成立的无线应用协议论坛(WAP
Forum)中所制定。使用一种类似HTML的语言WML(Wireless Markup Language),并可透过WAP
Gateway直接阅读一般的网页。WAP能令用户可以不需要通过笔记本计算机,简单的就能通过手机取得网络上各种资料。

OEM(Original Equipment Manufacture):原始设备制造商
应客户要求对某公司现有产品作局部改动的加工,不涉及机械结构、电路结构、软件功能上的重大改动,并要求将产品制作成客户的品牌,均属于OEM的范畴。


BMP Bean Managed Persistence

GIF Graphic Interchange Format

JPEG Joint Photographic Experts Group

JPG Joint Photographic Expert Group

PNG Portable Network Graphic











WUE 1



2006-03-09 17:23:19




供自己学习的一些资料来源:


?Dey Alexander Consulting 是一家澳大利亚的UXD咨询公司


http://www.deyalexander.com.au/


提供了近百种UXD相关资源,最近我将重点挖掘其中的“Design methods and techniques ”部分。


?The Usability SIG 的工具包提供很多测试可用性相关的模版,就是年代有些久远。

下载:http://www.stcsig.org/usability/resources/toolkit/toolkit2000.zip

主要内容是

计划和管理网站访问,

计划测试用户和招募新用户,

可用性测试的管理,

用户调查和反馈表,

可用性清单和启发式回顾,

可用性的目标和规范等。


关键字

WUE(Web User Experience)





GAME




  1. mmo 魔法战斗

  2. mmo 立体棋(大大扩大棋子的种类,3D)

  3. mmo 机器人篇(收集机器人资源<组装和升级>)

  4. mmo 战头宠物牧场(收集战斗宠物)

  5. mmo 步行机器人森林大作战

    玩家 数量不限

    玩家规则--

    玩家交互的形式 团队队抗

    目标 占领更多领地。

    玩法 超大3d空间上的跳跃(大立方体),fps

    规则 fps

    资源 hp,金钱、步行机器人、零件、燃料。

    冲突 时间><资源;敌人><团体

    边界 全3D世界。玩家只能在树干、树洞上移动(有些纵向卷轴)

    结果 无

    挑战 来自敌对阵营

    戏剧性 无

    假定--

    特性--

    故事 无

  6. 未知大陆的探索(未知大陆由大陆湖、大峡谷和大平原构成)

    玩家 数量不限

    玩家规则--

    玩家交互的形式 团队队抗

    目标 全大陆108个原始遗迹的发现;建立5个移民城市。

    玩法 类rpg

    规则 类rpg,参考我的rpg小结

    资源 血、财物、食物、武器、装甲

    冲突 最先发现所有遗迹还是更快建立移民城市。

    边界 未知大陆的边界(大海)

    结果 全大陆108个原始遗迹的发现 + 建立5个移民城市。

    挑战 来自敌对阵营,来自原始土著生物

    戏剧性 无

    假定--

    特性--

    故事 无

  7. 最近在看死亡笔记,这是个做社会游戏的好题材

    玩家规则--

    玩家交互的形式 多数人 VS 少数人

    目标 找到杀手或者无辜者全部被标记为death

    玩法 在一个全3D虚拟梦幻城市场景里杀手与普通玩家之间的战斗

    规则

    杀手需要具有执照,每个服务器的执照根据人口比例来调节。杀手可以获得任何一个他看到的其他玩家的资料,并选择是否将他标记为死去。杀手必须经常杀人
    (杀人的动作是否是手拿一个笔记本写写画画呢?),否则它的生命会很快消失。每杀死一个其他玩家,杀手可以获得一定的奖励时间。直到杀手杀死了标准数量的玩家,他就可以得到一次晋级。

               普通玩家可以知道现在全服务器有几个杀手,还剩余几个空执照。可以向法院检举杀手,被检举人的名字会被公布,其它普通玩家可以有10分钟进行投票是否处决这个杀手。一旦票数足够,此被检举人将被毁灭掉。一旦所有执照持有人都被毁灭。则诸位普通玩家得到一次晋级。
    被标示了死亡的玩家不具有投票权,但可以在被标示死亡的前30秒内具有说话的权利,此后将不得说话,成为幽灵。

    资源 --

    冲突

    边界 城市边缘、最低人口标准100+。时间和总人数限制呢?

    结果 每个玩家会有光明奖励和暗黑奖励两种分值>通向大奖赛之路。

    挑战 杀人和被杀,检举和隐藏

    戏剧性 无

    假定--

    特性-- 每个玩家的视线用探照灯效果来表现,这样玩家之间可以看到其他玩家在看什么。

    故事 无

  8. 惊吓,刚才在过道上对lilu试验了一下什么是“惊吓”。他反应还挺大的。瞳孔放大,头发倒立,面红耳赤......真是抱歉阿。

    玩家 全3D黑屋子游戏(参考天洙),人数8-16

    玩家规则--

    玩家交互的形式 团队对抗

    目标 将对方的"鬼"全部捕获

    玩法 采用恐吓、背后突袭、陷阱的方式使别的玩家晕眩然后捕获。

    规则 被恐吓的玩家如果在1秒内可以做出适当反应则有可能将对手反捕获等。(惊吓过度的玩家有可能暴走,而造成恐怖的无视型伤害)

    资源 精神、血、面具、衣服、变身药、陷阱、飞铙、布袋、藏身用纸箱子等

    冲突 --

    边界 屋内

    结果 完全捕获另一个阵营的对手玩家。

    挑战 --

    戏剧性 无

    假定--

    特性--

    故事 无

  9. call for S.O.S 参考ICO

    玩家 人数不限

    玩家规则--

    玩家交互的形式 小组间对抗

    目标 护送考古学家回到考古基地

    玩法 近距离手枪、轻冲锋枪fps + 偷袭

    规则 玩家可以选择三种职业 考古学家(不得使用飞铙等)、雇佣兵(高装甲)或者盗贼。考古学家将会随机出现在遗迹的某个秘宝附近,挖掘秘宝后,需要通过对讲机call顶多两名雇佣兵,并与之组队,雇佣兵将前来遗迹出口迎接该考古学家,并一路护送他返回考古基地。一旦一个小队出现在遗迹之外。所有盗贼就会收到情报并选择是否追踪该目标(同一时间一个盗贼只可以追踪一个小队,一旦锁定,则其他盗贼无权攻击该小队)。

    资源 hp、飞铙、 枪械、弹药、道具。

    冲突 --

    边界 大地图。

    结果 考古学家回到营地或者盗贼抢到秘宝。

    挑战 --

    戏剧性 无

    假定--

    特性(考虑,给考古学家一些低级战斗能力的情况下,可以考虑减少一名雇佣兵)

    故事 无




コメント (

0
)
| Trackback (

1
)









GAME









  1. 一个可以上班玩的简版游戏。游戏时间自由,有可持续性,可升级性需求。(北京浮生记?)

  2. 办公室踢足球?办公室这么大,踢足球……规则,踢倒最多的经理,得分最高。

    射门大赛,以每行空间上移动的员工脑袋为障碍物,能够穿过所有障碍射中经理办公室玻璃的为成功。射中每行中偶尔出现的工头也可以有奖励。或者做成2P、4P对射?

  3. 一直觉得很cool的是3D、漫画风格的大头fps大战。(重装松鼠?)

  4. 俏皮话。。。。对战。与npc或者另一个player游戏,一方提出俏皮话上半部分。另一方需要及时提供另一半。(悟空和界王)

  5. 无尽的华尔兹。对战游戏。不同的舞步会在地上留下不同的轨迹。不同的场景需要数种舞步配合才可以通过。除了尽快选择出需要用的舞步种类,还要把握节奏。游戏的目的是最先到达目的地,学习新的舞步。(wow里的小母牛,巨魔法师)

  6. 3d 卡通坦克或机甲fps大战,以攻城为大目标,消灭其他玩家为小目标。团队间对抗战。隐型单位、主战单位、远程单位、大面积支援单位、可累沙包修建简单工事。(坦克大战,thinktank)

  7. 弹球大作战,玩家操作3d主人公从空地上随机获得大小不等的铁球、玻璃球、能量球、耗子驱动球等。可推动球来碰撞其他玩家。目标是到达下一关的升级点。999关。(钢之练金2
    [ps2])

  8. 碰碰车大战。玩家操作的是3d小卡通车,以送外卖为生。通过撞击其他玩家的屁股部位可以让他失控转圈。争取自己尽快送掉更多的外卖。同时阻止你的竞争对手,就是目标。

  9. 你是天堂岛上的猴子,替主人摘椰子为生(3d俯视角)。猴子只能呆在树上,落地为出局。把摘到的椰子扔给主人算成功。掉在地上或扔给其他人算失败,扔给特殊游客,有可能获得奖励。
    猴子之间也会竞争。需要用弹弓、椰子为武器保护自己的领地。还要注意巨大蝙蝠和蜥蜴的骚扰。

  10. 上古的故事(纸上策略、升级游戏)。2000万年前之地球大陆。人类以村落为单位散落世界各地。每个村子10-50人。玩家作为村长,责任是带领他的子民与时间一起前进!以纸牌指派形式策划发展本村的政治、经济、文化、科学、放牧、种植和打猎技术。经济指数上升到一定程度可以组织远征队进行大陆探险。村落升级的要素是政治、经济、文化、科学、放牧、种植和打猎技术等要素达到一定程度,且需要满足一些特定条件,比如时间、任务等。游戏设计周期为1年以上(没有最高级设定,随游戏运营时间,最高级的设定也会变化)。

    这应该是一个每次游戏时间不超过两小时的游戏,其间主要是玩家安排他的村民工作。带领他们完成几次打猎、附近寻宝、祭拜(副本)之类的。
    mmo 即时策略 + rpg?

  11. 可穿越时空的mmorpg。(回到未来)

  12. 元素岛,以金木水火土为本质的精灵们慵懒地生活在元素岛上。直到那一天,来自岛外的空气元素大军登陆了元素岛,开始占据精灵的家园。结界被打破了,元素世界产生了通向人间的裂痕,两个世界开始混乱。主人公通过接界裂痕来到元素岛寻找混乱之源。最后,根源居然是一个找不到插座的电吹风妖精……

  13. 给rpg做个小结吧。除了目前mmorpg的多线任务方式,一个很cool的mmorpg应该有一个"特殊任务池"。由持有特殊执照的公司职员作为主持人来挑选池中的任务并安排任务的过程,主持人更可以动态的操作某些部分来影响游戏的参与者。比如控制boss的动作等。对于主持人的评判标准可以参考其任务的参与人数或由任务带来的虚拟货币收入等。

  14. 写到14楼的时候才看到高达online的报道。看来提督类的策略游戏在internet上也是有可能的。本楼的设定是以宇宙大开发年代为背景的策略游戏。以选择指令为游戏方式,合理的支配资源,使自己的空域和行星数最大化。小型即时战略系统:一个二维的战斗场景。一个空域可参与的团队不超过4个。

  15. 实时策略战斗系统1:舰队队形,方向,探索,攻击,合击,支援,补给,撤退,自动投降等。作战的奖励将是该空域的归属、当地的资源、补给、修理平台和资讯使用权。

    实时策略战斗系统2:战前指派舰长、可使用资源和战舰以及作战方针。由系统自动计算战斗结果。

    实时策略战斗系统3:战前指派舰长、可使用资源和战舰。战时负责控制舰队队形、方向和攻击形式。

  16. 泡泡装甲,很厚很夸张的半透明装甲。可以有三种类型:

    通用效果是高防御力高敏捷性,缺点是制造、维护费用很高。

    1)果胶型,受到冷兵器攻击产生水效果,兵器会像在水里一般速度变慢,只能缓慢的刺入。穿戴者所使用的武器如果具有魔法攻击性,这种魔法效果将会被该装甲学习,并随机向攻击者发射。

    2)强化型,受到冷兵器攻击,首先外层超薄保护层会吸收部分伤害,破碎后,里面一层具有钻石效果,不但可以反射魔法,还会反射一些武力攻击。此层破碎时会产生爆破效果,周围的敌人将受到碎片伤害。最里一层具有魔法增益效果,使用者可以将法力或能量转换为防御值直到精疲力尽(有可能带来无法行动负面效果)

    3)空气型,看起来非常柔软,像会随风飘动的大泡泡。其实它的外层为数层向外爆破层并附有天使之羽,攻击者会受到连续的爆破反击,而穿戴者会有很大的机会避开外来的伤害。装甲中含有反重力元素,使穿戴者敏捷性提高。


  17. Turning Ideas Into A Game



   Review Formal elements


 



  • 玩家:描述玩家在GO FISH和QUAKE中交互作用的不同。


 



  • 目标:列出五个游戏。一句话描述他们的目标。

  • 规则:能否描述一个没有规则的游戏,描述它。一条规则如何?

  • 冲突:比较足球和扑克中的冲突。描述他们如何为玩家制作冲突。

  • 挑战:列举三个具有挑战性的游戏,描述原因。

  • 假定:列举几个游戏的前提(强手棋假定玩家都是地主)

  • 故事:游戏内的故事曾经牢牢吸引你,情感上感动你或者激发你的想象力么?如果有,为什么?如果没有,为什么?


 




  • Formal elements of game design




 



  • 玩家

    • 玩家数量

    • 玩家规则

    • 玩家交互的形式



  • 目标

  • 玩法

  • 规则

  • 资源

  • 冲突

    • 障碍

    • 对立

    • 两难



  • 边界

  • 结果


 




  • Dramatic elements of game design




 



  • 挑战

  • 戏剧性

  • 假定

  • 特性

  • 故事




P.K.M
2005-09-23 18:38:01

写字板仍然是我用的最多的数据记录工具
一个与notepad启动速度一样快的写字板,提供在线增值功能(notepad+online entertainment)
一个dx的写字板,cool背景 比如热带鱼鱼缸?日本人做的鱼缸不错。
字大网,40+的人眼睛都不好,20px+的粗体圆滑字体,深色的背景,无闪烁的画面才可以看清楚,看起来也不累 → opera的网页放大功能 + 参考数字电视的EPG字体设计规范。
经常有人告诉我一些错误的网址。其实可能是其中某个字母出了错误而已。一个自动修正功能应该不会太难。(参考google “您找的会不会是xxxx" 功能)
快速轻量级论坛。一个徒有几十万注册用户。发言的却就那么几十个。帖子也就那么几百个。有什么用呢。社区应该是人认识相同爱好的朋友的地方,不说话怎么交流?!所以一个快速轻量级论坛应该是这样的:(思考来源:ftp论坛和成人论坛<人数增长加速度最快的论坛类型>)
专一:探讨的话题主要集中于一个特殊的话题,或只面向一小类人提供专门服务。
快速:没有那么多花哨,没有javascript。
活跃:绝大部分用户都必须是活跃用户。
公开:一旦成为member,必须强制公开相关个人信息。
新鲜:通常情况下禁止楼主转载。
方便:方便发帖和方便被google到它的主题。
稳定:年终无休的提供服务。
监督:斑竹必须有维护、发展上述特色的能力。
按照btchina做一个微型web2.0客户端软件。实时显示每个被下载物的下载数。会员收费尽量采用0坏账的方式比如银行卡。(参考物:青娱乐)
"The people" 个人信息包,个人上网的唯一标识。很自由的进入各大网站。但是这会不会是一把锁呢?
"The people" 一个完全专栏化的社区。可以展示mov, music, picture, text ,只要有内容就可以。入口在形式上应该看起来像一个超巨大的相册本。
commnet: 中国online shooping report2004
2005-03-10 12:47:14
艾瑞 中国网上购物研究报告 读后感1 4000元就算是高收入。(500$就算高收入了。)2 在没有计算网上服务购买的情况下(游戏、其他internet服务),53.3%交易是购买数码、IT产品。3 购物场所上来看,用户访问、购买排名第一的是卓越,新浪商城第六。没有看到三大拍卖网站?4 2004年交易总额6.8亿,市场交易规模45亿。为什么没有考虑网上服务?不算购物么?如果要做网上实物销售,IT产品可能是第一选择。

idea 1~photo's shop

2005-03-02 16:19:58
项目的名称应该叫"网上数码相片冲洗服务"之类的名称。前言面向 对冲洗时间和质量、数量和价格都有高要求的用户群,他们可能来自(*)公司和购买了数码相机的家庭。服务的流程如下:.服务提供商通过建立一个在线平台从网上接收用户的数码相片。.在线平台自动计算冲洗价格。.用户在线向服务提供商支付金额。.服务提供商负责在规定时间内冲洗相片并送达用户手中,完成点对点的冲洗服务。用户所能享有的价值是:! 不必出门找冲洗店! 统一的质量市场调查-〉去年到今年的数码冲洗的总量和趋势。-〉什么人群喜欢数码冲洗?公司还是年青人更多呢?-〉数码冲洗的平均受理时间-〉数码冲洗的市场价格-〉数码冲洗和传统相片冲洗的优劣比较实施方案一建立平台idea的来源:英国邮寄胶卷+money,backphotomsn的photo打印服务

星期一, 六月 14, 2004

韩国游戏工业资料

韩国软件振兴院 中国代表处
是韩国情报通信部下属的非营利性事业单位,主要职能是促进韩国软件产业的对外合作。
地址:中国北京朝阳区建国路乙118号京汇大厦20层
邮编:100022
电话:8610-65669973
传真:8610-65669705


韩国游戏产业开发院
原名“韩国游戏支援中心KGPC”,是韩国文化观光部下属非盈利组织,其宗旨为:通过整个游戏产业和游戏推广中心的发展,将游戏产业发展为一种重要的文化产业,从而使韩国游戏产业开发院成为韩国游戏产业发展的核心原动力。

韩国游戏公司名录1-1
韩国游戏公司名录1-2
韩国游戏公司名录2
韩国游戏公司名录3
韩国游戏公司名录4

星期一, 五月 24, 2004

日本游戏公司名称由来以及含义出处

  Sony:最早来自拉丁字Sonus,意为“声音”,听起来很上口,刚好又同所从事的行业关系密切。当时,日本已使用外来的英语了,有很多人叫可爱的小男孩Sonny.这个拉丁字相关其他字,不管是Sonny或者Sunny(阳光普照),都有乐观、光明、积极的含义,这点非常符合建立健康的企业形象。美中不足的是,Sonny读起来与日本字“输钱”谐音,有些“触霉头”,后来盛田昭夫灵机一动,去掉一个“n”,拼成“Sony”。

  SQUARE:square是公司开在日本四国,英文四国和英文square发音象,就用这个了吧!

  ENIX:enix是凤凰phoenix的结尾和世界上第一台计算机eniac组合的名字

  KONAMI:konami这个名字的由来是四个创始人的名前字母KOuzuk(上月),NAkama(仲真),Matsuda(松田)和Ishihara(石原)组成的

  CAPCOM:CAPCOM创立于1979年,在当时名为“I.R.M”,是一家电器零售商。1983年公司正式更名为CAPCOM,由于在当时任天堂的FC造成日本游戏市场大繁荣,CAPCOM将其业务方向转为软件销售。CAPCOM这一名字的由来是“CAPSULE”(胶囊)和“COMPUTER”(电脑)的合成。

  SNK:SNK全名为SHIN NIHON KIKAKV,意思为新日本企画(新日本プロレス),成立于大阪府吹田市。

  NAMCO:Namco是前身中村研究所NAkamura Manufacturing COmpany的缩写

  SEGA:SEGA名字来自SErvice GAmes

  JALECO;Jaleco是Japan LEisure COmpany

  任天堂:“堂”日本古老公司的称呼,所以就本意言之,任天堂就是任天公司。而“任天”呢?其代表意义可解释为“将幸运交给老天”或是一般普遍的说法:尽人事、听天命好像来自于这句话,人生一寸先は?、?は天に任せ、与えられた仕事に全力で取り?むjun4rui翻译成:谋事在人,成事在天

  HUDSON:是工藤兄弟表明自己的志向如同美国那条充满野心的HUDSON河而命名的!

  ATLUS:Atlus的名字来自于于希腊神话中的擎天神Atlas

  BANDAI:最初叫做?代屋,源自我国古书《六韬》中卷三‘龙韬’中“宫、商、角、徵、羽,此真正声也,?代不易”的‘?代不易’这句话,取其中‘永恒不变’之意。

  KOEI:取的是“光荣”二字中荣誉,荣耀之意。

星期五, 五月 21, 2004

游戏开发基础

游戏开发基础(1)



第一节. 概述
  随着计算机技术的发展,计算机从高技术的神坛上走下来,走进我们的家庭,走进我们的娱乐生活中。在家用计算机的使用中,作为游戏机的使用率是很高的。同时对于游戏迷来说现有的游戏中总有很多不足之处,让人想一试身手作出一套更好的游戏(不过,这可不容易)。作为和亲爱的用户您一样的游戏迷,我也有相同的想法。这样便产生了这套游戏编程基础的教学软件。

  这套软件主要是面对有一定编程基础的用户,但考虑到很多游戏迷在Windows编程方面还是个新手,所以这套软件中还增添了一章“Windows编程基础”。在基于Windows的游戏大部分都是支持Directx的,故我用了较大篇副讨论了Directx 5.0,并使用基于Directx 5.0的例程来讲解游戏编程的思想和实现。由于,Directx 6.0中的很多在功能只支持VC,而且在诸多高级语言中C是最适合游戏编程的,所以这套软件中的例程及讲解都是基于VC的。相信,用户通过这套软件的学习将能有信心编出使自己满意的游戏来。

  自己满意的游戏是否是大部分人都喜欢的呢?是否能成为市场上成功的游戏呢?为此在本章的下面几节中我们将讨论一些非技术的关于游戏总体设计的话题。

第二节 电脑游戏的发展史
60年代早期
  使用最原始的大型计算机,一小部分程序员开发游戏而其他人认为他们在做研究。在MIT的实验室里开发了Space War和其它早期的游戏。
1973年到1975年
   Nolan Bushnell建立了Syxygy。在市场上出售SpaceWar的“Arcade”版,但失败了。在1975年下半年,推出了Pong,并使之成为一个非常流行的游戏,之后,他重新命名公司为Atarh他们始创了我们今天所熟知的游戏工业。
1960年后期到1970年早期
  更多的程序员使用大型计算机开发游戏;WilliamCrowther开发了流行游戏Adventure。
1976年
   Rushnell以$2,600万将Atari出售给WarnerCommunications。

   1980年其它游戏系统依次登台,象Pllillins的Oddessy和Mattel的Intellivision,目标都是希望取代Atari。一些小的个人计算机开始出现,但视频游戏仍处于领先位置。

1977年
   Atari推出了Atari2600VCS,家庭视频游戏行业兴起!一个名叫Apple的小公司出售一种计算机Apple l,但没能引起人们的注意。
1979年
   Atari公司的一些项尖的程序开发员从对Atari的意见不一致发展到公开的不满,成立了自己的公司Activision,其它“第三方”开发公司相应出现。
1981年
   IBM推出了IBM PC。
1982年
   Atari开始滑坡,视频游戏市场处于大萧条状态。Atari的主席Ray Kassar宣布销售已下跌了50%。个人计算机应时出现,许多新型计算机游戏开发者纷纷出现,其中有些成为了专业开发队伍,新出现的知名公司包括Sierra On-Line 、 Broder-bund、Synapse、Sirius及Strategic Simulations。
Electronic Arts成立,并成为现代游戏公司的楷模。
1984年
   Apple推出新型计算机产品Macintosh,但该系统缺点是启动馒,此时该系统的购买者和软件开发者的主要目的不是开发游戏。
   1983年伟大的视频游戏跌至最低点,Mattel宣布其Intellivision产品损失$22,500万。
1985年
   Nintendo由于投入一种新的视频游戏产品?任天堂娱乐系统,而重新繁荣了家庭视膝游戏市场。16位的计算机的革命继续,Atari的产品Atari ST投入市场是对CommodOre一个沉重的打击。
1986年
   CommodOre极力宣传并推出Amiga计算机,该计算机是由Atari硬件设计师Jay Miner设计的,最初是想作为下一代的视频游戏系统,但由Commodore投资的这种机型却成为机器Commodore64的继承者。不幸的是,巨大的市场打击和其它的开发埋没了该系统的远大前程。
  Sega推出了Sega Master系统,在技术上要优于Nintendo Enter。tainment系统。由于Sega忽视第三方开发者,没有征集足够多的软件支持该系统,所以没有取得足够大的市场支持而失败。
1988年
  计算机行业从8位计算机转向16位系统,新出现的视频游戏控制台引起了新的合并,Cine。maware、Epyx等公司处于困难时期。
1987年
  游戏变得更复杂了,更多的公司转向集体开发,Electronic Arts推出了第一个集体开发产品“Skate or Die1"
IBM PC系列,由于具有了好的图形适配器,开始成为可采用的计算机游戏平台。
1989年
  新的16位系统初次登台,最值得注意的是Sega的Genesis。当Sega由于广告宣传和大量的EA运动游戏,占据控制台市场的领先地位时,任天堂(Nintendo)觉悟已晚,损失惨重。
1990年
   Amiga和ST在市场上基本灭迹,PC系列和控制台成为主要的游戏开发平台。Electromc Arts开始成为主要的游戏开发和发行者。
1992年
   PC游戏开始流行,在它迅速发展的几年里,其它一些计算机游戏平台被淘汰,把PC游戏市场推向一个新高度。
1991年
   Nintendo的产品Super一NES首次推向市场,16位之战的全面爆发促进了许多控制台系统的销售。
   1993年Pentium芯片出现,Microsoft预先展示了它的新产品Windows操作系统,代码名为Chicago。尽管PC迅速发展,Sega和Ninter1do继续处于领先位置,控制台系统占领了世界游戏市场的80%。
1994年
   Panasonic推出了Real-3Do游戏机,预示着32位控制台系统的出现。
   Atati推出Jaguar 32位游戏机。但这两个产品(尤其是3DO)都不很成功。
  Id Software推出了Doom,使人们意识到可以使用共享软件发行方法。
1995年
   Sega生产了Sega 32位控制台系统。Sony推出了Sony Playstation 32位控制台系统。
  Microsoft推出了Windows 95和windows Game SDK,使得大量的游戏开发转向Windows乎台。
  Internet和Word Wide Web流行,大量的用户上网。
1996年
   Nintendo推出了UItra64。大范围的多人游戏已经出现。多媒体、3D和虚拟现实等取代WWW而成为热点,主要的新技术和产品包括Java 1 ShockWare、JavaScript、Netscape 2等等。
1997年至今
   Iintel推出MMX技术,并基于此推出Pentium MMX和PentiumⅡ处理器。AMD和Cyrix也基于MMX技术推出了K6和M2处理器。游戏开始大量使用MMX技术。PC与其他的控制台系统在性能上已相差不大了。
  AMD推出3D-NOW技术,使其基于3D-NOW的处理器K6-3D在图象处理的方面超过PentiumⅡ。

第三节 游戏的组成要素
  游戏设计涉及一个置于所有其它要素的中心的东西??这就是交互性,使游戏从其它的创造性的媒介(如艺术、电影、音乐和书等)中分离出来的这一要素可以在游戏者同游戏的交互中体现,对于游戏这种媒介的驱动力量来自于游戏者的决策,换句括说,是游戏者的行为,而不是媒介本身。你不要仅仅看或仅仅听一个游戏??你应该控制它。设计者必须创造一个诱使人们去玩的产品,同时还要提供故事线索、情绪感染、真实的声音和其它创造性的媒介给予的特性。这是比较高的要求,但是这就是使游戏开发如此有趣的一个原因。

  玩游戏就意味着决策。因此,我们的游戏需要创造让游戏者不得不决定做什么的情境,这样,他或者她才能执行所要求的行为。在一些情况下,决策越具有挑战性,这个游戏就越具有感染力。此外,游戏者越能够影响游戏的结果,这个游戏就越能够吸引他们参与。

  当坐下来设计一个游戏的时候,要努力创造有趣的交互,还要努力在创设的情坎中为游戏牙提供作出决策的容易的方式。然后,提供将进步引出新情境的有趣结果。这个完整的过程要一进一遍地进行,直到取得最终的结果。

1.影响结果
  交互性的一个最重要方面就是游戏者能够影响游戏的结果,并对结果具有最终的控制权,一个游戏者坐下来玩游戏时希望能够在过程中有所进步,或者能够挽救世界,或者能够达到最高的等级水平,因此,在设计一个游戏时,你必须一开始就让游戏者明白,他或者她能够对结果改变到什么程度。当然,你不需要预先告诉游戏者他们能够改变什么结果,或者他们怎样改变它;你只需让他们知道:他们的确能够影响结果。游戏设计者用于创建好的游戏的一个重要概念叫做多重结果。多年以前,大部分游戏只提供两种结果??成功和失败,游戏者结束了这个游戏就是成功者,否则游戏者被消灭。然而,许多现在的游戏,尤其是交互式故事或者RPGs,就可以有几个完全不同的结果,这些不同的结果可能让游戏者进入完全不同的新的冒险,或者进入新的交互式故事。例如,如果你正在玩一个战争游戏,而且,你在某一场战斗中成功了,你将可能获得军衔的提升,到上尉、少将或将军。

注意:在设计游戏时,给予游戏者通过他控制游戏来决定的各种各样的结果是十分关键的,只有这样才能让人们花时间去玩。

2.成就的角色。
  玩任何游戏的根本目标都是获得什么东西,不管怎样设计,在任何游戏中成就是一个基本要素,当然,成就有许多不同的形式。可以简单到是打败一个对手、得高分或达到较高的等级水平。当你在游戏中设计成就因素时,可以在许多不同的水平上进行,你可以提供多重的成就目标;你也可以提供渐进的成就水平,例如你可以在每隔三个等级之后的等级结尾以不同的“主要怪物”作为特殊对手,在整个游戏的结尾则以“最大的物”,作为对手。

  不管你的游戏提供了什么形式的成就,成就是游戏过程最主要的转折点。当然,成就并不一定意味着赢,还应该是一个游戏逐渐走向最终结果的自然的前进,一些游戏提供了实际的结束作为成就,还有一些游戏成就是绝对的成功(特别是体育游戏),如赢得一场比赛或获得一个冠军。但它们也可以有不同的成就等级。

  重要的是游戏中成就的获得不应该有大的跳跃,而应该是进行一定的时间后发生的渐进的过程。另外,我觉得一个游戏应该有3/4的潜在游戏者能够获得100%的成就水平,毕竟,游戏不能因为太难而被认为违反一般潮流,游戏者喜欢的挑战是那些可以征服的挑战。

  人类的本性就是希望获得成就,如果人们去做一些不能获得成就感的事情,这是不符合人类本性的。这就是说,不要设计一个游戏,而又故意过早发布提供线索的资料。

3.失败的角色。
   “游戏结束(Game over)”大概是计算机游戏带来的最不好的一个短语,在这以前,基于竞争成绩或者简单成就的游戏都包含有各种各样的失败程度。

4.改变情景
  许多游戏允许游戏者控制或改变玩的情景或者参数。游戏者经常喜欢或需要修正游戏的可玩性,这不仅包括简单地改变他们能够拥有的“生命(lives)”的数量,而且包括改变一个战争游戏中的所有数字、参数。

通过改变情景,设计者应该提供游戏者如下特性:

a.修正游戏难度的能力
b. 改变玩的环境的能力
c. 修正等级水平或游戏角色的能力
5.问题解决。

  我们已经花了一些时间讨论成就和失败,这些原则的具体例子可以在问题解决中发现,而问题解决又经常与“智力难题类游戏(puzzles)”相联系,而且也是其它类游戏包括冒险游戏、RPG、策略游戏中的关键要素。

  问题解决是给游戏者清晰定义一些挑战,然后通过解决问题的方式来进行交流。这佯,游戏设计的一个主要工作就是创建一些有趣的和富有挑战性的问题,这些问题具有符合逻辑的解决方法,使游戏者能够最终通过玩游戏解决问题。如果方法太简单,游戏者将迅速完成,然后再去玩其它的一些游戏(也许,这些游戏还是你的主要竞争者)。另一方面,如果解决问题的方法太难,游戏者可能由于挫折而放弃。

  这听起来容易,但是,事实上许多游戏对于游戏者仍就具有一些不合逻辑的方法,或者问题太难,游戏中间题解决直接与游戏的成功或失败相联系,人们不介意失败的危险(这是挑战的一部分),但是,如果不可能获得成功或者根本就不可行,这个问题就是无法解决的,也就不再成为问题,而是一个绊脚石。

  在游戏中一个与呈现问题相关的有趣的事情是创设问题情境。你创设的问题应该让游戏者把已有的知识运用到问题解决中来。许多游戏的问题解决知识仅仅限于游戏本身的知识,对于大多数游戏来说这是不错的。

  然而,还有许多游戏,尤其是冒险游戏或者“交互式故事”,你可以表现真实世界中的智力难题,一个简单例子是这样一个古老的智力难题,一个拿着一块木板和一块石头的人被陷在一个巨大的坑里,还有一股水流向坑里,然后流到排水管里。游戏可以拿着石头来堵上排水管,当水填满坑以后就可以用木板漂上来。这是一个非常简单的例子,但是当你设计游戏时,尤其是你想对那些并不是劲头十足的游戏者构成挑战,就应该考虑运用这类智力难题。

  不论什么时候,都要努力把游戏者变成问题解决过程所不可或缺的部分,这样才难把游戏者吸引在你的游戏上面。这方面做得较好的一个例子是slmCity。

角色扮演和进入其他世界

  大概计算机和视频游戏的最基本的感染力就是能够逃避现实。大部分游戏是角色扮演游戏,把游戏者置身于一个想象的世界或情境中。玩游戏的人具有与看电影和看书的人一样的原因:人类具有想象,而且大部分的人需要给他们的想象以刺激。电影和书这么做了,而游戏又更进一步,允许游戏者实际地参与。

  以游戏设计为立足点来看,这就意味着你的一个重要的职责就是要给游戏者传达一个思想,通过它游戏者能够进入另一个世界。在这方面游戏设计就需要创造不同的气氛。不论它就仅枪战游戏那样简单,还是和Broderbund的新的法庭素材的产品那样复杂,最为重要的是,其中的一个目标就是创造一个世界,游戏者被假设为里面的一个角色。

6.幻觉状态(Suspension of Disbelief)
  我们刚刚讨论了进入另一个世界和人的想象力的独特本质,这种思想的最终体现就是在游戏界常听的一个术语“幻觉状态”。当游戏设计者谈到“幻觉状态”时,他们描述的是一种“想象的状态”这是指游戏者的意识融人到游戏世界中,这样,他或者她就不是在玩游戏,而实际上在体验另一个世界。

7.个人经验
  游戏设计的另一个核心因素是游戏者通过游戏所获得的个人经验。通过试验,我主要注意到游戏者在玩游戏时的三个核心的个人经验成分。

1.有趣
“有趣”是一种主观体验,只有试着玩了以后才能决定产品是不是真的有趣。而且,游戏只是对于将要玩的人是有趣的,如果你专门为女士设计了一游戏,就不必要增加一些对年轻男孩子来说有趣的因素。
2.学习
我想并不是所有的游戏都必须是:纯教育意义上的一个完整的学习经验。相反,我想所有好的游戏都要求你去学习以征服这个游戏,包括在游戏中学习对手的弱点和寻找智力难题解答案。在这两种倩况下都需要你有计划地设计好这些活动。如果想让游戏者学习计算机的弱点,你就应该有意地计划这些弱点是什么。
3.探索
超越有趣的最重要的因素是探索:现实的个人经验。我们去看电影或读小说的一个主要原因是:逃避现实。游戏通过把令人震惊的图形、人工智能、立体声音和狂野的想象一起融合在游戏的交互要素中,最终给人们提供了逃避现实的机会。我认为其中最重要的是探索原则。这就是游戏,当你玩它时,就逃离了你存在的世界,而假设一个生命在探索新世界。探索是一个关键的要素。最为重要的是,当你建构一个游戏时,在本质上你就在探索一个新世界。”
8。还有什么
除了这些基本概念,游戏设计还有更多的东西。游戏设计是非常主观的,到现在,你仅仅了解了游戏设计思想的一部分。就像任了何好学生一样,你应该一直寻找和探索以扩展你的知识。
此外,还应该懂得所有的游戏开发的材料并不仅仅来自于阅读有关游戏设计的书和玩其它的游戏,前面我所提到的许多开发者都往往从其它方面吸收新东西。
游戏设计是信息时代的复兴艺术,一个真正的复兴艺术家是一些万能博士,他们从不同的渠道获得大量的思想,超越了少量特定的游戏设计材料来寻找有关游戏的设计思想和灵感。


第四节 游戏的细节设计
  定是截然不同的。我所要求的那些内容可能与人们所的喜欢玩的内容不一致,从而导致人们认为 这个产品没有娱乐价值。

  当然,其中一些是市场决策,你必须尽早决定你想达到的市场方面的参数。并且应该提前考虑技术和实现的问题。生产一个要让我喜欢的竞选游戏,需要先进行大量的研究并整理大量的统计数据。

  在设计你的游戏时,你必须预先确切地决定要用多少细节。一些游戏设计者把细节放在最需要的地方。例如,考虑一个潜水艇模拟,如果你想使一个区域非常详细,该是哪个区域呢?当然是“用鱼雷袭击的部分”,你可以把潜水艇在搜索攻击目标时的运动设计得简单,但是,一旦你发现了船只,游戏应该能够允许你最大限度地参与鱼雷攻击的过程,因为这正是潜水艇游戏的焦点。?

1.什么时候一个任务会变得象日常琐事?
在一些情况下,如果一个游戏变得相当真实,就可能导致游戏中的任务成为一些日常琐事。我记得的有这么一个游戏,这是一个令人难以置信的模拟游戏,让你驾驶F?16战斗机。这个模拟是如此真实,甚至要求游戏者去完成一个真正的飞行员在起飞之前要作的许多任务,这些任务是如此之烦琐,致使游戏的娱乐性大大减少。这方面的缺点在一些RPG游戏中也可以发现,与他们在真实生活中的所作所为相比,其中游戏者可能发现他或者她花更多的时间在为别人跑腿。当然这并不是说这些游戏不好,上面谈到的游戏就是一个非常让人吃惊的精确的模拟,许多模拟游戏迷们都喜欢这些精确的细节。但是,应该明白许多游戏在写实方面做得太过火了。
对我来说最大的问题是什么时候“任务”变成了日常琐事。尽管我能够看到这些细节对游戏写实的贡献,但是,在我看来,它们更多地是玩游戏的障碍。所以说,这就是一个娱乐性和写实主义之间的难问题。最优秀的游戏处理这个问题是通过增加一些可控的设置来让人们控制这些写实主义的因素,设置的值越高,则游戏就变得越真实。
在游戏开发的过程中测试模拟游戏是十分重要的,要看人们对于你设置的任务的反应,如果你的任务太简单或者太复杂,你可以在测试阶段不断地进行调整。

2.“任务瓶颈”
在你的游戏中寻找“任务瓶颈”。任务瓶颈包括两种类型:必须的和隐藏的。必须的任务瓶颈是最糟糕的一种类型,它们是这样一些活动:游戏要求你一遍又一遍地执行以取得进步,这种类型的一个例子可以在冒险游戏中发现,其中增加了一些赌博的因素作为一种让游戏者增加钱财的方式,在这种情况下,你可能花一整天去玩各种各样的愚蠢的赌博比赛,其唯一目的就是赚钱。这确切他说还不是娱乐,因而对于游戏的真正目廊来说就是一个瓶颈。
隐藏的任务瓶颈是那些允许游戏者花大量的时间重复地作些事情以获得策略性优势的程序流程,甚至有时设计者并不是有意这么做。
最后一关是评测产品,看看在什么地方游戏者发现了他们不喜欢的重复性任务或者发现其他各种各样的问题。大部分问题都比较好解决,以避免让游戏成为一系列“日常琐事式的”重复性任务。

3.在游戏设计中运用市场研究的结果
许多情况下,你的设计需要进行适当的改变以满足市场走向。当你设计一个商品化的游戏时,重要的是要记住你必须最大限度地满足市场,而不是为了取悦于你自己或者参与游戏开发工作的其他人。你自己不去购买你自己的游戏,而是其它人购买,他们对你的游戏有和你不同的期望和要求。
当把市场因素综合到你的设计中时,第一步是了解你面对的市场,这是一个本质问题:“谁是这个游戏的一般购买者?”然后,努力确保你充分考虑他们的要求。例如,让我们设计一个深海潜水游戏并考虑相关的市场因素:
游戏概要:深海潜水:这是一个关于深海潜水和去寻找埋葬的金银财宝的游戏。目标是在水下的冤死鬼抓住你之前发现你能够找到的所有金钱。
市场:喜欢潜水的人;喜欢冒险游戏的人;喜欢海洋的人。
这里我作了一般性的假设,例如,这个游戏对于喜欢冒险游戏的人具有吸引力。这样这个游戏需要一个详细的故事??一些冒险游戏的爱好者的基本要求。对于吸引喜欢潜水的人,我们最好确保给他们一个有关深海潜水的令人心眼的模拟,因为既然喜欢潜水,可能就是这方面的专家,不会轻易被愚弄的。
这里的意思是:在你设计游戏和推断你的潜在对象的大概情况时,要按照他们的要求来调整你的思想和设计,这将不时地把你的游戏引向不同的方向。也许你最初的设计不能算是一个故事,只是一个潜水的模拟,然而,冒险游戏的市场总体上却是很大的,因此,你将发现你自己被迫增加了一些详细的神秘而有趣的故事。
在今天竞争激烈的游戏市场,许多游戏公司都在努力创作一些能够让更多的人们想玩的游戏。他们肯定都正在把市场研究和市场驱动的设计整合进来,用以帮助创建人们想要的软件。

5.满足“铁杆儿”游戏者??正面和反面的意见
在你设计游戏时,你应该特别考虑一个最重要的游戏群体??铁杆儿游戏者。高技术市场学说告诉我们,一个最重要的规则就是满足那些游戏高手,他们也会反过来产生帮助销售产品的热情,他们可能把产品推荐给其他游戏者,并促进他们的购买决策。
当然也要明白一点:你为游戏高手制作的游戏很可能对于哪些偶然玩游戏的人或不是高手的人来说很没意思。例如,人们可能不喜欢一个花400小时才能完成或者拥有100多种坦克的战争游戏,相反,他们可能喜欢一个真正好的、线性定向的游戏故事,或者一个关于人们之间友谊的游戏。当然也有折衷的办法,但是越来越多的开发者却在作在铁杆儿游戏者要求以外的很多事情,实际大部分游戏都这么做。
这里没有真正正确或错误的方法,在你设计一个游戏时,你应该描述谁是潜在对像,对于这个问题从两方面考虑是很重要的。如果你与其他喜欢游戏制作的人一样,就不太好办了,因为99%的热爱制作游戏的人他们本身就是铁杆儿游戏迷。因此,对于许多游戏设计者来说,必须有意识地考虑这些问题才不致于使他们按照习惯去设计制作游戏。
如果你想真的突破铁杆儿游戏者所喜欢的游戏类型,就不要在游戏中加入一些会让铁杆儿游戏者和评论者注意到的一些关键特征。当支持调制解调器(Modem)fo网络的游戏在市场上屈指可数时,有较大影响的评论家和铁杆儿游戏者会特别注意这类游戏的出现,这些人的挑剔和批评可能让你的游戏变得一钱不值,而如果不让他们注意到,你的游戏说不定会流行。
总之,你应该懂得如何满足游戏专家所喜欢的基本要素和特征,然后,有目的地决定在哪些地方实施这些特征。这里的关键是“有目的地”,不要猜想和假设,决定特殊的需要是什么,然后你就尽你所能去创建能够满足铁杆儿游戏者的游戏,也许同时能够吸引一些游戏高手之外的玩家。

第五节 对手智能设计
人工智能是个错误的名称,特别是与游戏联系起来时。我们真正的目的是要使用一些技术在游戏中加入“人”或类似智能的特征。这种技术越聪明越自然,它就越会被游戏者承认。我想对于这种技术更好的名字是“模拟智能” 大多数的游戏不需要非常前沿的技术,如神经网络。(而少数使用此种技术的游戏也都是AI专家们感兴趣的那类,如国际象棋等等。你从没有见到哪个打斗类游戏使用“神经网络”技术。)对于游戏中的AI我比较喜欢这个定义:
一个非游戏者控制的对象在基于各种复杂因素时的决策行为就象时由真正的人作出的,这是通过使用 一个决策算法来完成的,这个决策算法根据设计者确定的规则和提供给程序的信息进行处理。
这定义中,使用了三个需要进一步解释的术语:

1.决策
一个“智能的”对象通过抉择来决定它的行动而不是随机地动作。你可以把随机性作为强调某一个特定的决策的权重,但最终的决策是在至少两个可能的结果中抉择产生的。对游戏者行为预测得越准确(包括更深层次的反应),对游戏者来说这个游戏就越具有智能。因而重要的是决策必须模拟人的本能反应。它一般由可观察的信息组成,这些信息包括外部、内部的信息,例如敌人的数量、角色的肚子是否饿了等等。

2.多因素
作为人类,你知道对一件事作出决策需要考虑许多因素。举个例子来说,到哪儿去吃饭就是一个颇费周折的问题,你是开车去还是走着去?是和别人一起去吗?想吃什么菜?花多少钱合适?如果时间紧张,是否去吃快餐呢?要不要预订?……
使一个物体具有智能也要考虑很多因素并能迅速作出决策。这些因素决定对不同的情况有不同的反应。你的算法能支持(或处理)的因素越多,你创建一个真实的环境的可能性就越大。例如,你要定义一个战争游戏中敌军将领的决策行为,你可以让他们根据军中坦克、飞机、舰船的数量来做出生产军备力量的决策。另一方面,你应该引进其它因素。例如,这些敌军应该考虑一些军事基地建在岛屿上,所以要多生产舰船,少生产坦克,他们也要决定是进攻还是防守。你也应该加入一些感情因素。例如,一个好战的将军会在处于劣势时仍旧进攻,并生产进攻型武器:飞机,而实际上埋智的决策是应该防守并生产防守型武器,如坦克等,记住,一个“智能的”决策没有必要是最好的、最具有理性的决策。
所以当你试图模拟智能时,先确定用于做决策的信息(多因素)。同样要考虑决策者评估信息时所具有的个性。

3.规则
你也要确定在什么情况和规则下,一个对象可以获取作出决策所需要的信息。例如,在战争游戏中,计算机控制的对手是否可以观察全局,或只能得到本地信息?如果允许对手观察全局而不允许游戏者这做,那计算机就在作弊。但由于人类玩家实在太聪明了,就扯平了。
19.1.3重要的是表现在游戏者面前的是什么
当然,AI的目的不是创建一个在“作弊”方面表现良好的系统。但在游戏中,用“作弊”是使一个对手具有挑战性是最好的方法。不过这样做要小心,因为一旦游戏者发现计算机在“作弊”,他很可能再也不玩这个游戏了。
对于大多数AI游戏开发者来说,他们的目的是创造出一个最好的计算机对手,这样做的结果就是:他们创造出的敌人太强大了、太聪明了。这就引发了一些矛盾。首先,游戏应该让大多数游戏者能成为胜昔。Al开发者可能在努力创造一些能打败游戏高手的游戏敌人,但是我作为一个游戏者时,我不喜欢这样的游戏。我只希望我的计算机对手在我不认真玩的时候打败我,只要我全神贯注地玩,我就应该能赢。
第二,一个太智能化、复杂化的游戏会显得不真实。例如,在战争游戏中,一个AI开发者会坚持把计算机对手做成世界上最伟大的将军,这就很不真实。让计算机对手时不时犯点错误或者让它模拟现实存在的某位将军的风格才能使游戏更为真实。AI理论趋向于创造一个理想化的情景,而不是真实世界。
所以,你需要做的只是让计算机对手具有真实的外在形象和性格特怔,而不是创遣一个世界上第一强大的对手。你可以用AI技术为游戏者表现游戏世界中的深度、挑战性,模拟异类世界的情形。
不管你使用什么技术,最重要的是使用后的效果。如果有人对你说:“在你的游戏中加点人工智能吧”,他们很可能是让你加一些真实画面和角色,而不是什么神经网络之类的技术。

第六节 游戏的界面设计
著名的游戏开发者BilI Volk,曾经对游戏设计写下了一个等式“界面+产品要素=游戏”。这个等式与著名的程序设计语言的设计者Nicholas Wirth的经典等式“算法十数据结构=程序”是相似的。很明显,BilI Volk的目标不是说建构游戏就是简单的加法,而是强调在游戏设计中界面的重要性。他的观点基本上是这样的:你的游戏就是你的界面。
界面是游戏中所有交互的门户。不论你是用简单的游戏杆,还是运用具有多种输入设备的全窗口化的界面,你的界面是联系产品要素和游戏者的纽带。
创建一个好的界面都需要什么素和技术呢?下面我们将讨论一些建构有效的游戏界面时可能有用的一些基本问题。

1.内务管理问题
内务管理问题是指当一个人玩游戏时所必须的安装、准备等任务。这些问题包括初始安装、调用和保存游戏、游戏设置和在线指导或者一般文档。

2.游戏者介人的问题
游戏者介入的问题大概是与界面有关的最重要的因素。从根本上说,它将影响游戏者玩游戏的方法,是通过键盘、游戏杆、鼠标或者是这几种的结合?或者还运用其它类型的输入设备?
在设计界面时,应该了解游戏输入设备的范围。还要充分考虑灵活性和能够能够运用到你的游戏中的设备的类型。

3.让游戏者尽快开始游戏
什么时候当你拿到一个新游戏后,在开始游戏之前一页又一页地阅读用户手册?如果你与大部分的游戏者一佯,你大概是直接开始玩,井努力去了解如何通过用户界面进行交互,游戏者并不是典型的软件用户,他们没有兴趣学习大量新特征??他们只是简单地想玩!所以,当你设计你的用户界面时,应该使它容易让人理解和接受。你的第一个目标应该是让游戏及其界面尽可能地符合直觉。当然,你可以提供一个手册,但是不要期望你的所有用户都去读它。
下面让我们看看帮助你设计界面,以便于让用户能够正确入门的一些基本原则:

1.降低计算机的影响
降低计算机的影响是交互性中比较抽像的一个概念,但又是“幻觉状态中的一个成分。当然,在我们坐下来用计算机时,我们意识到我们正在运用计算机。然而在设计一个游戏特别是设计界面时,应该尽量让游戏者忘记他们正在使用计算机,这样会让他们感觉更好一些。尽量使你的游戏开始得又快又容易。游戏者进入一个游戏花的时间越长,越会意识到这是个游戏。

2.在你的游戏中加入帮助
尽量把你的手册结合到游戏当中,避免把游戏者拉开屏幕让他们去看书面的文字口这方面通过优秀的设计是可以解决的,如果需要的话,把文本世结合到游戏中。
例如,如果你有一幅让游戏者使用的地图,就不要让它成为文档的一部分,应该把它做成屏幕上的图形。

3.避免运用标准的界面
对于大部分在Windows环境下设计的游戏都别去运用常规的Windows界面。如果你这么做的话,你就又在提醒他们正在运用计算机。应该运用其他的对像作为按钮井重新定制对话框,尽量避免菜单等可能提醒他们正在运用计算机的对象。

4,综合集成界面
界面上关键的信息要简化。因为对于许多产品,界面绝对是产品特征的门户。对于游戏来说,目标就是要让界面越来越深入到游戏本身的结构中去。对于大量的游戏者来说,其中只有少部分人具有计算机经验,因而,界面问题就更加重要。

第七节 基于可玩性的考虑之一
可玩性不仅是测量游戏有趣程度或是图像如何“酷”的量度,它也是一个游戏的有趣程度,难易程度和坚固性等性能的总和,此外还包括音响效果、模拟图像等等。可玩性的关钟是为你的游戏的每个方面努力奋斗。虽说只有开发完毕后才能看出游戏的最终可玩性,而且开发期间的测试和调整也会起很大的作用。但是,每个细节的优化都可能大大提高可玩性。

1.最终的可玩性不到最后不会表现出来。
在游戏完成之前你不会知道你的游戏如何好玩。首先,你要确定你能完成你的游戏,除非测试游戏的人“都”说你的游戏真的差劲。在完成所有的声音、图像等工作以前,你不会确切知道你的游戏到底怎样。其次,最终结果常常表明每个小细节都很重要。游戏者会注意到游戏的方方面面,从简介中错位的像点到不合适的音效。每个小的失误都会带来不良的影响,破坏了游戏者的整体游戏体验和游戏的可玩性。

2.早期测试和经常测试??中期调整
当你的想法还停留在纸面上的时候,它可能看起来很神奇,但一旦你开始实现它,事情往往就变了,并且经常是变得很糟!因此,每个较大的游戏程序都需要很长的测试过程。
当在屏幕上的某些东西不是你最初想象的那样,或是你自认为设计很好的动画速度太慢或很难看。有时你的测试者会说:你的游戏太简单、太弱智、太烦人。所以即使你花几个月的时间停留在纸面设计上,你也不必感到沮丧。
准备好根据意见反馈来改变你的游戏设计计划。有时批评会有帮助,他们可以给你实际的建议并帮助你,使你的游戏更具可玩性。但有时他们也没什么帮助,你还得自己作出判断。必须明白,你应该在开发过程中不断测试你的游戏的可玩性,而不要等到开发完毕,那时就来不及作重大调整了。

3.设计内容:了解你的特定顾客
增加游戏可玩性的最好办法是:确定游戏是面向特定顾客而设计的。由于年龄、性别和教育背景等原因,对某一个游戏者来说好玩的游戏不一定让另一个游戏者喜欢。因此,在设计成功的游戏中,一个最重要的行动是调查研究需求和其它关于特定顾客的需要。


第八节 基于可玩性的考虑之二
让我们来看一下,如何针对不同年龄、性别来设计游戏。
游戏的市场是非常广阔的。你可以为2-4岁的孩子、受过大学教育的成人或在此两者之间的任何年龄层的人设计游戏。

1.年龄段:
(1)学龄前早期阶段(2-4岁):这一年龄段的孩子在控制鼠标、单独使用键盘或其他复杂的输入设备方面存在一定困难。因此,成人使用的标准游戏设备不适用于他们。他们不能打字也不能阅读文字。那么应如何为他们设计游戏呢了
学前儿童可以使用光标键。他们能在键盘上找出单个的字母键。如果你设计的热区有较大的区域,那么使用鼠标也是可以的。事实上,热区应不小于屏幕的1/12。然而,语音最适合早期学前儿童游戏。下面是使用语音的一些规则:
a.你的游戏应简单到不需要介绍,儿童立刻可以开始玩游戏。但是千万别用语音指导替代简单易懂的界面,可以使用简单的短语指导儿童玩游戏。。
b.当游戏中有一段时间没有用户输入时,播放一段短语告知儿童应该做什么,如“点一下猫”或“按A键”。
c.把语音作为奖励使用。在儿童完成一个任务后可以说:“很好”或“干得好”等。
d.讲故事采用播放语音的方式。
e.让你游戏中的人物说话。小孩子特别喜欢天真可爱的声音和单词。
f.小孩子喜欢明亮的颜色和简单的图形,但不幸的是,出版商喜欢细化的、高解析度的图形。因为他们的成人口味与孩子很不相同。成年人趋向于认为产品越复杂,质量就会越好。你作为一个游戏开发者应努力平衡这两方面的需求。
不要让界面显得太杂乱。学前儿童的大脑还没有那么多经验来体验复杂的详细图片。使用醒目的提示方式如弹出式菜单、随机动画等等。记住你的游戏者是儿童。尽量避免令人害怕的图像。这个年龄段的儿童还处于没有准备接受诸如伤害、死亡、分裂、暴力、怪物、女巫和坏家伙等概念的阶段。事实上,大部分不友好的东西都应该避免。
(2)学龄前阶段(4-6岁):适用于早期学前儿童的定律同样适用于这个年龄组。这些儿童能认出单词。他们中的一些人成为使用游戏俸的专家,并能更好地控制鼠标。除游戏俸和鼠标之外,提供一个支持键盘的界面不失为一个好主意。
(3)小学早期阶段(5-8岁):在这个年龄段,儿童可能喜欢怪物和其他坏家 伙,只要这些敌人是“安全的”。在游戏中敌人应当是友好而幽默的。暴力、伤害和流血仍然应该避免。
(4)小学/中学过渡阶段(7-11岁):这些儿童已达到“理性的年龄”,他们开始发展自己的“同辈文化(Peer CuIture)”,并开始思考他们自己。在这个年龄段,孩子们开始注意到比他们大的群体。作为设计者,你一定小心不要用太“孩子气”的材料。你的游戏表现的特点应比这个年龄层稍大一点。。
这个年龄层的儿童能阅读文章,尽管有时并不流利。在使用恰好在儿童记忆范围内的词汇表还是范围稍大一点的词汇表方面是有争论的,你应该自己拿主意。
(5)青少年阶段(12岁以上):这个年龄层的儿童被认为是最难以对付的顾客。针对他们的父母的市场策略是,非传统的教育题材的游戏可能最有发展前途。
“酷”是这些孩子追求的终极目标。男孩子疯狂地玩像Doom和真人快打这样的游戏,因为在那里他们满足自己的强烈欲望。女孩子更感兴趣的是社会活动,坐在计算机面前并不总出现在她们的时间表里。这些十几岁的孩子从能力和脑力上讲已经是成人了。实际上他们可能比大人更会玩计算机。包含合作/对抗玩法的游戏在这里可能最有市场。没错,用怪物、鲜血和暴力你一定可以取得成功!想想看,要是真人快打中没有这些它还会是什么?多人游戏和联机游戏同样也是很好的。
(6)成人阶段(17岁以上):这些游戏者大多是受到良好教育的成人,他们那种欣赏、处理复杂事物的能力允许你使用一些性、暴力成分较多的题材(在法律和道德的范围以内)。

2.确定性别特征
(1)游戏应该让男性和女性都作主角,而不是只让男性表现得特殊。有时可以允许游戏者选择不同性别的角色进行游戏。
(2)不要把鲜血和暴力特别表现出来。
(3)也不要特别表现打斗的场面。
(4)避免传统的性别习惯。男性不应总是领导者,女性也不应总是被描绘成漂亮的陪衬角色。
(5)可以试试加点幽默,但可不是黄色的幽默。

第九节 基于商品性的考虑
这些当你设计游戏的时候,你需要考虑哪些主要因素呢?它们当中有许多是与市场销售有关。例如:
对象是谁?
让你的游戏与众不同的话,需要做什么?”
你的游戏在哪儿和怎样被销售?
你的游戏属于哪一类?这是很重要的,因为它决定了你的游戏在零售渠道中怎样被分
和销售。
竞争和需求的情况如?。
你需要用3D图形和交互技术(真实3D景像、虚拟现实等)来吸引顾客吗?
你的游戏价格将会是多少?你会不断地升级吗?
为了有可能甘展游戏市场,你为游戏增加了一些什么特点?
你能在你的游戏中设计一些附加的成分,如附加的等级水平、场景和故事吗(例如,你创建了一个模拟类的游戏,如飞行模拟器,你可以创设一些其他的地形地貌,让游戏者去探索。这些附加的内容可以单独包装销售。)?
你的游戏能不能成能够被移植到其它环境(尤其是没有键盘的控制台环境),而且付出代价不大?
你能不能为你的游戏中独有的特点办理许可证或者进行名称认证?
你的游戏是不是具有国际性的感染力?
在设计游戏时,是否有办法让你的游戏保持尽可能长的生命力?
如果游戏销售状况良好的话,是不是可以有续集?。
这个游戏仅仅是一个娱乐性的产品还是可以作为教育的工具?
你的游戏是不是可以作为多人游戏?能够在网络上运行吗?
其中有些问题在后面的设计过程中将会被解决,但是你仍然需要一开始设计就考虑这些问题,否则的话在后面你会后悔的。无论你自信你的游戏会多么火爆,这些最终与市场相关的问题都是很重要的。毕竟,不管你的游戏设计得多么好,如果最终没有卖出去的话,所有艰苦的劳动都是徒劳。。
你的问题清单应该包括上面提到的这些问题和一些与你的特定开发情境有关的问题。当
然,你应该回答的问题还不止这些,但这些是我觉得应该强调的问题。有的开发者认为最大的问题是:“我能筹集足够的资金制作这个产品吗?”,这个问题的答案应该考虑以下三个主要方面:
生产这个游戏的成本,与之相关的还有开发这个游戏要花的时间。
游戏销售能力和销售利润。
开发者的能力,他们能否在进行主要投资之前对这些问题知道尽可能多的答案。
当你考虑这些问题以后,你将会发现这些问题可以分为两组:
强调成本和市场要素的问题。
强调创造性要素的问题。
下面让我们更详细地考虑一些更重要的与市场相关的问题。

1.你要设计什么类型的游戏?
这是你要回答的最为重要的问题,而且你要在创建设计文件以前回答这个问题。从本质上说,可以归结为这个问题:“做完以后要得到什么结果?”

如果在一定程度上你能够把你的最终结果可视化,你就能更好地回答我在上面提到的那些详细问题。
大部分开发者决定他们要开发什么类型的游戏主要从两方面来考虑,一方面是选择他们感兴趣的,另一方面是选择销售量可能大的。如果你喜欢战争游戏,你会说:“我要开发一个战争游戏”。这种情况下,你的目标是最终期望的结果。下一步就要求详细地考虑如何达到目标。
a.你的对像的年龄范围和性别特征是什么?
随着越来越多的人们购买计算机和控制台,就越来越要求游戏适合不同的年龄范围。游戏工业也就不仅仅以男孩子为特定对像。最近大量的讨论是关于生产适合女性购买和娱乐的游戏。你需要预先考虑和确认你的游戏的确切对像,千万不要落人这样的思维陷井:“我的游戏是如此火爆,以致于每一个人都想买去玩”。尽管生产一个具有较宽年龄范围的游戏是可能的,但是大量成功的游戏都是指向一个特定的年龄段,这是很重要的,因为,如果你的游戏对于一个特定的年龄段太具挑战性(或者没有什么挑战性),你的游戏都将不会被经常玩。

在这一方面,设计一个游戏和写一本书是没有区别的。如果你写一本关于在另一个星系上生活的科幻小说,你最好对你的读者有一个清晰的概念。如果你是给青年人写的书,就应该保证运用比较短的句予并详细解释在你的故丰中比较重要的技术概念。
b.你的游戏允许玩多少小时?
如果你的游戏是一个消遣游戏,即能够在办公休息的间隙用20分钟就能很快结束(我希望没有人在工作时间玩游戏被抓住。)?或者你的游戏是一个能够持续长达100小时的冒险游戏?或者你的游戏是一个技巧性游戏(Arcade)或体育游戏,即要求一小时又一小时反复地玩?

玩的时间在某些方式上决定于消费者,首先,价值是要考虑的,也就是说每玩一小时游戏投入的成本是多少?有些消费者不希望花太长的时间玩游戏。其次,你需要决定的游戏在哪里中断。
c.你的游戏将在什么系统上运行?
你的游戏是一个单一平台的游戏,还是一个能移植到其他平台上的游戏?这个决定在很大程度上将影响以后的技术决策。一个仅仅为Windows平台开发的游戏比设计一个多平台的游戏更具技术上的自由度
d.你打算让你的游戏满足什么样的价格水平?
你采用的游戏平台和对价格水平的选择,对技术都有影响。当然,一个低价的产品只需要较少的投资,这里最大利润不是问题,而什么样的投资对你选择的价格水平能生产最好的游戏才是一个问题。价格是消费者考虑的主要问题,当他们看到游戏的价格达到70或80美元时,许多人就变成“望价兴叹”者。如果你想销售量大,就应该努力创建一些低成本的娱乐产品。这些事实将使你的游戏价位成为一个主要决策,这当然会影响你的设计。

5.你对你的游戏将来用什么样的销售方式?
在游戏商业中,销售就是一切。如果没有充分考虑与游戏本身相适应的销售计划,即使最好的产品化的游戏也将可能面对失败。应该预先尽你所能投入时间和资源,以确保你计划开发的游戏能够充分利用手头的资源去销售。你应该主要考虑的三种销售系统是:零售、共享和直销,或者它们的任意组合。
如果你计划运用共享的方式,你的游戏设计应该与主要是零售的产品不同。应该探索不同的销售方式中产品之间的差异,并调整你的设计,以最大限度地满足人们的需求。对于每一个寻找新鲜花佯的消费者,有许多不同的销售方式。

6.你的游戏具有足够的交互性吗?
这听起来似乎奇怪,但是,我确实看到大量的游戏只具有低水平的交互性。如果你的游戏不能让游戏者与环境进行充分的交互,这是很麻烦的。例如,如果你在一个有人的城市,而游戏者又不能与这些人交谈,这就限制了交互性。由此再进一步说,如果,你有一个许多人居住的城市,但是,你只能与其中很少的几个人交谈,这实际上仍然限制了交互性,因此,在你设计你的游戏和附加的游戏组件时,应该确保它们能够有助于游戏交互的本质。你可能没有意识到,实际上在游戏中这种只具有低水平交互性的例子是很多的,如果你在游戏设计开发的一些环节上没有很好地注意这一向题的话,这就更不可避免了。

2.举一个例子
就像我前面所说的,你应该从具有最大创造性的问题出发:“我到底想做什么?”然后充实一些你的思想。最后,你将面对成本和市场问题,这些问题将对你最终的创造性决策产生影响。上我们再来看看这方面的问题。
例如,假设你将制作一个篮球游戏,你最初的目标可能是创造一个分场景的、五人对五人的动作游戏,每一个场景包括一些漂亮的扣篮动作的视频片断。然后你应该考虑成本和市场间的问题,对像是谁?你能否买得起或者获得这些视频信息的使用许可权?你能否用得起“魔术师”Johnson或者Charles Barkley配音?你将用什么平台?要回答这些问题你应该进行一些创造性的思考,以决定它的成本是多少,要花多少时间制作,有什么样的技术障碍等等。
对于这一产品进行了许多讨论以后,特别是考虑了市场和成本因素,你最后决定Windows平台,将游戏设计成三人对三人的比赛,而不是设计五人对五人(为了节省时间和资金)。这样你就又设定了一些参数,进一步的创造性设计要考虑到这些参数,这样你才能在这些参数的制约下规范你最初的思想。
设计和计划是共存的:它们互相依存。设计一个游戏,尤其是一个主要的商业化产品,要求比较好的平衡。真正设计的创造性也正体现在一个由成本和其他技术参数制约的现实的框架内,创立一个富有艺术性和感染力的游戏。创造性的设计并不是不受任何制约性。
至此,我们已经讨论了各种各样的游戏设计的思想、困难和问题,也逐渐明白了应用这些思想的背景条件。当然,这也并不是说在设计一个实际的游戏时都要运用这些思想,设计一个具有3000个角色(而且每一个角色都有非常酷的个性和独有的对话引擎)的RPG游戏听起来很好,但是你还要考虑实现技术、时间和投资的限制。
 


 


游戏开发基础(2)



第二章 windows编程基础

第一节 引言
为了跟上潮流,我们抛弃了已快被淘汰的DOS操作系统,所有的讲解和例程都是基于微软的Windows操作系统的。考虑到很多的用户并没有Windows编程基础,所以我们设置了这一专门讲述、讨论Windows的术语、概念的部分,以使这部分用户能较快地理解和掌握我们所讲述、讨论的编程思想和编程方法。这一部分中主要讲述的是Windows中十分基本的东西,所以用户应根据自己的情况有选择的进行学习。好!现在就让我们进入艰苦而又精彩有趣的游戏编程之路吧!

第二节 windows的介绍
Windows应用程序可以采用面向过程的实现方法。也可以使用面向对象的结构。所有的实现方法都集成了点击控制和弹出菜单,能够运行特别为Windows编写的应用程序。
Windows是一种基于图形界面的多任务操作系统。为这个环境开发的程序(那些专门为Windows设计的)有着相同的外观和命令结构。对用户来说,这使得学习使用Windows应用程序变得容易了。为了帮助开发Windows应用程序,Windows提供了大量的内建函数以方便地使用弹出菜单、滚动条、对话框、图标和其他一些友好的用户界面应该具有的特性。
  Windows运行应用程序以硬件无关的方式来处理视频显示、键盘、鼠标、打印机、串行口以及系统时钟。
最值得注意的Windows特性就是其标准化的图形用户界面。统一的界面使用图片或图标来代表磁盘驱动器、文件、子目录以及其它操作系统的命令和动作。 统一的用户界面也为程序员带来了好处。例如,你可以很方便地使用常见菜单和对话框的内建函数。所有的菜单都具有相同风格的键盘和鼠标接口,因为是Windows而不是程序员在实现它。
Windows的多任务环境允许用户在同一时刻运行多个应用程序或同一个应用程序的多个实例。一个应用程序可能处于激活状态。激活的应用程序是指它正接收用户的输入。因为每一个瞬间仅有一个程序能够被处理,因此同一时间也只能有一个应用程序处于激活状态。但是,可以有任意个数的并行运行的任务。
  

第三节 windows的基本概念
Windows消息和面向对象编程
  Windows实现了一种仿OOP(面向对象编程)环境。Windows下的消息系统负责在多任务环境中分解信息。从应用程序的角度来看,消息是关于发生的事件的通知。用户可以通过按下或移动鼠标来产生这些事件,也可以是通过改变窗口大小或选择一个菜单项等。这些事件也可以由应用程序本身产生。Windows本身也能产生消息。如“关闭Windows”消息,Windows通过这个消息来通知所有的应用程序,Windows将被关闭。
  内存管理
  在Windows系统中系统内存是最重要的共享资源之一。当同一时刻有多个应用程序在运行时,为了不耗尽系统资源,每个应用程序必须合作以共享内存。同时,当启动新的程序和关闭老的程序时,内存会变得碎片化。通过移动内存中的代码和数据块,Windows能够使内存空闲空间连起来。在Windows下也有可能超量使用内存。例如,应用程序可以比内存容量大。Windows能够废弃当前不使用的代码,在以后需要时再从应用程序中将之读入内存。Windows应用程序可以共享可执行文件中的例程。包含可共享的例程的文件称为动态链接库(DLL)。Windows包括了运行时将DLL例程链入程序的机制。
硬件无关性
  Windows同时提供了硬件或设备无关性,使你免于在生成程序的时候不得不考虑所有可能使用的显示器、打印机或输入设备。在Windows下面,每种硬件设备的驱动程序只编写一次。硬件无关性使编程对应用程序开发者来说更为简单。应用程序与Windows而不是各种设备打交道。
动态键接库
  动态键接库提供了更多的Windows功能。它们通过一个有力而灵活的图形用户界面增强了基本的操作系统。动态键接库包括一些预定义的函数,它们可以在一个应用程序被调入时与之键接(动态地),而不是在应用程序被创建时(静态地)。动态键接库使用DLL后缀。函数库将每一个程序员从重复开发诸如读取字符或格式化输出之类的通用例程中解放出来。程序员可以方便地构造它们自己的库以包含更多的功能,比如改变字体或检验文本。把函数变为通用工具减少了冗余设计,这是OOP的一个关键特性。
Windows的库是被动态地键接的。或者说,键接器并不把函数拷贝到程序的可执行文件中去。相反,当程序运行时,它产生对库函数的调用。自然,这样做节约了内存。不管有多少应用程序在运行,在RAM中总是只有库的一份考贝,而这个库可以被共享。
Windows的可执行文件格式
Windows具有一种新的可执行文件的格式,称为New Excutable格式。它包括新型的文件头,能够保存有关DLL函数的信息。

第四节 windows的窗口
Windows的窗口
  窗口看起来就是显示设备中的一个矩形区域,它的外观与特定的应用程序无关,可是,对于一个应用程序来说,窗口是屏幕上应用程序能够直接控制的矩形区域。应用程序能够创建并控制主窗口的一切,如大小和形状。当用户启动一个程序时,一个窗口就被创建了。用户每次单击窗口,应用程序作出响应。关闭一个窗口会使应用程序结束。多窗口带给用户Windows的多任务能力。通过将屏幕分为不同的窗口,用户能够使用键盘或鼠标选择一个并行运行的应用程序,以此对多任务环境中的一个特定程序进行输入,Windows截取了用户的输入并分配必要的资源(例如微处理器)。
Windows的布局
  所有的Windows应用程序都具有诸如边框、控制菜单、About对话框之类的共同特征。这些特征使得各个Windows应用程序非常类似。
边框
Windows的窗口被边框所包围。边框由围出窗口的线条组成。对于新手而言,边框看起来仅仅是为了将一个应用程序的屏幕视口与其它的区别开。但是,对于熟练者,边框有着不同的作用。例如,如果将鼠标指针放在边框上并按下鼠标的左键,用户就可以改变窗口的大小。
标题条
应用程序的名字显示在窗口顶部的标题条中。标题条总是在相关窗口顶部的中央。标题条非常有用,它可以帮助你记住正在运行哪个应用程序。活动应用的标题条以不同于非活动应用程序的颜色显示。
控制图标
控制图标是每个窗口左上方的小图片,每个应用程序都使用它。在控制图标上单击鼠标键会使Windows显示系统菜单。
系统菜单
当用鼠标单击控制图标时就打开了控制菜单。它提供了诸如Restore,Move,Size,Minimize,Maximize以及Close这样的标准操作。
最小化图标
每个Windows 95或Windows NT应用程序都在窗口的右上角显示三个图标。最左边的图标是一段短下划线,这就是最小化图标。它可以使用程序被最小化。
最大化图标
最大化图标是三个图标中中间的那一个,看起来象两个小窗口。使用最大化图标可以使用应用程序占满整个屏幕。如果选择了这个图标,其它应用程序窗口都会被盖住。
垂直滚动条
如果有必要,应用程序可以显示一个垂直滚动条。垂直流动条显示在应用程序窗口的右边,在两端有两个方向相反的箭头。它还有一个着色的棒和一个透明的窗口块。后者被用于显示当前显示内容与整个文档(着色的棒)的关系。你可以用滚动条来选择显示哪一页。一般在任何一个箭头上单击一下会使显示内容移动一行。单击向上箭头下方的窗口块并拖动它会使屏幕输出快速更新到应用程序屏幕输出的任意位置。
水平滚动条
也可以显示一个水平滚动条。水平滚动条显示在窗口的底部,具有与垂直滚动条类似的功能。你用它来选择要显示哪些列。一般在任何一个箭头上单击一个会使显示内容移动一列。单击向左箭头右边的窗口块并拖动它会使屏幕输出快速更新到应用程序屏幕输出的任意位置。
菜单条
一个可选择的菜单条可以显示在标题条的下方。通过菜单条来选择菜单和子菜单。这种选择可以通过用鼠标单击,也可以用热键组合来实现。热键组合经常是ALT与命令中带下划线的字母的组合,比如File命令中的“F”。
用户区
通常用户区占据了窗口最大的部分。这是应用程序的基本输出区域。应当由应用程序来复杂管理用户区。另外,应用程序可以输出到用户区。

第五节 windows的类
窗口的基本组件有助于说明应用程序的外观。有的时候应用程序需要创建两个外观和表现都相似的窗口。Windows的Paint就是一个例子。借助于同时运行Paint的两个实例(或拷贝),Paint允许用户剪贴或拷贝图片的一部分。然后信息就可以从一个实例拷贝到另一个实例。Paint的每个运行实例的外观和表现都与其他的相同。这就需要每个实例创建自己的外观和功能类似的窗口。
在这种情况下被创建的外观和功能都很类似的窗口被称为是属于同一个窗口类的。但是,你创建的窗口可以有不同的特征。它们可以有不同的大小,不同的位置,不同的颜色或不同的标题,也可以使用不同的光标。
每个被创建的窗都基于一个窗口类。在用C语言开发撕于的基于传统的函数调用方式的应用程序中,一些窗口为在Windows应用程序初始化的进修注册。你的应用程序可以注册属于自己的窗口类。为了能够使几个窗口在同一个窗口类的基础上创建,Windows定义了一些窗口特征,如CreateWindows()的参数,另一些定义的窗口类的结构。当你注册一个窗口类的时候,这个类可以被Windows下运行着的任何程序所使用。对于使用MFC的应用程序来说,多数注册工作已经由预定义的对象完成了。
具有相似的外观和表现的窗口可以被组合成一个类,以此来减少需要维护的信息。因为每个窗口类都有自己的可共享的类结构,不需要复制不必要的窗口类参数。同时,同类的两个窗口使用相同的函数以及相关的例程。这样可以节省时间和空间,因为不存在代码复制。

第六节 windows中的面向对象编程
在Windows下传统的C程序吸收了一些面向对象编程的特性。对象是一种包含数据结构和对这些数据结构进行操作的函数的抽象数据类型。而且,对象接收会引起它们不同动作的消息。
比如,一个Windows的图形对象是可以被作为一个实体来操纵的一些数据的集合,对于用户它是可视界面的一部分。特别地,一个对象意味这数据和数据的功能。菜单、标题条、控制块以及滚动条等都是图形对象的例子。下一部分描述一些影响应用程序外观的新的图形对象。
图标
  图标是用来使用记住特定操作、想法或产品的小图形对象。比如,一个电子表格程序被最小化时可以显示一个很小的柱状图以提醒用户这个程序还在运行之中。在柱状图上双击鼠标会使Windows激活这个应用程序。图标是非常有力的工具。它很适合用来引起用户的注意,比如在发出错误警告或者是向用户提供选择时。
光标
  光标是Windows用来跟踪指点设备的运动的图形符号。这种图形符号可以改变形状以指明特定的Windows操作。比如,当标准的箭头光标变为沙漏光标时说明Windows正在执行一个命令,需要暂停。
编辑光标
  应用程序在窗口中显示编辑光标以告诉用户在哪儿输入。编辑光标与其他屏幕符号显然不同,因为它是闪烁的。多数时候,鼠标输入与光标相连,而键盘输入与编辑光标相连。但是,可以用鼠标来改变编辑光标的输入点。
消息框
  消息框是另一类Windows图形对象。消息框是一种包含标题、图标和消息的弹出式窗口。图(?)是关闭Windows Notepad程序时出现的一个标准的消息框。
-------------------------------------------------------------------------
| |
------------------------------------------------------------------------
Windows的对话框
  对话框与消息框相似的地方在于它也是一种弹出式窗口。但是对话框主要用于接受用户输入而不仅仅是显示一些输出。对话框允许应用程序接受输入,每次一个域或是一个框的内容,而不是每次一个字符。图(?)显示了一个典型的Windows对话框。对知框的图形设计由Windows为你自动完成。对话框的布局通常用编译器中的资源编辑器完成。
-----------------------------------------------------------------------
| |
-----------------------------------------------------------------------
字体
  字体是一种图形对象或资源,它定义了完整的字符集合的字样。这些字符都有一个特定的大小和风格,可以使文本具有不同的外观。字样是字符的一种基本属性,它定义了字符的衬线和笔画宽度。
位图
位图是一种显示图片(按像素组织),存储于内存。当应用程序需要快速显示图片时可以使用位图。因为位图直接从内存中传送,所以它比用程序重新画出图片要快得多。位图有两个基本用途。首先,它可以在屏幕上显示图片。其次位图也用于创建刷子。刷子使你可以在屏幕上画出并填充对象。
  使用位图有两个缺点。首先,与其尺寸有关,位图会占据难以预估的大量内存。每个被显示的像素都要在内存中占据相应的空间。在彩色显示器上显示一个像素会比在单色显示器上占据更多的内存。在单色显示器上,只需一位(bit)就可以表示出像素的状态。可是在可以显示16种颜色的彩色显示器上,需要四位才能表示一个像素的特征。同样地,随着显示设备分辨率的增加,位图对内存的需求也增加了。位图的另一个缺点是它只包括静态的图片。比如,如果用位图来代表一辆汽车,就没有办法来访问图片的不同部分,如轮踏、顶盖、窗等。但是,如果汽车是有一系列基本绘图例程来生成的,应用程序就可以改变向这些例程传送的数据从而改变图片的不同部分。例如,应用程序可以修饰顶蓬线并把一辆轿车变为敞蓬车。
画笔
  当Windows在屏幕上显示一个图形时,它使用当前画笔的信息。画笔用于画出线条或轮廊。画笔具有三个基本特征:线宽、线型(虚线、短线、实线)以及颜色。Windows永远保留着用于画白线和黑线的画笔,任何应用程序可以使用它。你也可以创建自己的画笔。
刷子
  Windows用刷子来画出颜色并以预定义的样式来填充一个区域。刷子至少有8×8个像素大小。刷子有三个基本特征:样式和颜色。由于它们至少有8×8的大小,刷子被称作具有样式而不象画笔,称为线型。样式可以是纯的颜色,也可以是阴影线、斜线或其它用户自定义的组合
第七节 windows的消息
Windows的消息
  在Windows中,应用程序并不直接写屏幕、处理硬件中断或直接对打印机输出。相反,应用程序使用合适的Windows函数或者等待一个适当的消息被发出。
  Windows消息系统负责在多任务环境中分派消息。从应用程序的角度来看,消息可以看作是发生的事件的通知,有些需要作出特定的反应,有些就不需要。这些事件可能由用户产生,比如按下了鼠标或移动了鼠标,改变了窗口的大小或者选择了一个菜单。同时,这些事件也可能由应用程序本身所产生。
  这个过程使你的应用程序必须完全面向消息处理。当接收到消息时,应用程序必须能激活并决定正确的动作,完成这个动作之后回到等待状态。
  通过检查消息的格式和来源,下一部分将更仔细地讨论消息系统。
消息的格式
  消息通知一个应用程序发生了一个事件。从技术上来讲,消息不仅仅是与应用程序相关,而且是与应用程序的某一特定窗口有关。因此,所有的消息都被发往窗口。
  在Windows下只有一个消息系统-即系统消息队列。但是,每个正在Windows下运行的应用程序都有它自己的消息队列。系统消息队列中的每个消息最终都要被USER模块传送到应用程序的消息队列中去。应用程序的消息队列中存储了程序的所有窗口的全部消息。
  不管消息具有什么类型,它们都有四个参数:一个窗口句柄,一个消息类型,两个附加的32位参数。窗口消息中定义的第一个参数是消息所关联的窗口句柄。
  在编写Windows应用程序的时候经常使用句柄。句柄是一个唯一的数字,它被用于标识许多类型的对象,如菜单、图标、画笔和刷子、内存分配、输出设备甚至窗口实例。在Windows 95和Windows NT下面,程序的每个运行着的拷贝叫做实例。
  因为Windows 95和Windows NT允许你同时运行一个程序的多个实例,操作系统就有必要保持对这些实例的追踪。这是通过赋予每个运行实例一个唯一的实例句柄来实现的。
  实例句柄通常被用作一个内部维护着的表的索引。通过引用表中的元素而不是实际的内存地址,Windows 95和Windows NT可以动态地调整所有的资源,而只需在此资源所对应的表格位置中插入一个新的地址。
  根据一个应用程序的多个实例被处理的方式,内存资源由Windows 95和Windows NT保存。
  应用程序的实例具有很重要的作用。应用程序的实例定义了程序的函数所需的所有对象。这包括控件、菜单、对话框以及更多的新Windows类。
  消息中的第二个参数是消息类型。这是在Windows独有的一些头文件中定义的标识符。这些头文件可以通过WINDOWS.H来使用。在Windows下,每个消息由两个字符的助记符开始,跟着是下划线,最后是一个描述符。
  最后的两个参数提供了解释消息所需的附加信息。因此最后两个参数的内容依赖于消息的类型。
产生消息
  消息传送概念使Windows能够实现多任务。消息有四个基本来源。应用程序可以从用户那儿接受消息,也可以是Windows本身,应用程序本身或者是其它应用程序。
  用户消息包括按键消息、鼠标移动、鼠标指点或单击、菜单选择、滚动条的定位等。应用程序必须花费大量的时间来处理用户消息。用户产生的消息表明运行程序的人希望改变应用程序的表现方式。
  无论何时,如果状态发生改变,将会有一个消息被发往应用程序。一个例子是用户单击了应用程序的图标,表明他们想要将此应用程序变为活动的应用程序。在这种情况下,Windows告诉应用程序它的主窗口被打开了,它的大小和位置被改变了等等Windows产生的消息可以被处理,也可以被忽略,这跟应用程序当前的状态有关。
相应消息
在传统的面向过程的C语言Windows应用程序中,对于遇到的每一种消息,它都有一个相应的过程来处理这消息。不同的窗口对相同的消息会产生不同的响应。Windows把每个消息发送到应用程序的不同窗口,而不同的窗口对相同的消息会有不同解释。不令应用程序需要不同的过程来处理每一种消息,每一个窗口也应有不同的过程来处理不同的消息。窗口过程集合了应用程序的所有消息处理过程。
消息循环
所有Windows应用程序的一个基本组成就是消息处理循环。每一个C应用程序都在内部执行这个操作。C应用程序包含了创建并初始化窗口的过程,随后是消息处理循环,最后是结束应用程序所需的一些代码。消息循环负责处理Windows发给主程序的消息。在这儿,程序知道有了消息,并且要求Windows将消息发送到合适的窗口过程以供处理。当消息被接受时,窗口过程就执行希望的动作。

第八节 windows的函数
Windows向应用程序开发人员提供了数以百计的函数。这些函数的例子包括DispatchMes-sage(),PostMessage(),RegisterWindowMessage()以及SetActiveWindow()。对于使用基础类库的C++程序员,许多函数自动被运行。
在16位的Windows 3.x下的函数声明包括一个pascal修饰符,这在DOS下更为有效Windows95和Windows NT下的32位应用程序不再使用这个修饰符。如你所知,所有Windows函数的参数是通过系统来传递的。函数的参数从最右边的参数开始向左压入栈,这是标准的C方式。在从函数返回之前,调用过程必须按原来压入栈的字节数调整栈指针。

第九节 windows应用程序框架
Windows头文件:WINDOWS.H
WINDOWS.H头文件(以及其它相关文件)是所有程序的内在部分。传统上,WINDOWS.H是所有C语言编写的Windows应用程序必需的一部分。当在C++中使用基础类库时,WINDOWS.H包括在AFXWIN.H头文件中。
Windows应用程序的组成
   在开发Windows应用程序的过程中有一些重要的步骤:
  *用C语言编写WinMain()函数和相关的窗口函数,或者在C++中使用基础类,比如CWinApp等。
*创建菜单、对话框和其它资源并把它们放入资源描述文件。
*(可选)使用Vinsual C++编译器中的企业编辑器来创建对话框。
*(可选)使用Vinsual C++编译器中的企业编辑器来创建对话框。
*用项目文件来编译并链接所有的C/C++源程序和资源文件
Windows应用程序中的组成部分

1. WinMain()函数
Windows 95和Windows NT需要一个WinMain()函数。这是应用程序开始执行和结束的地方。
从Windows向WinMain()传递四个参数。下面的代码段演示了这些参数的使用:
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPreInst,
LPSTR 1pszCmdLine, int nCmdShow)
第一个参数hInst包含了应用程序的实例句柄。当应用程序在Windows下运行时,这个数字唯一标识了应用程序。
第二个参数hPreInst将始终是一个NULL值,表明没有这个应用程序的其它实例正在运行,因为在Windows 95和Windows NT下每个应用程序都在它自己单独的地址空间中运行。
第三个参数1pszCmdLine是指向一个以'/0'结尾的字符串的长指针,这个字符串代表了应用程序的命令行参数。
WinMain()的第四个参数是nCmdShow。在nCmdShow中存储的整数代表了Windows预定义的许多常量中的一个,它决定了窗口显示的方式。

2. WNDCLASS
WinMain()负责注册应用程序的主窗口类。每个窗口类都建立在一些用户选择的风格、字体、标题字、图标、大小、位置等的基础上。窗口类实际上是定义这些属性的一个模板。
  基本上,所有的Windows类定义都使用相同的标准C/C++结构。下面的例子是一个说明WNDCLASSW结构的typedef语句,WNDCLASS是从这儿继承的:
typedef struct tagWNDCLASSW
UINT style;
WNDPROC 1pfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBR8USH hbrBackground;
LPCWSTR 1pszMenuName;
LPCWSTR 1pszClassName;
WNDCLASSW,*PWNDCLASSW,NEAR*NPWNDCLASSW, FAR*LPWNDCLASSW;
下面的部分讨论了WNDCLASS结构中的不同的域。其中有些域可以被赋予NULL,告诉Windows使用缺省的预定义值。
style:style域指明了类风格。
  1pfnWndProc:接受一个指向窗口函数的指针,它将执行所有的窗口任务。
  cbClsExtra:指定了必须在窗口类结构后面分配的字节数,它可以是NULL。
  cbWndExtra:指定了必须在窗口实例后面分配的字节数,它可以是NULL。
  hInstance:定义了注册窗口类的应用程序实例。它必须是一个实例句柄,不得是NULL。
  hIconhIcon:划定利用窗口最小化时显示的图标。它可以是NULL。
  hCursorhCursor:定义了应用程序使用的光标。这个句柄可以是NULL。
hbrBackground:提供了背景刷子的标识符。
1pszMenuName:是指向一个以空字符结尾的字符串的指针。这个字符串是菜单的资源名。这一项可以为NULL。
1pszClassName:是指向一个以空字符结尾的字符串的指针。这个字符串是窗口类的名字。

3.WNDCLASSEX
Windows提供了一种扩展的WNDCLASS定义,名为WNDCLASSEX,它允许应用程序使用小图标。下面是WNDCLASSEX结构的定义:
typedef struct WNDCLASSEX
UINT style;
WNDPROC 1pfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hbrBackground;
LPCTSTR 1pszMenuName;
LPCTSTR 1pszClassName;
HICON hIconSm;
WNDCLASSEX;
你可以看到这两个结构是相同的,除了WNDCLASSEX包括了hIconSm成员,这是与窗口类有关的小图标的句柄。

4.定义窗口类
应用程序可以定义它们自己的窗口类,只要先定义一个合适类型的结构,然后用窗口类的信息来填充结构的域。
下面的代码示范了如何定义并初始化一个WNDCLASS结构。

char szProgName[]="ProgName";
.
.
.
WNDCLASS wcApp;
.
.
.
wcApp.1pszClassName=szProgName;
wcApp.hInstance=hInst;
wcApp.1pfnWndProc=WndProc;
wcApp.hCursor=LoadCursor(NULL,IDC-ARROW);
wcApp.hIcon=NULL;
wcApp.1pszMenuName=szAppIName;
wcApp.hbrBackground=GetStockObject(WHITE-BRUSH);
wcApp.style=CS-HREDRAW| CS-VREDRAW;
wcApp.cbClsExtra=0;
wcApp.cbWndExtra=0;
if(!RegisterClass (&wcApp))
return 0;

WNDCLASS结构中的第二个域是wcApp.hInstance,它被赋予了WinMain()被激活后返回的hInst的值。这指明了应用程序的当前实例。1pfnWndProc被赋予执行所有窗口任务的窗口函数的指针地址。对于大部分应用程序,这个函数叫做WndProc()。
  注意:WndProc()是一个用户定义而不是预定义的函数名。在赋值语句之前必须给出函数原型。
wcApp.hCursor域被赋予实例的光标句柄。
当wcApp.1pszMenuName被赋予NULL值的时候,Windows就认为这个窗口类没有菜单。 如果有,菜单必须有一个名字,它必须出现在引号里面。GetStockOject()函数返回一个刷子句柄,用于在这个类创建的窗口用户区中画出背景色。
wcApp.style窗口类风格被设为CS-HREDRAW或CS-VREDRAW。
最后的两个域,weApp.cbClsExtra以及wcApp.cbWndExtra经常被设为0。这些域可以被选用以指明窗口结构和窗口数据结构后面应该保留的附加字节数。
下面这段代码用于注册窗口类:
if(!hpreInst)

.
.
.
if(! RegisterClass(&wcApp))
return FALSE;

Windows 95和Windows NT通过检查hPreInst的值来确定多少个实例,而hPreInst总是NULL,所以就注册窗口类.

5.创建窗口
窗口通过调用CreateWindow()函数来创建。这个过程对所有版本的Windows都是一样的。窗口类定义了窗口的一般特征,允许同一个窗口类被用于多个不同的窗口,CreateWin-dow()函数的参数指明了关于窗口的更详细的信息。
CreateWindow()函数的参数信息包括以下内容:窗口类、窗口标题、窗口风格、幕位置、窗口的父句柄、菜单句柄、实例句柄以及32位的附加信息。在大部分应用程序中 ,这个函数会是下面这个样子:
hWnd=CreateWindow(szProgName,"Simple Windows Program",
WS-OVERLAPPEDWINDOW,CW-USEDEFAULT,
CW-USEDEFAULT,CW-USEDEFAULT,
CW-USEDEFAULT,(HWND)NULL,(HMENU)NULL,
(HANDLE)hInst,(LPSTR)NULL);
第一个域szProgName(已赋过值)定义了窗口的类,后面是窗口标题条上使用的标题。窗口的风格是第三个参数
下面的六个参数代表了窗口的x、y坐标和x、y方向的大小,然后是父窗口句柄和窗口菜单句柄。每个域都被赋予一个缺省值。hInst域包含了程序的实例句柄,后面是一个附加参数(NULL)。
显示和更新窗口
  在Windows下,ShowWindow()函数被用来实际显示一个窗口。下面的代码示范了这个函数:
Show Window(hWnd,nCmdShow);
在调用CreateWindow()时生成的窗口句柄被用作hWnd参数。ShowWindow()的第二个参数是nCmdShow,决定了窗口被如何显示。这个显示状态也被称为窗口的可视状态。
显示窗口的最后一步是调用Windows的Update Window()函数。

UpdateWindow(hWnd);

6.消息循环
一旦调用Win-Main()函数并显示了窗口,应用程序就需要一个消息处理循环。最常用的实现方法是使用一个标准的while循环:
while (GetMessage (&lpMsg,NULL,0,0))
{
TranslateMessage(&lpMsg);
DispatchMessage(&lpMsg);
}

GETMESSAGE()函数:应用程序要处理的下一个消息可以通过调用Windows的GetMessage()函数来取得。
NULL参数指示函数取回这个应用程序的任何窗口的任何消息。最后两个参数0和0告诉GetMessage()不要使用任何消息过滤器。消息过滤器能够将接收到的消息限制在一个明确的范围之内,如键盘消息或鼠标消息等。
一般应用程序应该确认通向消息循环的所有步骤都已经正确地执行过了。这包括确认每个窗口类都已经注册过,都已经被创建。否则,一旦进入了消息循环,只有一个消息能够结束这个循环。无论何时处理了WM-QUIT消息,返回值是FALSE。这会引发主循环关闭例程。WM-QUIT消息是应用程序退出消息循环的唯一途径。
TRANSLATEMESSAGE()函数:通过TranslateMessage()函数,虚拟消息可以被转换为字符消息。
DISPATCHMESSAGE()函数:Windows通过DispatchMessage()函数将当前的消息发送到正确的窗口过程。
******* 窗口函数
所有的应用程序都必须包括一个WinMain()函数和一个回调窗口函数。因为一Win-dows应用程序从不直接访问任何窗口函数,每个应用程序都必须向Windows提出请求以执行规定的操作。
一个回调函数在Windows中注册,当Windows要对一个窗口进行操作时,它就被调用。各个应用程序的回调函数的实际代码长度会大不相同。窗口函数本身可以非常小,只处理一个或两个消息,也可以非常大而且复杂。
下面的代码段(不完整的应用程序说明语句)显示了在应用程序中的回调窗口函数WndProc()的一个范例:

LRESULT CALLBACK WndProc(HWND hWnd,UNIT messg,WPARAM wParam,LPARAM 1Param)

HDC hdc;
PAINTSTRUCT ps;
switch(messg)

case WM-PAINT:
hdc=BeginPaint(hWnd,&ps);
.
.
.
ValidateRect(hWnd,NULL);
EndPaint(hWnd,&ps);
break;
case WM-DESTROY:
postQuitMessage(0);
break;
default:
return(DefWindowProc(hWnd,messg,wParam,1param));

return(0);

Windows希望窗口类结构定义中wcApp,1pfnWndProc域的名字能够与回调函数的名
字匹配。后面用这个窗口类创建的所有窗口的回调函数都应该用WndProc()的名字。
下面的代码段讨论一个窗口类结构中回调函数名的位置和赋值:
.
.
.
wcApp.1pszClassName=szProgName;
wcApp.hInstance=hInst;
wcApp.1pfnWndProc=WndProc;
.
.
.
Windows有向百个消息可以发送给窗口函数。这些消息用“WM-”打头的标识符来
标识。
WndProc()的第一个参数是hWnd。hWnd包含了Windows发送消息的窗口句柄。
函数的第二个参数messg按WINUSER.H中的定义指明了即将被处理的实际消息。最后的两个参数wParam以及1Param,指明了处理每个消息所需的附加信息。
WndProc()函数继续定义了两个变量:hdc指明了显示设备句柄,ps指明了存储用户区
信息所需的一个PAINTSTRUCT结构。
回调函数被用于检查将被处理的消息并选择执行适当的动作。这个选择过程通常在一个标准的C语言的switch语句中完成。
模块定义文件
正如你在前面学到的,LINK提供了所有Windows应用程序需要的模块定义文件在命令行方式下的替代品。模块定义文件向链接器提供有关的定义和描述信息,这样链接器就可以知道如何来组织Windows应用程序的可执行文件。这些信息已经成为新执行文件格式的文件头的一部分。
注意:在Windows 95和Windows NT下面,你不太可能需要创建一个模块定义文件。这些信息是为了完整性和向后兼容。


第十节 VC++提供的windows编程工具
Visual C++编译器包含几个资源编辑器。单独的编辑器可以通过编译器主菜单中的Insert Resource菜单来运行。图形对象都是资源,象图标、光标、消息框、对话框、字体、位图、画笔、刷子等。资源代表应用程序的可执行文件中包含的数据。
  资源编译器RC.EXE是一个Windows资源的编译器。。
  资源以及附加的编译器的使用增加了应用程序开发的复杂性。但是它容易在项目工具中使用。
项目文件
  项目文件提供了概览资源和程序代码编译过程的手段,同时也可以使应用程序的可执行版本保持最新。它们跟踪源文件的日期和时间以实现这些增强的功能。项目文件包含了有关特定程序的编译链过程的信息。项目文件是在集成的C或C++编辑环境中创建的。项目文件还支持增强的编译和链接。
资源
  当你使用VisualC++编译器提供的资源编辑器时,用自己的图标、指针和位图来定制Windows应用程序非常容易。这些编辑器给你提供了一个开发图形资源的完整环境。这些编辑器同时也能帮助你开发菜单和对话框-Windows下数据输入的基本手段。这些编辑器还能帮你操纵单独的位图、加速键和字符串。。
资源编辑器
  每一种编辑器都在VisualC++环境中提供,都是编译器的一个集成的部分。这样,每种编辑器都是在Windows下运行的完全集成的资源开发工具。你可以通过选择Insert Resource来启动每一种编辑器。
下面我们将通过教程演示资源编辑器的使用。请单击返回,然后运行教程。
? 第十一节 MFC的基本概念
基础类库为你提供了易于使用的对象。将Windows与C++联系起来是很自然的,这样就可以充分利用面向对象技术的优点。MFC开发组实现了大量的Windows应用程序编程接口(API)。这个C++库在一些可重用的类中封装了最重要的数据结构和API函数调用。
  类似MFC这样的类库比起前面两章讨论的C程序员使用的函数库有很多优点。
下面列出了C++类的一些优点,比如:
  *用类对数据和代码进行封装
  *继承性
  *消除函数和变量名的冲突
  *类是语言的自然扩展
  *通常,精心设计的库减少了代码量
  利用基础类库,创建一个窗口所需的代码大约只占传统应用程序的三分之一。这就可以使程序员只用花很少的时间与Windows打交道,把更多的精力集中在开发自己的程序代码上。
22.2 MFC的设计考虑
  基础类库设计小组定义了严格的设计规则,在设计MFC库时必须遵循这些规则。这些规则和方针如下:
  *利用C++的威力,但不能把程序员吓倒
  *使从标准API调用到类库的转换尽可能简单
  *允许混合使用传统的函数调用和新的类库
  *在设计类库的时候综合考虑功能和效率
  *建成的类库必须能够方便地在不同平台间移植,如Windows 95和Windows NT
设计小组感到要开发高质量的代码必须从MFC库本身开始。C++基础类库必须又小又快。它的简单性使它易于使用,而执行速度与庞大的C函数库接近。
  这些类的设计方式应该让熟练的Windows程序员不必重新学习各种函数的名字。通过仔细的命名和设计可以实现这一点。Microsoft认为这一点是MFC区别于其它类库的一个特征。
MFC小组还把基础类库设计为是允许以混合方式编程的。这就是说,在同一个源文件里,既可以使用类也可以使用传统的函数调用。即使是在使用MFC时,类似SetCursor()和GetSystemMetrics()这样的函数还是需要直接调用。
Microsoft也知道类库必须方便使用。其它厂商提供的一些类库设计得太抽象。按Microsoft的说法,这些笨重的类企图生成又大又慢的应用程序。MFC库提供了合理的抽象,保证代码很小。
开发小组将原始的MFC库设计为动态的而不是静态的。动态的结构是这些类可以适应我们现在使用的Windows 95和Windows NT环境。
22.3 MFC库的关键特性
从其它编译器厂商那儿也可以获得Windows类库,但Microsoft宣称他们的MFC类库具有许多真正的优点:
  *全面支持所有的Windows函数、控件、消息、GDI(图形设备接口)绘图原语、菜单以及对话框。
  *使用与Windows API相同的命名约定。因此,从名字上就可以直接知道类的功能。
  *消除了一个错误源,即大量的switch/case语句。所有的消息都被映射到类的成员函数。这种消息-方法的映射方法应用于所有的消息。
  *能够把对象的信息输出到文件,这提供了更好的诊断支持。同时还提供了验证成员变量的能力。
  *增强的例外处理设计,使得程序代码失败的可能性更小。能够解决“内存不足”以及其它一些问题。
  *可以在运行时决定数据对象的类型。这允许对类的域进行动态操纵。
  *小而快速的代码。前面已经提到,MFC库只添加了很少一些代码,执行起来几乎与传统的C语言Windows应用程序一样快。
*对组件对象模型(COM)的支持。
有经验的Windows程序员会立刻喜欢上其中的两个特性:熟悉的命名约定和消息-方法映射机制。如果你重新检查一下在第二十一章中开发的应用程序的源代码,你会看到大量用于处理错误的switch/case语句。还应该注意这些应用程序调用了大量的API函数。当你使用MFC库的时候,这两种现象都消失或减少了。
专业程序员肯定会欣赏在MFC库中实现的更好的诊断和很小的代码。现在程序员就可以利用MFC库的好处而不必担心他们的应用程序的代码大小了。
最后,MFC是唯一真正有用的类库。
22.4 一切从CObject类开始
类似MFC这样的类库通常都来自很少的几个基类。然后,另外的类就可以从这些基类中继承而来。CObject是在开发Windows应用程序时大量使用的一个基类。在MFC/INCLUDE子目录下提供的MFC库头文件包括了许多类定义信息。
我们来简单地看一下,CObject,它在头文件AFX。H中有定义:
///////////////
//class CObject is the root of all compliant objects
class CObject

public:
//Object model(types,destruction,allocation)
virtual CRuntimeClass*GetRuntimeClass () const;
virtual~CObject();//virtual destructors are necessary
//Diagnostic allocations
void*PASCAL operator new(size-t nSize);
void*pascal operator new(size-t,void*p);
void PASCAL operator delete(void*p);
#if defined(-DEBUG)&&!defined(-AFX-NO-DEBUG-CRT)
//for file name/line number tracking using DEBUG-NEW
void* PASCAL operator new(size-t nSize,LPCSTR 1pszFileName,int nLine);
//Disable the copy constructor and assignment by default
//so you will get compiler errors instead of unexpected
//behavior if you pass objects by value or assign objects.
protected:
CObject();

private:
CObject(const CObject& objectSrc);//no implementation
void operator=(const CObject& objectSrc);

//Attributes
public:
BOOL IsSerializable()const;
BOOL IsKindOf(const CRuntimeClass*pClass)const;
//Overridables
virtual void Serialize (CArchive& ar);
//Diagnostic Support
virtual void AssertValid()const;
virtual void Dump(CDumpContext& dc)const;
//Implementation
public:
static const AFX-DATA CRuntimeClass classCObject;
#ifdef-AFXDLL
static CRuntimeClass*PASCAL-GetBaseClass();
#endif

为了清楚起见,对这段代码作了一些细微的改动。但和你在头文件AFX.H可以找到的代码基本一样。
检查CObject的代码,注意构成这个类定义的成分。首先,CObject被分为公有、保护和私有三个部分。CObject还提供了一般的和动态的类型检查以及串行化的功能。回忆一下,动态类型检查使你可以在运行时确定对象的类型。借助于永久性的概念,对象的状态可以被保存到存储介质中,比如磁盘。对象的永久性使对象成员函数也可以是永久的,允许对象数据的恢复。
子类从基类继承而来。例如,CGdiObject是一个从CObject类继承来的类。这儿是AFXWIN。H中找到的CGdiObject类定义。同样,为了清楚起见,对其作了一些改动。
//////////////////////
//CGdiObjet abstract class for CDC SelectObject
class CGdiObject:public CObject

DECLARE-DYNCREATE(CGdiObject)
public:
//Attributes
HGDIOBJ m-hObject;//must be first data member
operator HGDIOBJ()const;
static CGdiObject*PASCAL FromHandle(HGDIOBJ hObject);
static void PASCAL Delete TempMap();
BOOL Attach (HGDIOBJ hObject);
HGDIOBJ Detach();
//Constructors
CGdiobject();//must create a derived class object
BOOL DeleteObject();
//Operations
int GetObject (int nCount,LPVOID 1pObject)const;
UINT GetObjectType()const;
BOOL CreateStockObject(int nIndex);
BOOL UnrealizeObject();
BOOL operator==(const CGdiObject& obj)const;
BOOL operator!=(const CGdiObject& obj)const;
//Implementation
public:
virtual~CGdiObject();
#ifdef-DEBUG
virtual void Dump(CDumpContext& dc)const;
virtual void AssertValid()const;
#endif

CGdiObject和它的成员函数允许在Windows应用程序中创建并使用绘画对象,如自定义画笔、刷子和字体等。诸如CPen之类的类是进一步从CGdiObject类继承而来的。
Microsoft提供了MFC库的全部源代码,以尽可能地增加编程的灵活性。但是,对于初学者,没有必要去了解不同的类是如何定义的。
例如,在传统的C语言Windows应用程序中,DeleteObject()函数按下面的语法调用:
DeleteObject(hBRUSH);/*hBRUSH is the brush handle*/
在C++中,利用MFC库,可以按下面的语法访问类成员函数以实现同样的目的:
newbrush.DeleteObject();//new brush is current brush
正如你可以看到的,从C语言Windows函数调用转向类库对象是简单的。Microsoft在开发所有Windows类的时候都使用这种方法,使得从传统函数调用到继承类库对象的转移非常简单。
 


 


游戏开发基础(3)



第三章 DirectX SDK简介
第一节 关于DirectX SDK
Microsoft DirectX提供了一套非常优秀的应用程序接口,包含了设计高性能、实时应用程序的源代码。DirectX技术将帮助您建构下一代的电脑游戏和多媒体应用程序。它的内容包括了DirectDraw、DirectSound、DirectPlay、Direct3D和DirectInput等部分,它们分别主要应用在图形程序、声音程序等方面。
由于DirectX,使在Windows下运行应用程序的性能可以与在DOS或游戏平台下运行的应用程序性能相媲美,甚至超过它们。它将为您的Windows游戏开发提供一个具有鲁棒性的、标准化的操作环境。
DirectX包括两部分:运行期部分(Runtime)和SDK。在DirectX开发时,这两部分都要用到,但在DirectX应用程序运行时只用运行期部分。在Windows NT 4.0及以上版本中含有DirectX运行期部分,Win95则没有。但Win95可以很容易获得DirectX运行期部分。而Windows NT 4.0以前的版本不能运行DirectX程序。许多基于DirectX的应用程序和游戏都包含了DirectX运行期部分。
它目前有五个版本:1、2、3、5和6(没有版本4)。不同版本具有不同的运行期部分,但新版本的运行期部分可与旧版本的应用程序配合,即向上兼容。当前大部分流行的游戏都是基于版本5开发的。


第二节 DirectX5 SDK的获得
DirectX SDK包括开发DirectX应用程序所需要到的全部示例和帮助文件,但这些都是可
选资源,必须的文件是头文件(.h文件)和库文件(.lib文件)。获得DirectX SDK比获得运行期部分要困难一些。Windows NT 4.0和Win95都不带DirectX SDK,要获得SDK可通过以下3种办法:
* 购买Visual c++5.0(包括DirectX SDK)
* 访问Microsoft Web站点的DirectX下载页
* 成为MSDN(Microsoft开发网络)用户
SDK 也可以从Microsoft Web站点上获得,下载量很大,尤其是在拨号连接时,有可能需要一整夜的时间。
成为MSDN用户是获取SDK的好办法,除非您反对通过Microsoft付费的方式获得升级及其操作系统的程序开发特权。SDK由MSDN level 2及以上提供。


第三节 元件对象模型(COM)
DirectX根据Microsoft的COM(Component Object Model,即元件对象模型)规格得以实现。COM设计成用来提供完全便携的、安全的、可升级的软件结构。COM使用一个面向对象的模型,这要比像C++等语言所用的模型更严格。例如,COM对象经常通过成员函数进行存取,而且并不具备公共数据成员。COM对继承性的支持与C++相比也是有限的。
很多DirectX中API是作为COM对象的实例来创建的。您可以这样看:对象就象一个黑盒子,它代表了硬件,从而需要通过一个接口与应用程序进行联络。通过COM接口发送和接收的命令被称为“方式(method)”。例如,方式IDirectDraw2::GetDisplayMode是通过接口IDirectDraw2发送,从而能够从DirectDraw对象得到当前显示适配器的显示模式。
COM对象提供实际函数性,而COM接口则提供存取函数性的方法。COM对象不能被直接存取,相反,所有的存取者通过接口来完成。这条规则是如此强有力的得到遵守,以致于我们不能给任何COM对象以名称。我们只能给用来存取对象的接口名称。
所有的COM接口都是从IUnknown接口中衍生出来的。“I”标志经常用于命名COM接口(它代表Interface即接口)。
IUnknown接口提供3个成员函数,所有COM接口因而继承这些函数:
●AddRef():使对象内部引用值加一。例如,当您创建一个新的接口时,构造函数将自动调用AddRef()。
●QueryInterface():让COM对象就它们是否支持特定接口进行查询。例如,升级的COM对象提供附加的接口,而非现有接口的修改版。QueryInterface()函数可以用来确定旧的接口,以决定新的接口是否被支持。如果被查询的对象不支持有问题的接口,则更替接口的指针就返回。
●Release():使对象内部引用值减一。您应该在接口的指针将要超出范围时或者要通过使用接口指针来结束时使用这个函数。
对象的每一个DirectX接口都代表了一种设备,例如IDirectDraw2、IDirectSound和IDirectPlay。DirectX对象模型象征性地为每一个设备提供了一个主对象,其它所支持的功能对象由主对象衍生而来。例如,DirectDraw对象代表了显示适配器,您可以由它创建DirectDrawSurface对象来代表显存。同样地,DirectSound对象代表声卡并可以创建DirectSoundBuffer对象代表声卡上的声音资料。
在C程序中可以调用任意的COM接口方式。下面的例子创建一个平面,使用DirectDraw对象的IDirectDraw2::CreateSurface方式:
ret = lpDD->lpVtbl->CreateSurface (lpDD, &ddsd, &lpDDS,
NULL);
这里lpDD表示与新建平面相关的DirectDraw对象。该方式填充一个平面类型的结构(&ddsd)并返回一个指向平面的指针(&lpDDS)。
同样功能用C++的实现为:
ret = lpDD->CreateSurface(&ddsd, &lpDDS, NULL)


第四节 DirectDraw
DirectDraw是DirectX SDK中的一个组件,它允许用户直接地操作显存,支持硬件覆盖、内存与显存反转。DirectDraw在提供这些功能的同时并支持基于Microsoft Windows的应用程序和设备驱动。DirectDraw作为一种软件接口,支持Windows的GDI(图像设备接口),并提供直接操作显示设备的功能。它提供了与设备无关的游戏、Windows子系统等软件开发方式。
它还能够支持许多种显示硬件,包括从简单的SVGA显示器到高级硬件性能(支持图像剪切、延伸和非RGB颜色格式)。接口的设计使您的应用程序能够知道所使用硬件的特性,从而可以支持它们的硬件加速功能。
关于DirectDraw,在以后还将有更加详细的讲解。


第五节 DirectSound
Microsoft DirectSound的API是DirectX平台软件开发工具(SDK)程序员指南的声音组件。DirectSound提供了硬件加速、对声音设备的直接操作。在提供这些功能的同时,它还保持了与当前设备驱动的兼容性。
新版本的DirectSound能够在运行中查询硬件性能以确定最优的操作方式,还具有抓获声音、低延迟混音等功能特性。它的新功能还包括支持属性集(Porperty Sets),从而即使DirectSound不直接支持新硬件的特性,它也能够利用并受益。
DirectSound是通过“硬件抽象层(HAL)”来操作声音硬件的,这是一个由声音设备驱动来实现的接口。HAL提供了以下功能:
要求和释放对声音硬件的控制;
描述声音硬件的特性;
在硬件允许的条件下实现特定的操作;
在硬件不允许的时候报告操作失败。
DirectSound能够自动地利用硬件加速,包括硬件混音和硬件声音缓冲。您的应用程序无须查询硬件和程序特性,而可以在运行期部分查询DirectSound来获得对声音设备特性的充分描述,然后根据各个特性的存在或缺少来使用不同的方法进行优化。您还可以指定接受硬件加速的声音缓冲区。
下图显示了DirectSound与系统中其它声音组件的关系:

关于DirectSound,在以后还将有更加详细的讲解。


第六节 DirectPlay
DirectPlay的主要作用在于使应用程序间通讯方式得以简单化。DirectPlay的技术不仅提供了一个方案,实现了通讯与传送协议及在线服务的无关性,而且还实现了与传输服务器和游戏服务器的无关性。万一所基于的网络不支持某一方式,DirectPlay包含了必要的代码来仿效它。
DirectPlay的service provider结构使应用程序与网络隔绝开来。应用程序通过查询DirectPlay来获得所基于网络功能特性(例如延迟、带宽等),从而相应地调整通讯方式。下面的图显示了DirectPlay service provider的结构:

使用DirectPlay的第一步是选择使用哪一种service provider,它决定了将用于通讯的网络或协议。协议可以是遍布Internet的TCP/IP、局域网中的IPX或两台计算机间的连接电缆。
DirectPlay提供了两种很有用的连接管理方式:
IDirectPlay3::EnumConnections列举了应用程序可以获得的所有连接;
IDirectPlay3::InitializeConnection初始化一种特定的连接。
在确定了网络的连接后,为了方便理解下面的内容,先来看看这样几个概念:
首先从session讲起。DirectPlay session是若干台计算机之间的通讯通道。一个应用程序只有在成为了某个session的一部分后才能够开始与其它计算机进行通讯。有两种方法可以实现:列举一个网络中所有存在的session然后加入其中的一个;或新建一个session并等待其它计算机的加入。当应用程序成为session的一部分后,它就可以创建一个player并与session中其它的player相互传递信息。
每一个session中都有一台计算机作为主机。主机是session的所有者并拥有唯一的改动session属性的权力。
下图显示了一个session模型:

Session有两种类型:对等类型和客户/服务器类型。
Player是session中逻辑概念上的对象,能够发送和接收消息。在DirectPlay Session中没有对实体计算机的表示方法。Player可以作为一个本地player(在您自己的计算机上),或作为一个远程player(在其它计算机上)。只有在拥有了至少一个player后,计算机才能够发送和接收消息。每一台计算机可以拥有不止一个本地player。
Group是在逻辑概念上是一些player的集合。通过创建一个group,一个应用程序可以向它发送消息使其中所有的player都收到。DirectPlay提供了管理group和成员关系的方式。
下图显示了一个session内容的逻辑结构:

假如,一台计算机在完成了连接之后,就可以调用IDirectPlay3::EnumSessions列举所有可以得到的session。然后,IDirectPlay3::Open根据session cache中的内容加入一个session,或新建一个session。
接下来,可以创建player和group。而在游戏中,更多的是通过Message Managment中的各个方式进行频繁地、大量的消息传送。
在不同类型的session中,消息的流向是不同的。在对等类型中,消息的传送是各个player之间直接进行的。对于使用multicast向group中传送消息时,则要在group中指定一个multicast server,通过它再将消息传送给group中其它的计算机。
在客户/服务器类型的session中,所有的player只与服务器进行直接通讯,再由它与其它各个客户机进行消息传送。下图表示了这样的关系:


第七节 DirectInput
DirectInput用以支持包括鼠标、键盘和游戏杆等在内的输入设备,甚至还有力度反馈的高级输入/输出设备。它也是基于COM的。
DirectInput对象由输入设备提供数据。每一个相应设备都有所谓“对象实例”,即个别的控件,如按键、按纽和游戏杆的轴向等。键盘上的每一个按键都可以作为键盘对象的一个对象实例。即使程序在后台运行,DirectInput也能够对输入设备做出响应。
一般来说,先要创建一个DirectInput对象(通过DirectInputCreat方式),表示一个DirectInput子系统。然后,列举出系统中可以得到的输入设备,再为这些个别设备创建DirectInputDevice对象(通过IDirectInput::CreateDevice方式)。这种对象的方式用于获取关于设备的信息,设置它们的属性并从它们那里得到数据。
下面给出一个使用鼠标的例子。您可以在光盘Example目录下的Scrawl.c文件中找到这些代码。
首先创建DirectInput鼠标设备

// LPDIRECTINPUT g_pdi; // 已被初始化
LPDIRECTINPUTDEVICE g_pMouse;
HRESULT hr;

hr = g_pdi->CreateDevice(GUID_SysMouse, &g_pMouse, NULL);

if (FAILED(hr)) {
Complain(hwnd, hr, "CreateDevice(SysMouse)");
return FALSE;
}

CreateDevice方式有三个参数:第一个是全局独有标志符GUID_SysMouse,这里表明为鼠标;第二个是IDirectInputDevice接口指针类型,如果这个调用成功,它就指向可用的鼠标;一般不使用COM聚合体,第三个参数为NULL。
然后,设置鼠标信息的格式:

hr = g_pMouse->SetDataFormat(&c_dfDIMouse);

if (FAILED(hr)) {
Complain(hwnd, hr, "SetDataFormat(SysMouse, dfDIMouse)");
return FALSE;
}

设置鼠标行为:

hr = g_pMouse->SetCooperativeLevel(hwnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND);
//后面的两个标志符实现了前台运行时对输入设备的独占
if (FAILED(hr)) {
Complain(hwnd, hr, "SetCooperativeLevel(SysMouse)");
return FALSE;
}

要使用事件通告(event notification)来发觉鼠标的动作并将经过缓冲的鼠标输入读进来,这都需要进行一些设置。首先,创建一个事件,将它与鼠标联系起来。当发生硬件中断时,就可以告诉鼠标设备对象有新的数据来到。

// HANDLE g_hevtMouse; // 全局变量

g_hevtMouse = CreateEvent(0, 0, 0, 0);

if (g_hevtMouse == NULL) {
Complain(hwnd, GetLastError(), "CreateEvent");
return FALSE;
}

hr = g_pMouse->SetEventNotification(g_hevtMouse);

if (FAILED(hr)) {
Complain(hwnd, hr, "SetEventNotification(SysMouse)");
return FALSE;
}

现在您需要设定缓冲区大小。在此之前要初始化一个DIPROPDWORD结构,这当中很多数量是无关紧要的,关键是最后一个,dwData,它决定了缓冲区中可容纳项目的数量。

#define DINPUT_BUFFERSIZE 16

DIPROPDWORD dipdw =
{
// the header
{
sizeof(DIPROPDWORD), // diph.dwSize
sizeof(DIPROPHEADER), // diph.dwHeaderSize
0, // diph.dwObj
DIPH_DEVICE, // diph.dwHow
},
// the data
DINPUT_BUFFERSIZE, // dwData
};

接着按照您希望改变的属性的标志符,将头地址传送给IDirectInputDevice::SetProperty方式:

hr = g_pMouse->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);

if (FAILED(hr)) {
Complain(hwnd, hr, "Set buffer size(SysMouse)");
return FALSE;
}

至此,设置结束。然后开始根据程序的要求操作鼠标。

第八节 Direct3D
Direct3D提供了与硬件无关的的3D视频硬件操作方式。您可以在两种模式(立即模式和保留模式)中选择其中之一使用。
立即模式较保留模式是一个低层的3D API,后者建构于前者之上。立即模式适合于开发高品质的多媒体应用程序,它在底层与加速硬件进行通讯。保留模式应用于对3D场景进行实时的操纵。
对3D场景的操作是基于矩阵和多边形的运算,这是创建和管理3D的基础知识,在很多资料中均可获得。
使用立即模式可以选择通过调用DrawPrimitive方式或利用执行缓冲区。对于初学者一般从调用DrawPrimitive方式。不过,千万不要认为两者有优劣之分。在熟悉了Direct3D后,使用哪个方法取决于您应用程序的要求。前者的方法与其它COM雷同;在利用执行缓冲区的方法时,要创建DirectDraw和Direct3D对象,设置渲染状态,填充执行缓冲区等。
无论哪种模式,应用程序与硬件的通讯都很类似。正如下图所示。由于Direct3D相当于DirectDraw对象的一个接口,这里的HAL被表示为DirectDraw/Direct3D HAL。

对保留模式的操作是通过使用一些对象来实现的。Direct3D和DirectDraw是紧密联系在一起的。一个DirectDraw对象将DirectDraw和Direct3D状态封装起来,通过IDirectDraw::QueryInterface方式将IDirect3D接口转换为DirectDraw对象。
有一个很重要的概念是z缓冲区。它决定了将很多显示内容如何覆盖和裁剪。如果没有z缓冲区,保留模式无法给覆盖层排序。没有指定z顺序的覆盖层被缺省设定为0,处于最底层。一共可以有四十亿个覆盖层(应该够用了吧!),z顺序为2的层将可能遮掩了z顺序为1的层的某些内容。记住,不能有两个层的z顺序相同。
关于3D场景,还有诸如材资、光源等概念,在此不再一一聱述。

第九节 Vc++中引入Direct SDK
一旦安装了SDK,就得马上通知Visual C++ SDK的位置。默认状态下,SDK安装在dxsdk目录下。头文件放在dxsdk/inc目录下,库文件放在dxsdk/lib目录下。

可利用下述两种方法之一通知visual C++ SDK的位置。一种方法是在使用文件时给出完整的文件路径;另一种法是将这些目录加到Visual C++的搜索路径中。第二种方法更好一些,可以通过Tools[Options]Directories对话框实现。
增加dxsdk/lib目录的方法大体上同增加dxsdk/inc目录的方法相同。

如果你获得了一个含有比Visual C++的DirectX SDK新的版本,您需要将DirectX SDK目录置于常规的Visual C++目录上面。否则就得使用旧版本。(Visual C++从上至下查找目录)

根据我们已经讨论过的内容,你应该能够编辑DirectX程序了。然而还有最后一个潜在的障碍。除非INITGUID符号已经被定义,否则在DirectX GUIDs下调用Query-Interface()函数的程序同DirectX2 SDK的链接会失败。INITGUID符号只能由一个源文件定义,并且必须出现在#include语句之前,如下列代码所示:

#define INITGUID
#include〈ddraw.h〉
//…other includes…
  
对于DirectX3及以上版本,这种解决方法都是有效的,但还有一个更好的方法,即将dxguid.lib文件链接到你的工程上(Build[Settings]Link对话框),以替代INITGUID符号的定义。
 


 


游戏开发基础(4)



第四章 diectxdarw基础篇
第一节 DirectDraw简介
Grubers的一个观点是DirectDraw“只是一个bltting发动机”。这是相当准确的,但却太简化了。更准确地讲,DirectDraw是一个可以提供软件仿真测试的独立于硬件设备的bltting发动机。DirectDraw的主要用途是尽可能快、尽可能可靠并且尽可能连续地将图形考贝到视频显示设备上。
  另外一个定义DirectDraw的方式是把它作为一个视频存储器管理器,同常规的存储器管理器一样,DirectDraw发放存储器信息包,跟踪每一个信息包的状态。信息包可以随意地创建、复制、修改或破坏,同时这些操作的细节被程序员隐含起来,这样讲是过于简单了。此外,DirectDraw是能够使用系统RAM和视频RAM的。存储器管理器也经常被设计成和主要目标一样强健,而不只是追求性能。对于DirectDraw,性能只是设计目标之一。
从技术角度讲,DirectDraw是随同设备驱动器集合的便携式API。DirectDraw设计成完全避开传统意义上的Windows图形机构(GDI,或称图形设备接口)。GDI由于性能低而名声不好,所以DirectDraw的设备独立性在提供最佳性能方面是至关重要的。

第二节 DirectDraw基本概念
1. 显示模式
显示模式是由允许将要显示的图形输出的显示硬件支持的可视配置。最常用的显示模式属性是分辨率。Windows使用的显示模式的默认值是640×480的分辨率。这意味着,水平方向有640个像素,垂直方向有480个像素。其他一些常见的显示模式分辨率有800×600,1024×768。一些显示卡支持Mode X显示模式。一个典型的Mode X显示模式的分辨率为320×200。
  显示模式也随像素深度的变化而变化。像素深度决定着每一个像素所容纳的多少不同的值,因而也就可以显示多少种颜色。例如对于8位像素深度的显示模式,每个像素能够再现256种颜色的一种。像素深度为16位的显示模式支持65536种颜色(即2的n次方),典型的像素深度为8、16、24和32位。
  显示模式由安装在机器中的显示设备或视频卡支持。显示设备有自己的RAM,从计算机的RAM中分离出来。我们把位于显示设备中的存储器称为显示RAM,而把常规存储器称为系统RAM。
支持一个给定的显示模式的RAM的容量取决于显示模式的分辨率和像素深度。例如,640×480×8(640×480像素,深度为8位)的显示模式需要307200字节。1024×768×16的显示模式需要1572864字节。支持显示模式的存储器必须是显示RAM。一个给定显示设备所支持的显示模式因此也被可以利用的显示RAM的容量所限制。例如,1024×768×16的显示模式因为需要一兆字节以上的内存,所以就不能被只有一兆字节RAM的显示设备所支持。
DirectDraw的一个主要特征是显示模式切换。这允许一个DirectDraw应用程序检测和激活所安装的显示设备所支持的任何显示模式。我们将在第4章中讨论显示模式切换的细节。
2. 硬件加速
DirectDraw具有最优性能的最重要的原因是,它尽可能地使用硬件加速来进行设计。硬件加速发生在当显示设备能够用建立在显示设备之中的处理功能执行操作时。硬件加速具有两个优点,首先,当硬件加速出现的时候,硬件按指定要求设计成支持图形操作,这提供了执行给定任务的最快的方法:其次,硬件加速使得计算主处理器从执行操作中解放出来,这使得主处理器可以执行其他任务。
3. 表面
表面是存储器的一个矩形部分的DirectDraw术语,通常包括图像数据。该存储器通常用来表示一个存在于显示RAM或系统RAM中的表面。驻留在显示RAM中的表面享有超高性能,因为绝大多数显示硬件不能对系统RAM直接存取。
表面分为向大类,最简单的类型是脱离屏幕表面。脱离屏幕表面可以驻留在显示RAM中或系统RAM中,但却不能被显示。这类表面一般用于存储子画面和背景。
另一方面,一个主表面是可在屏幕上看到的视频RAM的一部分部分。所有的DirectDraw程序(可以提供视频输出)都拥有主表面。主表面必须驻留在显示RAM中。
主表面通常很复杂,或是可翻转的。可翻转表面允许页面翻转,这是一项整个表面的内容可以通过一个硬件操作而瞬时可见的技术。页面翻转用于许多基于DirectDraw或其他的图形应用程序中,因为它可以产生相当平滑、不闪烁的动画。一个可翻转的主表面实际上是两个表面,一个可见,另一个不可见。不可见的表面称为后备缓冲区。当发生页面翻转时,以前是后备缓冲区的表面就成为可见的,而以前可见的表面则成为后备缓冲区。
  离屏表面和主表面都有两类:调色板的和无调色板的。在DirectDraw中,只有8位表面是调色板表面。调色板表面并不包含色彩数据,但是却引入一个色彩表。该表称为调色板。像素深度为16、24或32位的表面是无调色板表面。无调色板表面存储实际色彩值,而不引入调色板。
因为在无调色板表面中的每一个像素都存储色彩数据,所以知道表面的像素格式是很重要的。像素格式描述了存储于像素中的红色、绿色和蓝色(RGB)元件的方式。像素格式随像素深度、显示模式和硬件设计的不同而不同,在第5章中可以了解所有的像素格式。
4. Bltting
Bltting是用于复制的图形语言。典型的blt操作是将离屏表面的内容拷贝到一个后备缓冲区中。当Bltting通过硬件完成的时候,执行速度相当快。如果无法得到硬件加速,DirectDraw将使用一个软件操作来仿真blt。这种仿真操作虽然也能够完成任务,但却比硬件慢得多.一般只有驻留在显示RAM中的表面能够通过使用显示硬件来完成blt。
  blt操作调用一个源表面和一个目标表面,源表面的内容被拷贝到目标表面中。源表面中的内容在操作中不会改变,只有目标表面受blt的影响。blt操作也并不需要使用全部的源表面或目标表面。源表面中的任何矩形区域可以被放置于目标表面中的任何位置。
不规则形状表面的bltting(例如典型的子画面)是以透明方式完成的。透明性是通过指定表面中某个不被blt操作拷贝的像素而获得的。像素值通过使用色彩键码给以标志。
色彩键码可以附加到源表面或目标表面上。源色彩键码是很普遍的。源色彩键码允许透明性,因为源表面中的像素值并未被考贝。至于目标色彩,只有目标表面中通过色彩所指定的像素值能够被源表面的内容所覆盖。
DirectDraw也支持一些特定的操作,包括拉伸、压缩、镜像映射,以及混合等。这些功能的实现往往取决于显示硬件。DirectDraw能够仿真其中的某些操作,但是跟性能相比,价格往往是昂贵的。
DirectDraw也有不能仿真的功能(例如目标色彩键码)。使用这些功能是冒险的,除非该功能为所安装的显示硬件支持,否则使用该功能的操作将失败。这给DirectDraw的开发者带来两种基本选择:要么放弃使用这些功能:要么往应用程序中增加定制软件。
5. 调色板
  使用8位显示模式的应用程序需要提供调色板。调色板就是任何时候都可以使用的色彩表。如果8位显示模式不需要调色板,应用程序将被迫使用256种颜色的固定设置。调色板允许用户定义将要使用的256种颜色之一。
  当你使用调色板显示模式时,必须保证在应用程序中的图像也使用同一调色板。如果没有做到这一点,所显示的一些或全部图像中将出现错误的颜色。调色板也会带来麻烦,尤其是用一个调色板来显示大量图像的时候。调色板也有一些优势。正如前面提到的,调色板允许在一个有限色彩的场合使用最多的色彩。调色板也允许调色板动画。
  调色板动画是动画通过改变调色板项目,而不是改变像素值来执行的技术,这就使得一个屏幕上的很多像素可以瞬时改变颜色。对于一些有限的应用程序,诸如分配的、重复的动画,调色板动画很有用处。
6. 剪裁
理想状态下,一个blt操作就是整个表面被blt成为另一个表面。通常源表面被blt成为目标表面的边,或者目标表面被另一个表面或窗口遮蔽。像这样的情况就需要进行剪裁。剪裁只允许一部分或一个表面的一部分被blt。
在编写窗口DirectDraw应用程序时经常用到剪裁,因为这些应用程序必须遵守Windows桌面的规则。我们将在本章后面讨论窗口应用程序。
DirectDraw提供全矩形剪裁支持。也有这种情况,就是付费提供定制剪裁例程,我们将在第3章中研究定制剪裁解决方案。
7. 其他表面
离屏表面和主表面(具有任选的后备缓冲区)是绝大多数DirectDraw应用程序的主干。然而一些其他的表面就有不同,包括重叠表面、alpha通道表面、Z-缓冲区以及3D设备表面等。
重叠表面是硬件单色画面,因而也就在仅在支持重叠的显示硬件上获得。和软件单色画面不同,它可以被移动而不需要背景图像被恢复。
alpha通道表面用来执行alpha调配。Alpha调配是透明的高级形式。允许表面以透明度或半透明方式来拷贝。alpha通道表面可用来控制每一像素的透明度设置。alpha通道表面的深度有1、2、4、8位。1位深度alpha通道表面仅支持两种透明设置,不透明(非透明)或不可见(全透明)。另一方面,8位alpha通道表面允许256种不同的透明度设置。Alpha调配是不被DirectDraw仿真的功能的一个例子。为了使用alpha调配,因而就需要有支持它的显示硬件或建立在应用程序之中的定制调配方案。
Z-缓冲区和3D设备表面用于3D应用程序中。这些类型的表面已被特别地加入到DirectDraw之中,以支持Direct3D。Z-缓冲区用于景象绘制时期,以跟踪景象中离浏览者最近的对象,从而该对象可以在其他对象的前面出现。3D设备表面可以用来作为Direct3D绘制目标的表面。本书并不包括Z-缓冲区或3D设备。

第三节 元件对象模型(COM)
1.Microsoft的COM规格
DirectDraw根据Microsoft的COM(Component Object Model)规格得以实现。COM设计成用来提供完全便携的、安全的、可升级的软件结构,COM是一个大的项目,但是它并不是本软件讨论的对象。我们讨论COM只是为了方便使用DirectDraw进行编程。
COM使用一个面向对象的模型,这要比像C++等语言所用的模型更严格。例如,COM对象经常通过成员函数进行存取,而且并不具备公共数据成员。COM对继承性的支持与C++相比也是有限的。
2. 对象和接口的比较
COM在对象和接口之间具有很大的区别。COM对象提供实际函数性,而COM接口则提供存取函数性的方法。COM对象不能被直接存取,相反,所有的存取者通过接口来完成。这条规则是如此强有力的得到遵守,以致于我们不能给任何COM对象以名称。我们只能给用来存取对象的接口名称。因为我们无法存取COM对象,所以这里绝大多数时候是根据接口来讲的。
一个COM对象能够支持多个接口。这听起来像个特例,但是它经常出现,因为根据COM规格,一个COM接口一旦定义之后,就不能再被改变或增加。这样做是为保证旧程序在一个COM对象升级的时候不会被停止使用。这个初始接口始终存在,一个新的、替换的接口在提供存取对象的新的函数性的时候才被提供。
3. IUnknown接口
所有的COM接口都是从IUnknown接口中衍生出来的。“I”标志经常用于命名COM接口(它代表Interface即界面)。DirectDraw接口总以“I”开头。但是在文献中经常看不到这个标志。以后提到接口时也将省略“I”标志。
  IUnknown接口将提供3个成员函数,所有COM接口因而继承这些函数:
●AddRef()
●Release()
●QueryInterface()
AddRef()和Release()成员函数为称为生命期封装(lifetime encapsulation)的COM功能提供支持。生命期封装是一个将每一个对象根据它自己的结构放置的协议。
生命期封装通过引用值来实现。每一个对象拥有一个可以跟踪对象的指针数,或者引用的内部值。当对象创建之后,该值为1。如果附加的接口或接口的指针被创建,则该值递增。与此类似,如果接口的指针被破坏,则该值递减。当它的引用数到0的时候,该对象自行破坏。
AddRef()函数用来使对象的内部引用值递增。绝大部分时间里,该函数通过DirectDraw API被用户调用。例如,当你使用DirectDrawaw API创建一个新的接口时,创建函数就自动调用AddRef()。
Release()函数用来给对象的内部引用值递减。用户应该在接口的指针将要超出范围时或者要通过使用接口指针来结束时使用这个函数。AddRef()和Release()函数都返回一个值,表示对象新的引用值。
QueryInterface()函数允许COM对象就它们是否支持特定接口进行查询。例如,升级的COM对象提供附加的接口,而非现有接口的修改版。QueryInterface()函数可以用来确定旧的接口,以决定新的接口是否被支持。如果被查询的对象不支持有问题的接口,则更替接口的指针就返回。
4. GUID
为了查询一个对象是否支持使用QueryInterface()函数的指定接口,就有秘要识别有问题的接口。这通过接口的GUID(Globally Unique IDentifier)来实现。一个GUID是一个128位的值,也就是说,对于所有意图和目的是唯一的。所有DirectDraw接口的GUIDs都包含在DirectX头文件中。
上述对于COM的简单介绍,就是为有效使用DirectDraw API所需要的全部内容。以后当我们再讨论DirectDraw API时,你会发现这些内容是有联系的。

第四节 DirectDraw接口函数
1.关于 DirectDraw API
衡量API的一个方法就是看它的大小。一个庞大复杂的API可能就是计划不周的结果。另一方面,一个庞大的API有时就意味着每一种情况都有可能出现。一个小的API就是一个新的、缺乏功能的软件包的证据。它也意味着,一个API只能做它所需要做的,而不能多做一点。
DirectDraw API是比较小的,因此本章中所讨论的每一个函数不致于使本章看起来像一本参考手册。DirectDraw提供很少的方便,也很少有限制。
DirectDraw由个COM对象构成,每个对象可以通过一个或多个接口存取。这些接口包括:
●DirectDraw
●DirectDraw2
●DirectDrawSurface
●DirectDrawSurface2
●DirectDrawSurface3
●DirectDrawPalette
●DirectDrawClipper
我们将讨论每一个接口,并随后讨论它们的成员函数。但我们并不讨论每个函数的细节,因为我们并不是向您提供一份参考手册。相反,我们将讨论每个函数是干什么的,为什么这样使用,以及你有可能如何去使用它。
当DirectX首次推出的时候(早先它被称作Games SDK),DirectDraw核心函数性以DirectDraw接口表示。当DirectX2推出的时候,DirectDraw也已经被升级了。DirectDraw遵守COM规格而未被改变。新的函数性可能通过DirectDraw2接口存取。
特别要注意的是,DirectDraw2接口是DirectDraw接口的超级设置。DirectDraw2接口可提供DirectDraw接口的所有函数,另外还增加了一些新的函数。如果你正在使用DirectX或更高版高,那么你可以随意选用DirectDraw接口或DirectDraw2接口。但是,由于DirectDraw2接口较DirectDraw接口的功能更强,所以没有必要使用DirectDraw接口。同样,Microsoft并不主张使用这些无组织的、网络可变的接口。因此,在本书以后的程序中我们只使用DirectDraw2接口。
DirectDraw和DirectDraw2接口提供的成员函数如下(按字母顺序排列):
●Compact()
●CreateClipper()
●CreatePalette()
●CreateSurface()
●DuplicateSurface()
●EnumDisplayModes()
●EnumSurfaces()
●FlipToGDISurface()
●GetAvailableVidMem()
●GetCaps()
●GetDisplayMode()
●GetFourCCCodes()
●GetGDISurface()
●GetMonitorFrequency()
●GetScanline()
●GetVerticalBlankStatus()
●RestoreDisplayMode()
●SetCooperativeLevel()
●SetDisplayMode()
●WaitForVerticalBlank()
接下来我们讨论DirectDraw接口函数。注意,在本章以后的内容中,DirectDraw接口既表示DirectDraw接口,也表示DirectDraw2接口。只有在区分DirectDraw接口和DirectDraw2接口的函数时,才加以区别。
1. 接口创建函数
DirectDraw接口表示DirectDraw本身。该接口在被用于创建其他DirectDraw接口实例时,是一个主接口。DirectDraw接口提供三个这样的接口实例创建函数:
●CreateClipper()
●CreatePalette()
●CreateSurface()
CreateClipper()函数用于创建DirectDrawClipper接口实例。并非所有的DirectDraw应用程序都用到剪裁器,所以该函数并不是在所有的程序中都出现。我们将很快讨论DirectDrawClipper的细节。
CreatePalette()函数用于创建DirectDrawPalette接口实例。同DirectDrawClipper一样,并非所有的DirectDraw应用程序都用到调色板。比如,应用程序使用16位显示模式时,就不用调色板。但是,当应用程序使用8位显示模式时,就必须创建至少一个DirectDrawPalette实例。
CreateSurface()函数用于创建DirectDrawSurface接口实例。任何一个DirectDraw应用程序都要用表面来生成图像数据,因此经常要用到这一函数。
DirectDraw接口自己的实例是由DirectDrawCreate()函数创建的。DirectDrawCreate()是DirectDraw函数中少有的几个常规函数之一,但并不是COM接口成员函数。
2. GetCaps()函数
DirectDraw接口允许准确确定软硬件都支持的特征。GetCaps()函数可以对两个DDCAP结构实例进行初始化。一个结构表明哪些特征由显示硬件直接支持,另一个结构表明哪些特征由软件仿真支持。最好是用GetCaps()函数来决定你将用到的特征是否被支持。
提示:DirectX浏览器
DirectX SKD是与DXVIEW程序同时推出的。DXVIEW说明了DirectX组件的功能,包括DirectDraw。大多数系统中,有两个DirectDraw项目:主显示驱动器和硬件仿真层。第一项说明了显示硬件的功能。第二项说明了在缺乏硬件支持的情况下,DirectDraw将要仿真的一些特征。在具有两个以上的DirectDraw支持的显示卡的计算机中,DXVIEW会显示卡的功能。
3. SetCooperativeLevel()函数
SetCooperativeLevel()函数用于指定应用程序所要求的对显示硬件的控制程度。比如,一个正常合作度意味着应用程序既改变不了当前显示模式,也不能指定整个系统调色板的内容。而一个专有的合作度允许显示模式切换,并能完全控制调色板。不管你决定使用哪种合作度,都必须调用SetCooperativeLevel()函数。
4. 显示模式函数
DirectDraw接口提供4种显示模式操作函数。它们是:
●EnumDisplayModes()
●GetDisplayMode()
●RestoreDisplayMode()
●SetDisplayMode()
EnumDisplayModes()函数可用于查询DirectDraw使用何种显示模式。通过设置EnumDisplayModes()函数默认值可以得到所有的显示模式,而且可以通过显示模式描述消除那些不感兴趣的模式。进行显示模式切换的过程中最好使用EnumDisplayModes()函数。现在市场上有各种各样的显示设备,每种显示设备都有自己的特征和局限。除了默认的640×480×8窗口显示模式,最好不要依靠任何给定的显示模式的支持。
  GetDisplayMode()函数可以检索到有关当前显示模式的信息,并在DDSURFACEDESC结构实例中显示当前显示模式的宽度、高度、像素深度以及像素格式等信息。还有别的途径可以检索到同样的信息(比如检索主表面描述),因此该函数并不出现在所有的程序中。
  SetDisplayMode()函数用于激活所支持的显示模式。SetDisplayMode()函数的DirectDraw2版本还允许设定显示模式的刷新率。而DirectDraw接口版本的SetDisplayMode()函数只能进行显示模式宽度、高度和像素深度的设置。任何一个要进行显示模式切换的程序都要用到SetDisplayMode()函数。
RestoreDisplayMode()函数用于存储调用SetDisplayMode()函数之前的显示模式。SetDisplayMode()和RestoreDisplayMode()函数都要求优先使用SetCooperativeLevel()函数得到的专有合作存取。
5. 表面支持函数
除了CreateSurface()函数之外,DirectDraw接口还提供了以下向个表面相关函数:
●DuplicateSurface()
●EnumSurfaces()
●FlipToGDISurface()
●GetGDISurface()
●GetAvailableVidMem()
●Compact()
DuplicateSurface()函数用于考贝当前表面。该函数只复制表面接口,不复制内存。被复制的表面与源表面共享内存,因此改变内存的内容就同时改变了两个表面的图像。
EnumSurfaces()函数可用于迭代所有满足指定标准的表面。如果没有指定标准,那么所有当前表面都被枚举。
FlipToGDISurface()函数的作用是在终止页面翻转应用程序前确保主表面得以正确存储。取消页面翻转时,有两个表面交替显示。这就是说,在终止应用程序之前有可能没有保存最初的可见表面。这种情况下,Windows通过绘制一个不可见表面来恢复。利用FlipToGDISurface()函数就可以轻而易举地避免发生这种情况。
GetGDISurface()函数可以向只被GDI认可的表面返回一个提针。GDI表面是Windows用于输出的表面。在进行屏幕捕捉时,这个函数非常有用,DirectDraw可以捕捉到Windows桌面的任一部分。
GetAvailableVidMem()函数用于检索正在使用中的视频存储器(显示RAM)的数量。这一函数由DirectDraw2接口提供,而不是由DirectDraw接口提供。该函数用于确定应用程序利用显示RAM可创建表面的数量。
Compact()函数不是通过DirectX5实现的,但它可以为视频存储器提供碎片整理技巧。在基于显示RAM的表面被不断创建或受到破坏的时候,可以释放大量内存。
6. 监视器刷新函数
DirectDraw接口提供了4种适于计算机显示设备或监视器的函数,但这些函数不适于显示卡,它们是:
●GetMonitorFrequency()
●GetScanLine()
●GetVerticalBlankStatus()
●WaitForVerticalBlank()
这些函数尤其与监视器的刷新机制紧密机连。这在确保生成动画时尽可能不产生闪烁和图像撕裂现象时是至关重要的。但必须注意,并非所有的显示卡/监视器组合都支持这些函数。
GetMonitorFrequency()函数用于检索监视器当前的刷新率。刷新率通常用赫兹表示,缩写为Hz。例如,60Hz的刷新率表示屏幕每秒更新60次。
GetScanLine()函数用于向监视器返回当前正在被刷新的扫描行(水平像素行)。不是所有的显示设备/监视器组合都支持该函数。如果这一功能得不到支持,该函数将返回DDERR-UNSUPPORTED。
对于高性能图形应用程序来说,通常要求利用垂直刷新同步地更新屏幕。尤其是,当显示器刚完成屏幕刷新时,最好能够更新主表面。否则,屏幕的一部分显示新的图像数据,而另一部分仍显示旧的图像数据,这种现象就是所谓的图像撕裂。DirectDraw默认利用垂直刷新同步更新屏幕。如果不是这样还可以利用GetVerticalBlankStatus()和WaitForVerticalBlank()函数实现同步刷新。
7. GetFourCCCodes()函数
DirectDraw接口提供的最后一个函数是GetFourCCCodes()函数。该函数用于返回显示卡所支持的FourCC代码。FourCC代码用于描述非RGB或YUV表面。我们不在此讨论YOV表面,它们已超出本书的范围。
第五节 DirectDrawSurface接口函数
同DirectDraw接口一样,DirectDrawSurface接口也遵守COM规格.最初,表面支持是由DirectDrawSurface接口提供的。DirectX2介绍了DirectDrawSurface2接口的新的函数性,DirectX5介绍了DirectDrawSurface3接口。
尽管本软件中讨论的是DirectDraw2接口,而不是DirectDraw接口,但我们仍忠于最初的DirectDrawSurface接口,因为DirectDrawSurface2和DirectDrawSurface3接口新增的函数并不十分重要。在以后的内容里,我们将用DirectDrawSurface接口表示这3种接口,除非特别注明。
DirectDrawSurface是最大的DirectDraw接口,它允许表面内容的拷贝、清除以及被调用程序直接存取。DirectDrawSurawSurface接口总共提供36个成员函数,按字母顺序排列如下:
●AddAttachedSurface()
●AddOverlayDirtyRect()
●Blt()
●BltBatch()
●BltFast()
●DeleteAttachedSurface()
●EnumAttachedSurfaces()
●EnumOverlayZOrders()
●Flip()
●GetAttachedSurface()
●GetBltstatus()
●GetCaps()
●GetClipper()
●GetColorKey()
●GetDC()
●GetDDInterface()
●GetFlipStatus()
●GetOverlayPosition()
●GetPalette()
●GetPixelFormat()
●GetSurfaceDesc()
●IsLost()
●Lock()
●PageLock()
●PageUnlock()
●ReleaseDC()
●Restore()
●SetClipper()
●SetColorKey()
●SetOverlayPosition()
●SetPalette()
●SetSurfaceDesc()
●Unlock()
●UpdateOverlay()
●UpdateOverlayDisplay()
●UpdateOverlayZOrder()
1. 表面描述函数
我们首先讨论的个可用于检索表面自身信息的函数,它们是:
●GetCaps()
●GetPixelFormat()
●GetSurfaceDesc()
●SetSurfaceDesc()
同DirectDraw接口提供的GetCaps()函数一样,DirectDrawSurface接口提供的GetCaps()函数用于输出表征哪些特征可被表面支持的数据。该信息包括:表面是主表面还是离屏表面;表面使用的存储器定位于显示RAM还是系统RAM。
GetPixelFormat()函数在用于高彩和真彩表面时是非常重要的,这是由于像素格式因显示卡的不同而不同。该函数返回表征码,这些表征码表明每一种颜色部件是如何存储的。
GetSurfaceDesc()函数返回一个表面描述。该信息包括表面的宽度、高度和深度。表面像素格式(同样被GetPixelFormat()函数检索)也包含在其中。
SetSurfaceDesc()函数(对于DirectX5)来讲是新增的,只由DirectDrawSurface3接口提供)允许设定某些表面属性。该函数可用于指定表面使用的内存。这一点在设计定制表面存储器管理器策略时非常有用。
2。 表面Blt函数
DirectDrawSurface接口提供3个支持blt操作的函数:
●Blt()
●BltBatch()
●BltFast()
Blt()函数是一个主要函数。Blt()函数能够进行常规的blting(无特殊影响的简单的表面到表面的blt),同时支持延伸、旋转、镜像和颜色填充的操作。当用于同剪裁器关联的表面时,Blt()可进行剪裁blt操作。
BltBatch()函数不是在DirectX3下实现的(你可以调用该函数,但什么也不会发生)。执行BltBatch()函数时,如果可能,它可同时进行多blt操作。
BltFast()函数是Blt()函数的优化版本。BltFast()函数的效率提高了,但性能却下降了。BltFast()函数不能进行一些特殊的blt操作,而Blt()函数可以。而且,BltFast()函数不能用于剪裁。但是BltFast()函数支持源和目标色彩键码blt的操作。在遵循定制剪裁例程的情况下,BltFast()函数可进行DirectDraw能够提供的最快捷、灵活的blt操作。在下章中我们将执行一个定制剪裁例程。
以上3个blt函数均将源表面和目标表面作为变量。其他的数据,例如blt在目标表面上的理想定位,是通过指定理想blt操作的确切属性来提供的。一旦可能,这3个函数将进行硬件加速blt。
3. Flip()函数
Flip()函数用于页面翻转操作。调用Flip()函数可隐藏屏幕上先前可见的表面,并使一个后备缓冲区显现。只有被明确地创建为翻转表面的表面,才响应该函数的调用。
必须牢记,真正的翻转操作不可能总是成功。页面翻转要求有足够的显示RAM容纳两整屏有效数据。如果满足不了这一要求,系统RAM中将创建一个后备缓冲区。这时调用Flip()函数进行的是blt操作而不是页面翻转。基于系统RAM的后备缓冲区中的内容被拷贝到主表面上。这样会严重影响性能,但是,在真正的页面翻转中如果没有足够的显示RAM,又不退出程序,也只能如此了。如果你的应用程序要求最佳性能,就得设法避免激活不能进行真正页面翻转的显示模式。
4. 表面状态函数
下面讨论两个能检索有关操作和翻转操作信息的函数,它们是:
●GetBltStatus()
●GetFlipStatus()
GetBltStatus()函数用于确定当前是否进行blt操作。这一点很重要,因为正被blt的表面不能进行其他操作。该函数表明,给定的表面是否正是一个进行blt操作的源表面或目标表面。
同样地,GetBltStatus()函数表明是否正在进行翻转操作。即使DirectDraw通过blt操作仿真页面翻转,该函数而不GetBltStatus()也必须用于由Flip()函数初始化的监视器页面翻转。
5. 色彩键码函数
DirectDrawSurface接口提供了以下两个函数,来设置和检查表面色彩键码或色彩键码的范围,它们是:
●GetColorKey()
●SetColoKey()
默认状态下,表面没有色彩键码。一个色彩键码只对应一种颜色,但某些硬件支持色彩键码范围。色彩键码和色彩键码范围是DDCOLORKEY结构定义的。GetColorKey()和SetColoKey()函数都将该结构的指针作为变量。在要求表面的一部分透明时或需要进行目标色彩键码操作时,可以使用这两个函数。
6. Lock和Unlock()函数
DirectDraw的一个主要特点,就是能够提供对图像数据的直接存取。直接存取可以提供最佳性能和更好的灵活性,因为没有中间API影响运行速度,并且开发人员呆任意使用图像数据。对表面存储器的中间存取通过以下出众个函数实现:
●Unlock()
●Lock()
Lock()函数向组成表面的存储器返回一个指针,不管表面存储器位于显示RAM还是系统RAM。存储器一般按线性风格排列,以便能简单地进行图像 数据存取。Unolock()函数在完成表面存储器的存取之后指定给DirectDraw。
对图像数据的直接存取必须付出代价。为了支持这种存取方式,DirectDraw在表面锁定的时候必须关闭基本的Windows机构。在Windows95状态下,如果忘记解锁表面,必定会损坏机器。
因此,表面锁定的时间应尽量缩短。测试前应仔细检查Lock()和Unlock()函数之间的程序调用。因为这一程序无法用传统的调试程序进行调试。
锁定表面不能被blt和翻转,因此试图保持表面处于锁定状态没有任何有益之处。而且,一旦表面解锁,由Lock()函数检索的指针就失效了。
表面锁定后不能再次被锁定。在表面锁定时也就无法调用Lock()函数。
7. GetDC()ReleaseDC()函数
对表面的直接存取占用很大内存,有时候把表面作为一个常规的Windows设备会更好。在此,DirectDrawSurface接口提供以下两个函数:
●GetDC()
●ReleaseDC()
GetDC()函数提供了一个DC(设备环境),可以用常规的Win32函数写到表面上。例如,DC可以用Win32的TextOut()函数在表面上绘制文本。用完DC后必须马上调用ReleaseDC()函数。
同Lock()和Unlock()函数使用一样,在调用完GetDC()函数后必须马上调用ReleaseDC()函数。这是因为GetDC()函数内部调用Lock函数,而ReleaseDC()函数内部调用Unlock()函数。
8. PageLock()和PageUnlock()函数
接下来,我们讨论另外两个与Lock()函数和Unlock()函数看上去非常相像的函数:
●PageLock()
●PageUnlock()
尽管这两个函数看上去很像Lock()和Unlock()函数,但它们却有完全不同的作用。PageLock()和PageUnlock()函数用于控制Windows对基于系统RAM的表面的处理方式。这两个函数由DirectDrawSurface2接口提供,而不是由DirectDrawSurface接口提供。
当Windows认为当前正在运行的其他应用程序或进程更适于使用内存时,Windows会向硬盘释放部分内存。这种缓冲对于整个系统内存都起作用,因此驻留在系统内存中的DirectDraw表面有可能被存到硬盘上。如果要用到这样的表面,Windows需要花费一定的时间从硬盘上读取表面数据。
PageLock()函数提示Windows哪些给定的表面不应该释放到硬盘上。这样,在使用表面时就不用耗费时间进行硬盘存取了。相反地,PageUnlock()函数用于告知Windows哪些表面内存可被释放。
过程调用PageLock()函数会减少缓冲内存的总量,从而导致Windows的速度大大降低。至于这种情况何时发生,取决于页面锁定系统内存量及机器提供的系统内存量。
PageLock()和PageUnlock()函数主要是由DirectDraw提供而非DirectDraw应用程序。举个例子来说,DirectDraw自动使用PageLock()函数,以确保运行blt操作时,基于系统RAM的表面不被释放到硬盘。
PageLock()函数可以被同一个表面多次调用。DirectDraw用参考计数法记录PageLock()函数被调用的次数,因此多次调用PageUnlock()函数就必须避免多次调用PageLock()函数。
PageLock()和PageUnlock()函数对于驻留在显示RAM中的表面不起作用。
9. IsLost()的Restore()函数
现在讨论两个与使用驻留在显示RAM中的表面有关的函数:
●IsLost()
●Restore()
让我们来看一看下面这种情况。应用程序正在运行时,尽量把表面分配到显示RAM中,剩下的创建到系统RAM中。应用程序在运行一段时间之后,用户执行或切换到另一个应用程序。该应用程序是任意的,可以是一个常规的Windows程序,如Windows开发程序或记事本。它也可以是另外的DirectDraw应用程序,该程序也试图将尽可能多地分配显示RAM。如果DirectDraw不接受显示RAM,那么新的应用程序就很可能根本运行不了。相反的情况就意味着,应用程序不允许分配到任何显示RAM中。
因此,DirectDraw可以随意将任何一个或者所有的基于显示RAM的表面从非激活应用程序中移走。这种情况就是所谓的表面丢失。从技术上讲,程序仍具有表面,但它们不再同任何内存相关。要使用丢失的表面会导致DDERR-SURFACELOST错误。IsLost()函数可用于确定一个表面是否丢失了内存。
表面内存丢失后可通过Restore()函数恢复,但只能在应用程序被重新激活之后才可恢复。这会导致应用程序无法将处于最小化状态的所有表面复原。
Restore()函数可恢复附属于表面的任一内存,但并不恢复内存的内容。表面被复原后,应用程序就可以恢复表面内容了。
注意,这种用法不适合利用系统RAM创建的表面。如果需要用到基于系统RAM的表面所占内存,那么Windows会立即将这些表面释放到硬盘上。Windows自动地处理存储和恢复,包括恢复表面的内容。
10. GetDDInterface()函数
GetDDInterface()函数可检索用于创建给定表面的DirectDraw接口的指针。由于程序中大多数情况下只有一个DirectDraw接口实例,所以GetDDInterface()函数并不常用。但是一个应用程序中有可能使用多个DirectDraw接口,在这种情况下,GetDDInterface()函数会起到重要作用。
11. 表面连接函数
DirectDrawSurface接口提供以下4个函数,用来维持表面间的连接:
●AddAttachedSurface()
●DeleteAttachedSurface()
●EnumAttachedSurfaces()
●GetAttachedSurface()
DirectDraw支持大量的用于表面间的连接情况。最常见的情况就是页面翻转。进行页面翻转时,两个或两个以上的表面连接成环状,每次调用Flip()函数时,都会使连成环状的表面中的下一个表面显现。
表面连接函数用于创建、检查或消除表面间的连接,但这些函数并非必不可少的。DirectDraw往往是自动创建连接表面。比如,当创建一个主翻转表面时,可以指定用于连接表面的后备缓冲区的数量。DirectDraw就会创建这些表面,并将它们连接起来。
12. 重叠函数
DirectDrawSurface接口用于支持重叠的函数如下:
●AddOverlayDirtyRect()
●EnumOverlayZOrders()
●GetOverlayPosition()
●SetOverlayPosition()
●UpdateOverlay()
●UpdateOverlayDisplay()
●UpdateOverlayZOrder()
GetOverlayPosition()和SetOverlayPosition()函数用于控制重叠的位置。UpdateOverlay()函数用于更新大量的重叠设置,包括重叠是否可见,以及重叠是以色彩键码还是用alpha混合到背景表面上。
UpdateOverlayDisplay()函数用于更新显示设置。该函数用于更新整个重叠显示,或者只更新由AddOverlayDirtyRect()函数指定的矩形重叠部分。EnumOverlayZOrders()函数可根据重叠的Z值(Z值控制哪一个重叠位于最上面)重复重叠。重叠可按从前到后或从后到前的顺序枚举。
13. 剪裁器函数
DirectDraw支持的剪裁是将DirectDrawClipper接口(该接口我们尚未讨论)的一个实例连接到表面。一旦连接完毕,剪裁器对象就会有规律地blt到表面。剪裁器/表面的连接由以下两个DirectDrawSurface函数控制:
●GetClipper()
●SetClipper()
SetClipper()函数用来将剪裁器对象连接到表面。GetClipper()函数用于向前一个连接的剪裁器对象返回一个指针。SetClipper()函数还可用于解除表面同剪裁器的连接,具体的做法是通过指定NULL来替代DirctDrawClipper接口指针。
14。 调色板函数
像剪裁器一样,调色板也可连接到表面。DirctDrawSurface接口为此提供以下两个函数:
●GetPalette()
●SetPalette()
SetPalette()函数用来将DirctDrawPalette接口(该接口我们接下来就要讨论)的一个实例连接到表面。GetPalette()函数用于检索前一个连接调色板的指针。
调色板可被连接到任何表面,但只有连接到主表面时,调色板才起作用。当与主表面连接时,调色板决定显示硬件调色板的设置。
第六节 DirectDrawPlette接口函数
DirctDraw提供DirctDrawPalette接口用于调色板显示模式和表面。尽管Windows支持几种低于8位像素深度的显示模式,但DirctDraw所支持的唯一的调色板显示模式是8位模式。
DirctDrawPalette接口实例由DirctDraw CreatePalette()函数创建。CreatePalette()函数用大量的标志来定义调色板属性。
DirctDrawPalette接口只提供以下3个函数:
●GetCaps()
●GetEntries()
●SetEntries()
GetCaps()函数用于检索有关调色板的信息,包括调色板项目数量,调色板是否支持同步垂直刷新,以及在8位调色板状态下是否所有的256个调色板项目都能被设定。
SetEntries()函数允许在程序中设置调色板的色彩值。该数据从文件中读取。而这些项目在运行过程中可被计算和设定。GetEntries()函数用于检索先前设定的调色板项目。
DirctDrawPalette()接口实例可利用DirctDrawSurface SetPalette()函数连接到表面。将不同调色板连接到主表面或利用SetEntries()函数改变调色板项目都可激活调色板。

第七节 DirectDrawClipper接口函数
DirctDrawClipper接口支持剪裁。将剪裁器对象连接到表面并在blt操作中将其当作目标表面就可以进行剪裁。
directDrawClipper实例由DirectDraw CreateClipper()函数创建。DirectDrawClipper接口支持以下5个函数:
●SetHWnd()
●GetHWnd()
●IsClipListChanged()
●SetClipList()
●GetClipList()
剪裁器对象一般用于出现在窗口中的DirctDraw应用程序必需的剪裁。要求剪裁器必须确保在blt操作过程中考虑到桌面上其他的窗口。比如,当应用程序的全部或一部分被另一个窗口遮蔽,剪裁就必须确保被遮蔽的窗口不被DirctDraw应用程序破坏。
桌面剪裁由SetWnd()函数完成。SetHWnd()函数将剪裁器对象连接到一个窗口句柄。这样就初始化了Windows和剪裁器对象之间的通讯。当桌面上的任何一个窗口发生变化时,剪裁器对象就会得到通知,并作出反应。GetHWnd()函数用于决定剪裁器同哪一个窗口句柄连接。IsClipListChanged()函数用于决定内部剪裁清单是否因桌面的改变而被更新。
SetClipList()和GetClipList()函数为DirectDrawClipper接口提供便利的定制使用。SetClipList()函数用于定义一些矩形区域,这些矩形区域用来定义blt操作的合法区域。GetClipList()函数用于检索剪裁器的内部剪裁数据。
一旦被连接到表面,Blt(),BltBatch()以及UpdateOverlay()函数所进行的blt操作将根据DirctDrawCliper接口中包含的数据被自动地剪裁。注意,Blt Fast()函数在此被忽略。BltFast()函数不支持剪裁。

第八节 附加DirectDraw接口
DirctDraw还提供了另外个我们没有讨论的接口,它们是:
●DDVideoPortContainer
●DirectDrawColorControl
●DirectDrawVideoport
这些接口由DirectX5介绍,它们提供低水平视频端口控制。这些接口还向DirctDraw表面提供流活动视频的方法。尽管利用这些接口可以为DirctDraw应用程序增加视频支持,但除非高水平视频APIs不能满足需要,否则最好不用这一方法。

第九节 DirectDraw结构
我们已讨论过DirctDraw接口及其成员函数了,接下来再看看DirctDraw定义的结构。DirctDraw总共定义了8全结构:
●DDBLTFX
●DDCAPS
●DDOVERLAYFX
●DDPIXELFORMAT
●DDSURFACEDESC
●DDSCAPS
●DDBLTBATCH
●DDCOLORKEY
我们已经见过其中的一些结构了,比如在讨论DirctDrawSurface SetColorKey()函数时我们就接触过DDCOLORKEY结构。在此,我们不详细讨论每一个结构的细节,但必须指出,DirctDraw quirk被忘记时会导致失败。
以上所列结构中的前5个有一个称作dwSize的字段。该字段用于存储结构的大小,设定该字段的工作由你来做。另外,该字段如果没有被正确设定的话,那么任何一个将这5个结构作为变量的DirctDraw函数都会失效。
以DDSURFACEDESC结构为例,使用结构的代码如下:
DDSURFACEDESC surfdesc;
surfdesc.dwSize=sizeof(surfdesc);
surf->GetSurfaceDesc(&surfdesc);
首先声明结构,然后用sizeof()关键字设定dwSize字段。最后结构传递给DirctDrawSurface GetSurfaceDesc()函数。忘记设定dwSize字段将导致这段代码失效。
到底为什么DirctDraw坚持要求给出它所定义的结构的大小?原因在于这5个包含dwSize字段的结构将来有可能会改变。DirctDraw会检查结构的大小,以便确定正在使用的版本。DirctDraw坚持要求给出一个有效的大小值,是为了让开发者提供有效的结构大小。这样做是有好处的,因为DirctDraw的新版本可以正确运用旧版本的DirctDraw程序。
在使用结构之前,最好将结构初始化为零。这样,前面的代码就变成:
DDSURFACEDESC surfdesc;
ZeroMemory (&surfdesc,sizeof(surfdesc));
surfdesc.dwSize=sizeof(surfdesc);
surf->GetSurfaceDesc(&surfdesc);
ZeroMemory()函数是一个Win32函数,它将作为第一个参数的存储器设定为零.ZeroMemory()函数的第二个参数表明有多少存储器应被初始化。这一做法的好处是,通过GetSurfaceDesc()函数调用可以知道结构的哪些字段被更新了。如果没有对结构进行初始化,就有可能将结构字段中不可预测的值当作DirectDraw的设定值。

第十节 窗口应用程序
DirctDraw应用程序主要有两种型式:窗口的和全屏的。窗口DirctDraw应用程序看起来就像一个常规的Windows程序。我们很快将讨论到全屏应用程序。
窗口应用程序包括窗口边界、标题框以及菜单,这些都是传统的Windows应用程序中常见的部分。由于窗口应用程序同其他窗口一起出现在桌面上,因此它们被迫使用Windows当前所使用的分辨率和比特深度。
窗口程序有一个主表面,但只在进行真实页面翻转时才显现。而且,主表面并不代表窗口的客户区域(该区域在窗口边界内)。主表面还代表整个桌面。这就是说,你的程序必须追踪窗口的位置和大小,以便在窗口内正确显示可见的输出。换言之,利用窗口化的应用程序中可以在整个桌面上进行绘图。
如果不允许页面翻转,那么图像就必须从离屏缓冲区blt到主表面上。这就增加了图像撕裂的可能性,因为blt比页面翻转速度慢。为了避免图像撕裂,blt操作可以与监视的刷新率保持同步。
如果与窗口客户区域同样大小的离屏缓冲区被创建到显示RAM中,窗口应用程序就可以很好地运行。这样,窗口的内容可利用离屏表面合成。然后离屏表面可以通过硬件加速很快地到主表面上。
由于显示存储器的缺乏而不得不将离屏缓冲区创建到系统RAM中时,会严重影响性能。不幸的是,这种情况常常发生,尤其是在只有2MB显示卡的时候,这是因为人们总希望为自己Windows的桌面设置高分辨的显示模式。例如,采用1024×768×16显示模式的主表面自己就要占用2MB的RAM。在一个2MB显示卡上,几乎没有显示RAM留给离屏表面。
窗口应用程序的另一个问题是剪裁。一个性能良好的应用程序必须有一个连接到主表面的剪裁对象。这是有损性能的,原因在于为了检查剪裁器的剪裁清单内容,blt操作只能在一个小的矩形部分内进行。而且,不能使用优化的BltFast()函数。Bltting必须用较慢的,(而且更笨重的)Blt()函数。
最后要讲的是,窗口应用程序不允许全调色板控制。由于Windows保留了20个调色板项,所以在256种颜色只有236种颜色可被设定。被Windows保留的颜色只用系统调色板的前10个项和后10个项。因此在给图像上色时,只能使用中间236个调色板项。

第十一节全屏应用程序
包含对显示设备进行专有存取的DirctDraw应用程序就是全屏应用程序。这种应用程序可以任意选取显示卡所支持的显示模式,并享有全调色板控制。另外,全屏应用程序还可进行页面翻转。因此同窗口应用程序相比,全屏应用程序速度更快,灵活性更好。
典型的全屏应用程序首先检查所支持的显示模式,并激活其中一个。然后创建具有一个或更多后备缓冲区的可翻转主表面,剩下的显示RAM用于创建离屏表面。当显示RAM耗尽时,就启用系统RAM。屏幕被在后备缓冲区中合成的第一个场景更新,然后进行页面翻转。即使主表面占用了所有的可用的显示RAM,全屏应用程序还可输出执行其窗口化的副本,这是因为全屏应用程序可进行真实页面翻转。
由于全屏应用程序不一定非得使用Windows的当前显示模式,所以显示RAM的可用与否不存在多少问题。如果只检测到2MB的显示RAM,就可使用低分辨率显示模式来保留内存。如果检测到4MB的显示RAM,应用程序就可使用要求的显示模式并仍有保持良好性能。
全调色板控制也是个有利因素。这样可以使用所有256个调色板项而无需根据Windows保留的20中颜色重新分配位图。

第十二节混合应用程序
混合应用程序既可以在全屏方式下运行也可在窗口方式下运行。混合应用程序内部非常复杂,但却可以提供良好的性能。用户可在窗口方式下运行,如果太慢,则可切换到全屏方式下运行。
编写混合应用程序最好的方法是编写一个定制库,该库包括那些与应用程序使用全屏方式还是窗口方式无关的函数。
 


 


游戏开发基础(5)



第五章 diectxdarw基础篇
第一节 DirectDraw简介
Grubers的一个观点是DirectDraw“只是一个bltting发动机”。这是相当准确的,但却太简化了。更准确地讲,DirectDraw是一个可以提供软件仿真测试的独立于硬件设备的bltting发动机。DirectDraw的主要用途是尽可能快、尽可能可靠并且尽可能连续地将图形考贝到视频显示设备上。
  另外一个定义DirectDraw的方式是把它作为一个视频存储器管理器,同常规的存储器管理器一样,DirectDraw发放存储器信息包,跟踪每一个信息包的状态。信息包可以随意地创建、复制、修改或破坏,同时这些操作的细节被程序员隐含起来,这样讲是过于简单了。此外,DirectDraw是能够使用系统RAM和视频RAM的。存储器管理器也经常被设计成和主要目标一样强健,而不只是追求性能。对于DirectDraw,性能只是设计目标之一。
从技术角度讲,DirectDraw是随同设备驱动器集合的便携式API。DirectDraw设计成完全避开传统意义上的Windows图形机构(GDI,或称图形设备接口)。GDI由于性能低而名声不好,所以DirectDraw的设备独立性在提供最佳性能方面是至关重要的。
第二节 DirectDraw基本概念
1. 显示模式
显示模式是由允许将要显示的图形输出的显示硬件支持的可视配置。最常用的显示模式属性是分辨率。Windows使用的显示模式的默认值是640×480的分辨率。这意味着,水平方向有640个像素,垂直方向有480个像素。其他一些常见的显示模式分辨率有800×600,1024×768。一些显示卡支持Mode X显示模式。一个典型的Mode X显示模式的分辨率为320×200。
  显示模式也随像素深度的变化而变化。像素深度决定着每一个像素所容纳的多少不同的值,因而也就可以显示多少种颜色。例如对于8位像素深度的显示模式,每个像素能够再现256种颜色的一种。像素深度为16位的显示模式支持65536种颜色(即2的n次方),典型的像素深度为8、16、24和32位。
  显示模式由安装在机器中的显示设备或视频卡支持。显示设备有自己的RAM,从计算机的RAM中分离出来。我们把位于显示设备中的存储器称为显示RAM,而把常规存储器称为系统RAM。
支持一个给定的显示模式的RAM的容量取决于显示模式的分辨率和像素深度。例如,640×480×8(640×480像素,深度为8位)的显示模式需要307200字节。1024×768×16的显示模式需要1572864字节。支持显示模式的存储器必须是显示RAM。一个给定显示设备所支持的显示模式因此也被可以利用的显示RAM的容量所限制。例如,1024×768×16的显示模式因为需要一兆字节以上的内存,所以就不能被只有一兆字节RAM的显示设备所支持。
DirectDraw的一个主要特征是显示模式切换。这允许一个DirectDraw应用程序检测和激活所安装的显示设备所支持的任何显示模式。我们将在第4章中讨论显示模式切换的细节。
2. 硬件加速
DirectDraw具有最优性能的最重要的原因是,它尽可能地使用硬件加速来进行设计。硬件加速发生在当显示设备能够用建立在显示设备之中的处理功能执行操作时。硬件加速具有两个优点,首先,当硬件加速出现的时候,硬件按指定要求设计成支持图形操作,这提供了执行给定任务的最快的方法:其次,硬件加速使得计算主处理器从执行操作中解放出来,这使得主处理器可以执行其他任务。
3. 表面
表面是存储器的一个矩形部分的DirectDraw术语,通常包括图像数据。该存储器通常用来表示一个存在于显示RAM或系统RAM中的表面。驻留在显示RAM中的表面享有超高性能,因为绝大多数显示硬件不能对系统RAM直接存取。
表面分为向大类,最简单的类型是脱离屏幕表面。脱离屏幕表面可以驻留在显示RAM中或系统RAM中,但却不能被显示。这类表面一般用于存储子画面和背景。
另一方面,一个主表面是可在屏幕上看到的视频RAM的一部分部分。所有的DirectDraw程序(可以提供视频输出)都拥有主表面。主表面必须驻留在显示RAM中。
主表面通常很复杂,或是可翻转的。可翻转表面允许页面翻转,这是一项整个表面的内容可以通过一个硬件操作而瞬时可见的技术。页面翻转用于许多基于DirectDraw或其他的图形应用程序中,因为它可以产生相当平滑、不闪烁的动画。一个可翻转的主表面实际上是两个表面,一个可见,另一个不可见。不可见的表面称为后备缓冲区。当发生页面翻转时,以前是后备缓冲区的表面就成为可见的,而以前可见的表面则成为后备缓冲区。
  离屏表面和主表面都有两类:调色板的和无调色板的。在DirectDraw中,只有8位表面是调色板表面。调色板表面并不包含色彩数据,但是却引入一个色彩表。该表称为调色板。像素深度为16、24或32位的表面是无调色板表面。无调色板表面存储实际色彩值,而不引入调色板。
因为在无调色板表面中的每一个像素都存储色彩数据,所以知道表面的像素格式是很重要的。像素格式描述了存储于像素中的红色、绿色和蓝色(RGB)元件的方式。像素格式随像素深度、显示模式和硬件设计的不同而不同,在第5章中可以了解所有的像素格式。
4. Bltting
Bltting是用于复制的图形语言。典型的blt操作是将离屏表面的内容拷贝到一个后备缓冲区中。当Bltting通过硬件完成的时候,执行速度相当快。如果无法得到硬件加速,DirectDraw将使用一个软件操作来仿真blt。这种仿真操作虽然也能够完成任务,但却比硬件慢得多.一般只有驻留在显示RAM中的表面能够通过使用显示硬件来完成blt。
  blt操作调用一个源表面和一个目标表面,源表面的内容被拷贝到目标表面中。源表面中的内容在操作中不会改变,只有目标表面受blt的影响。blt操作也并不需要使用全部的源表面或目标表面。源表面中的任何矩形区域可以被放置于目标表面中的任何位置。
不规则形状表面的bltting(例如典型的子画面)是以透明方式完成的。透明性是通过指定表面中某个不被blt操作拷贝的像素而获得的。像素值通过使用色彩键码给以标志。
色彩键码可以附加到源表面或目标表面上。源色彩键码是很普遍的。源色彩键码允许透明性,因为源表面中的像素值并未被考贝。至于目标色彩,只有目标表面中通过色彩所指定的像素值能够被源表面的内容所覆盖。
DirectDraw也支持一些特定的操作,包括拉伸、压缩、镜像映射,以及混合等。这些功能的实现往往取决于显示硬件。DirectDraw能够仿真其中的某些操作,但是跟性能相比,价格往往是昂贵的。
DirectDraw也有不能仿真的功能(例如目标色彩键码)。使用这些功能是冒险的,除非该功能为所安装的显示硬件支持,否则使用该功能的操作将失败。这给DirectDraw的开发者带来两种基本选择:要么放弃使用这些功能:要么往应用程序中增加定制软件。
5. 调色板
  使用8位显示模式的应用程序需要提供调色板。调色板就是任何时候都可以使用的色彩表。如果8位显示模式不需要调色板,应用程序将被迫使用256种颜色的固定设置。调色板允许用户定义将要使用的256种颜色之一。
  当你使用调色板显示模式时,必须保证在应用程序中的图像也使用同一调色板。如果没有做到这一点,所显示的一些或全部图像中将出现错误的颜色。调色板也会带来麻烦,尤其是用一个调色板来显示大量图像的时候。调色板也有一些优势。正如前面提到的,调色板允许在一个有限色彩的场合使用最多的色彩。调色板也允许调色板动画。
  调色板动画是动画通过改变调色板项目,而不是改变像素值来执行的技术,这就使得一个屏幕上的很多像素可以瞬时改变颜色。对于一些有限的应用程序,诸如分配的、重复的动画,调色板动画很有用处。
6. 剪裁
理想状态下,一个blt操作就是整个表面被blt成为另一个表面。通常源表面被blt成为目标表面的边,或者目标表面被另一个表面或窗口遮蔽。像这样的情况就需要进行剪裁。剪裁只允许一部分或一个表面的一部分被blt。
在编写窗口DirectDraw应用程序时经常用到剪裁,因为这些应用程序必须遵守Windows桌面的规则。我们将在本章后面讨论窗口应用程序。
DirectDraw提供全矩形剪裁支持。也有这种情况,就是付费提供定制剪裁例程,我们将在第3章中研究定制剪裁解决方案。
7. 其他表面
离屏表面和主表面(具有任选的后备缓冲区)是绝大多数DirectDraw应用程序的主干。然而一些其他的表面就有不同,包括重叠表面、alpha通道表面、Z-缓冲区以及3D设备表面等。
重叠表面是硬件单色画面,因而也就在仅在支持重叠的显示硬件上获得。和软件单色画面不同,它可以被移动而不需要背景图像被恢复。
alpha通道表面用来执行alpha调配。Alpha调配是透明的高级形式。允许表面以透明度或半透明方式来拷贝。alpha通道表面可用来控制每一像素的透明度设置。alpha通道表面的深度有1、2、4、8位。1位深度alpha通道表面仅支持两种透明设置,不透明(非透明)或不可见(全透明)。另一方面,8位alpha通道表面允许256种不同的透明度设置。Alpha调配是不被DirectDraw仿真的功能的一个例子。为了使用alpha调配,因而就需要有支持它的显示硬件或建立在应用程序之中的定制调配方案。
Z-缓冲区和3D设备表面用于3D应用程序中。这些类型的表面已被特别地加入到DirectDraw之中,以支持Direct3D。Z-缓冲区用于景象绘制时期,以跟踪景象中离浏览者最近的对象,从而该对象可以在其他对象的前面出现。3D设备表面可以用来作为Direct3D绘制目标的表面。本书并不包括Z-缓冲区或3D设备。
第三节 元件对象模型(COM)
1.Microsoft的COM规格
DirectDraw根据Microsoft的COM(Component Object Model)规格得以实现。COM设计成用来提供完全便携的、安全的、可升级的软件结构,COM是一个大的项目,但是它并不是本软件讨论的对象。我们讨论COM只是为了方便使用DirectDraw进行编程。
COM使用一个面向对象的模型,这要比像C++等语言所用的模型更严格。例如,COM对象经常通过成员函数进行存取,而且并不具备公共数据成员。COM对继承性的支持与C++相比也是有限的。
2. 对象和接口的比较
COM在对象和接口之间具有很大的区别。COM对象提供实际函数性,而COM接口则提供存取函数性的方法。COM对象不能被直接存取,相反,所有的存取者通过接口来完成。这条规则是如此强有力的得到遵守,以致于我们不能给任何COM对象以名称。我们只能给用来存取对象的接口名称。因为我们无法存取COM对象,所以这里绝大多数时候是根据接口来讲的。
一个COM对象能够支持多个接口。这听起来像个特例,但是它经常出现,因为根据COM规格,一个COM接口一旦定义之后,就不能再被改变或增加。这样做是为保证旧程序在一个COM对象升级的时候不会被停止使用。这个初始接口始终存在,一个新的、替换的接口在提供存取对象的新的函数性的时候才被提供。
3. IUnknown接口
所有的COM接口都是从IUnknown接口中衍生出来的。“I”标志经常用于命名COM接口(它代表Interface即界面)。DirectDraw接口总以“I”开头。但是在文献中经常看不到这个标志。以后提到接口时也将省略“I”标志。
  IUnknown接口将提供3个成员函数,所有COM接口因而继承这些函数:
●AddRef()
●Release()
●QueryInterface()
AddRef()和Release()成员函数为称为生命期封装(lifetime encapsulation)的COM功能提供支持。生命期封装是一个将每一个对象根据它自己的结构放置的协议。
生命期封装通过引用值来实现。每一个对象拥有一个可以跟踪对象的指针数,或者引用的内部值。当对象创建之后,该值为1。如果附加的接口或接口的指针被创建,则该值递增。与此类似,如果接口的指针被破坏,则该值递减。当它的引用数到0的时候,该对象自行破坏。
AddRef()函数用来使对象的内部引用值递增。绝大部分时间里,该函数通过DirectDraw API被用户调用。例如,当你使用DirectDrawaw API创建一个新的接口时,创建函数就自动调用AddRef()。
Release()函数用来给对象的内部引用值递减。用户应该在接口的指针将要超出范围时或者要通过使用接口指针来结束时使用这个函数。AddRef()和Release()函数都返回一个值,表示对象新的引用值。
QueryInterface()函数允许COM对象就它们是否支持特定接口进行查询。例如,升级的COM对象提供附加的接口,而非现有接口的修改版。QueryInterface()函数可以用来确定旧的接口,以决定新的接口是否被支持。如果被查询的对象不支持有问题的接口,则更替接口的指针就返回。
4. GUID
为了查询一个对象是否支持使用QueryInterface()函数的指定接口,就有秘要识别有问题的接口。这通过接口的GUID(Globally Unique IDentifier)来实现。一个GUID是一个128位的值,也就是说,对于所有意图和目的是唯一的。所有DirectDraw接口的GUIDs都包含在DirectX头文件中。
上述对于COM的简单介绍,就是为有效使用DirectDraw API所需要的全部内容。以后当我们再讨论DirectDraw API时,你会发现这些内容是有联系的。

第四节 DirectDraw接口函数
1.关于 DirectDraw API
衡量API的一个方法就是看它的大小。一个庞大复杂的API可能就是计划不周的结果。另一方面,一个庞大的API有时就意味着每一种情况都有可能出现。一个小的API就是一个新的、缺乏功能的软件包的证据。它也意味着,一个API只能做它所需要做的,而不能多做一点。
DirectDraw API是比较小的,因此本章中所讨论的每一个函数不致于使本章看起来像一本参考手册。DirectDraw提供很少的方便,也很少有限制。
DirectDraw由个COM对象构成,每个对象可以通过一个或多个接口存取。这些接口包括:
●DirectDraw
●DirectDraw2
●DirectDrawSurface
●DirectDrawSurface2
●DirectDrawSurface3
●DirectDrawPalette
●DirectDrawClipper
我们将讨论每一个接口,并随后讨论它们的成员函数。但我们并不讨论每个函数的细节,因为我们并不是向您提供一份参考手册。相反,我们将讨论每个函数是干什么的,为什么这样使用,以及你有可能如何去使用它。
当DirectX首次推出的时候(早先它被称作Games SDK),DirectDraw核心函数性以DirectDraw接口表示。当DirectX2推出的时候,DirectDraw也已经被升级了。DirectDraw遵守COM规格而未被改变。新的函数性可能通过DirectDraw2接口存取。
特别要注意的是,DirectDraw2接口是DirectDraw接口的超级设置。DirectDraw2接口可提供DirectDraw接口的所有函数,另外还增加了一些新的函数。如果你正在使用DirectX或更高版高,那么你可以随意选用DirectDraw接口或DirectDraw2接口。但是,由于DirectDraw2接口较DirectDraw接口的功能更强,所以没有必要使用DirectDraw接口。同样,Microsoft并不主张使用这些无组织的、网络可变的接口。因此,在本书以后的程序中我们只使用DirectDraw2接口。
DirectDraw和DirectDraw2接口提供的成员函数如下(按字母顺序排列):
●Compact()
●CreateClipper()
●CreatePalette()
●CreateSurface()
●DuplicateSurface()
●EnumDisplayModes()
●EnumSurfaces()
●FlipToGDISurface()
●GetAvailableVidMem()
●GetCaps()
●GetDisplayMode()
●GetFourCCCodes()
●GetGDISurface()
●GetMonitorFrequency()
●GetScanline()
●GetVerticalBlankStatus()
●RestoreDisplayMode()
●SetCooperativeLevel()
●SetDisplayMode()
●WaitForVerticalBlank()
接下来我们讨论DirectDraw接口函数。注意,在本章以后的内容中,DirectDraw接口既表示DirectDraw接口,也表示DirectDraw2接口。只有在区分DirectDraw接口和DirectDraw2接口的函数时,才加以区别。
1. 接口创建函数
DirectDraw接口表示DirectDraw本身。该接口在被用于创建其他DirectDraw接口实例时,是一个主接口。DirectDraw接口提供三个这样的接口实例创建函数:
●CreateClipper()
●CreatePalette()
●CreateSurface()
CreateClipper()函数用于创建DirectDrawClipper接口实例。并非所有的DirectDraw应用程序都用到剪裁器,所以该函数并不是在所有的程序中都出现。我们将很快讨论DirectDrawClipper的细节。
CreatePalette()函数用于创建DirectDrawPalette接口实例。同DirectDrawClipper一样,并非所有的DirectDraw应用程序都用到调色板。比如,应用程序使用16位显示模式时,就不用调色板。但是,当应用程序使用8位显示模式时,就必须创建至少一个DirectDrawPalette实例。
CreateSurface()函数用于创建DirectDrawSurface接口实例。任何一个DirectDraw应用程序都要用表面来生成图像数据,因此经常要用到这一函数。
DirectDraw接口自己的实例是由DirectDrawCreate()函数创建的。DirectDrawCreate()是DirectDraw函数中少有的几个常规函数之一,但并不是COM接口成员函数。
2. GetCaps()函数
DirectDraw接口允许准确确定软硬件都支持的特征。GetCaps()函数可以对两个DDCAP结构实例进行初始化。一个结构表明哪些特征由显示硬件直接支持,另一个结构表明哪些特征由软件仿真支持。最好是用GetCaps()函数来决定你将用到的特征是否被支持。
提示:DirectX浏览器
DirectX SKD是与DXVIEW程序同时推出的。DXVIEW说明了DirectX组件的功能,包括DirectDraw。大多数系统中,有两个DirectDraw项目:主显示驱动器和硬件仿真层。第一项说明了显示硬件的功能。第二项说明了在缺乏硬件支持的情况下,DirectDraw将要仿真的一些特征。在具有两个以上的DirectDraw支持的显示卡的计算机中,DXVIEW会显示卡的功能。
3. SetCooperativeLevel()函数
SetCooperativeLevel()函数用于指定应用程序所要求的对显示硬件的控制程度。比如,一个正常合作度意味着应用程序既改变不了当前显示模式,也不能指定整个系统调色板的内容。而一个专有的合作度允许显示模式切换,并能完全控制调色板。不管你决定使用哪种合作度,都必须调用SetCooperativeLevel()函数。
4. 显示模式函数
DirectDraw接口提供4种显示模式操作函数。它们是:
●EnumDisplayModes()
●GetDisplayMode()
●RestoreDisplayMode()
●SetDisplayMode()
EnumDisplayModes()函数可用于查询DirectDraw使用何种显示模式。通过设置EnumDisplayModes()函数默认值可以得到所有的显示模式,而且可以通过显示模式描述消除那些不感兴趣的模式。进行显示模式切换的过程中最好使用EnumDisplayModes()函数。现在市场上有各种各样的显示设备,每种显示设备都有自己的特征和局限。除了默认的640×480×8窗口显示模式,最好不要依靠任何给定的显示模式的支持。
  GetDisplayMode()函数可以检索到有关当前显示模式的信息,并在DDSURFACEDESC结构实例中显示当前显示模式的宽度、高度、像素深度以及像素格式等信息。还有别的途径可以检索到同样的信息(比如检索主表面描述),因此该函数并不出现在所有的程序中。
  SetDisplayMode()函数用于激活所支持的显示模式。SetDisplayMode()函数的DirectDraw2版本还允许设定显示模式的刷新率。而DirectDraw接口版本的SetDisplayMode()函数只能进行显示模式宽度、高度和像素深度的设置。任何一个要进行显示模式切换的程序都要用到SetDisplayMode()函数。
RestoreDisplayMode()函数用于存储调用SetDisplayMode()函数之前的显示模式。SetDisplayMode()和RestoreDisplayMode()函数都要求优先使用SetCooperativeLevel()函数得到的专有合作存取。
5. 表面支持函数
除了CreateSurface()函数之外,DirectDraw接口还提供了以下向个表面相关函数:
●DuplicateSurface()
●EnumSurfaces()
●FlipToGDISurface()
●GetGDISurface()
●GetAvailableVidMem()
●Compact()
DuplicateSurface()函数用于考贝当前表面。该函数只复制表面接口,不复制内存。被复制的表面与源表面共享内存,因此改变内存的内容就同时改变了两个表面的图像。
EnumSurfaces()函数可用于迭代所有满足指定标准的表面。如果没有指定标准,那么所有当前表面都被枚举。
FlipToGDISurface()函数的作用是在终止页面翻转应用程序前确保主表面得以正确存储。取消页面翻转时,有两个表面交替显示。这就是说,在终止应用程序之前有可能没有保存最初的可见表面。这种情况下,Windows通过绘制一个不可见表面来恢复。利用FlipToGDISurface()函数就可以轻而易举地避免发生这种情况。
GetGDISurface()函数可以向只被GDI认可的表面返回一个提针。GDI表面是Windows用于输出的表面。在进行屏幕捕捉时,这个函数非常有用,DirectDraw可以捕捉到Windows桌面的任一部分。
GetAvailableVidMem()函数用于检索正在使用中的视频存储器(显示RAM)的数量。这一函数由DirectDraw2接口提供,而不是由DirectDraw接口提供。该函数用于确定应用程序利用显示RAM可创建表面的数量。
Compact()函数不是通过DirectX5实现的,但它可以为视频存储器提供碎片整理技巧。在基于显示RAM的表面被不断创建或受到破坏的时候,可以释放大量内存。
6. 监视器刷新函数
DirectDraw接口提供了4种适于计算机显示设备或监视器的函数,但这些函数不适于显示卡,它们是:
●GetMonitorFrequency()
●GetScanLine()
●GetVerticalBlankStatus()
●WaitForVerticalBlank()
这些函数尤其与监视器的刷新机制紧密机连。这在确保生成动画时尽可能不产生闪烁和图像撕裂现象时是至关重要的。但必须注意,并非所有的显示卡/监视器组合都支持这些函数。
GetMonitorFrequency()函数用于检索监视器当前的刷新率。刷新率通常用赫兹表示,缩写为Hz。例如,60Hz的刷新率表示屏幕每秒更新60次。
GetScanLine()函数用于向监视器返回当前正在被刷新的扫描行(水平像素行)。不是所有的显示设备/监视器组合都支持该函数。如果这一功能得不到支持,该函数将返回DDERR-UNSUPPORTED。
对于高性能图形应用程序来说,通常要求利用垂直刷新同步地更新屏幕。尤其是,当显示器刚完成屏幕刷新时,最好能够更新主表面。否则,屏幕的一部分显示新的图像数据,而另一部分仍显示旧的图像数据,这种现象就是所谓的图像撕裂。DirectDraw默认利用垂直刷新同步更新屏幕。如果不是这样还可以利用GetVerticalBlankStatus()和WaitForVerticalBlank()函数实现同步刷新。
7. GetFourCCCodes()函数
DirectDraw接口提供的最后一个函数是GetFourCCCodes()函数。该函数用于返回显示卡所支持的FourCC代码。FourCC代码用于描述非RGB或YUV表面。我们不在此讨论YOV表面,它们已超出本书的范围。
第五节 DirectDrawSurface接口函数
同DirectDraw接口一样,DirectDrawSurface接口也遵守COM规格.最初,表面支持是由DirectDrawSurface接口提供的。DirectX2介绍了DirectDrawSurface2接口的新的函数性,DirectX5介绍了DirectDrawSurface3接口。
尽管本软件中讨论的是DirectDraw2接口,而不是DirectDraw接口,但我们仍忠于最初的DirectDrawSurface接口,因为DirectDrawSurface2和DirectDrawSurface3接口新增的函数并不十分重要。在以后的内容里,我们将用DirectDrawSurface接口表示这3种接口,除非特别注明。
DirectDrawSurface是最大的DirectDraw接口,它允许表面内容的拷贝、清除以及被调用程序直接存取。DirectDrawSurawSurface接口总共提供36个成员函数,按字母顺序排列如下:
●AddAttachedSurface()
●AddOverlayDirtyRect()
●Blt()
●BltBatch()
●BltFast()
●DeleteAttachedSurface()
●EnumAttachedSurfaces()
●EnumOverlayZOrders()
●Flip()
●GetAttachedSurface()
●GetBltstatus()
●GetCaps()
●GetClipper()
●GetColorKey()
●GetDC()
●GetDDInterface()
●GetFlipStatus()
●GetOverlayPosition()
●GetPalette()
●GetPixelFormat()
●GetSurfaceDesc()
●IsLost()
●Lock()
●PageLock()
●PageUnlock()
●ReleaseDC()
●Restore()
●SetClipper()
●SetColorKey()
●SetOverlayPosition()
●SetPalette()
●SetSurfaceDesc()
●Unlock()
●UpdateOverlay()
●UpdateOverlayDisplay()
●UpdateOverlayZOrder()
1. 表面描述函数
我们首先讨论的个可用于检索表面自身信息的函数,它们是:
●GetCaps()
●GetPixelFormat()
●GetSurfaceDesc()
●SetSurfaceDesc()
同DirectDraw接口提供的GetCaps()函数一样,DirectDrawSurface接口提供的GetCaps()函数用于输出表征哪些特征可被表面支持的数据。该信息包括:表面是主表面还是离屏表面;表面使用的存储器定位于显示RAM还是系统RAM。
GetPixelFormat()函数在用于高彩和真彩表面时是非常重要的,这是由于像素格式因显示卡的不同而不同。该函数返回表征码,这些表征码表明每一种颜色部件是如何存储的。
GetSurfaceDesc()函数返回一个表面描述。该信息包括表面的宽度、高度和深度。表面像素格式(同样被GetPixelFormat()函数检索)也包含在其中。
SetSurfaceDesc()函数(对于DirectX5)来讲是新增的,只由DirectDrawSurface3接口提供)允许设定某些表面属性。该函数可用于指定表面使用的内存。这一点在设计定制表面存储器管理器策略时非常有用。
2。 表面Blt函数
DirectDrawSurface接口提供3个支持blt操作的函数:
●Blt()
●BltBatch()
●BltFast()
Blt()函数是一个主要函数。Blt()函数能够进行常规的blting(无特殊影响的简单的表面到表面的blt),同时支持延伸、旋转、镜像和颜色填充的操作。当用于同剪裁器关联的表面时,Blt()可进行剪裁blt操作。
BltBatch()函数不是在DirectX3下实现的(你可以调用该函数,但什么也不会发生)。执行BltBatch()函数时,如果可能,它可同时进行多blt操作。
BltFast()函数是Blt()函数的优化版本。BltFast()函数的效率提高了,但性能却下降了。BltFast()函数不能进行一些特殊的blt操作,而Blt()函数可以。而且,BltFast()函数不能用于剪裁。但是BltFast()函数支持源和目标色彩键码blt的操作。在遵循定制剪裁例程的情况下,BltFast()函数可进行DirectDraw能够提供的最快捷、灵活的blt操作。在下章中我们将执行一个定制剪裁例程。
以上3个blt函数均将源表面和目标表面作为变量。其他的数据,例如blt在目标表面上的理想定位,是通过指定理想blt操作的确切属性来提供的。一旦可能,这3个函数将进行硬件加速blt。
3. Flip()函数
Flip()函数用于页面翻转操作。调用Flip()函数可隐藏屏幕上先前可见的表面,并使一个后备缓冲区显现。只有被明确地创建为翻转表面的表面,才响应该函数的调用。
必须牢记,真正的翻转操作不可能总是成功。页面翻转要求有足够的显示RAM容纳两整屏有效数据。如果满足不了这一要求,系统RAM中将创建一个后备缓冲区。这时调用Flip()函数进行的是blt操作而不是页面翻转。基于系统RAM的后备缓冲区中的内容被拷贝到主表面上。这样会严重影响性能,但是,在真正的页面翻转中如果没有足够的显示RAM,又不退出程序,也只能如此了。如果你的应用程序要求最佳性能,就得设法避免激活不能进行真正页面翻转的显示模式。
4. 表面状态函数
下面讨论两个能检索有关操作和翻转操作信息的函数,它们是:
●GetBltStatus()
●GetFlipStatus()
GetBltStatus()函数用于确定当前是否进行blt操作。这一点很重要,因为正被blt的表面不能进行其他操作。该函数表明,给定的表面是否正是一个进行blt操作的源表面或目标表面。
同样地,GetBltStatus()函数表明是否正在进行翻转操作。即使DirectDraw通过blt操作仿真页面翻转,该函数而不GetBltStatus()也必须用于由Flip()函数初始化的监视器页面翻转。
5. 色彩键码函数
DirectDrawSurface接口提供了以下两个函数,来设置和检查表面色彩键码或色彩键码的范围,它们是:
●GetColorKey()
●SetColoKey()
默认状态下,表面没有色彩键码。一个色彩键码只对应一种颜色,但某些硬件支持色彩键码范围。色彩键码和色彩键码范围是DDCOLORKEY结构定义的。GetColorKey()和SetColoKey()函数都将该结构的指针作为变量。在要求表面的一部分透明时或需要进行目标色彩键码操作时,可以使用这两个函数。
6. Lock和Unlock()函数
DirectDraw的一个主要特点,就是能够提供对图像数据的直接存取。直接存取可以提供最佳性能和更好的灵活性,因为没有中间API影响运行速度,并且开发人员呆任意使用图像数据。对表面存储器的中间存取通过以下出众个函数实现:
●Unlock()
●Lock()
Lock()函数向组成表面的存储器返回一个指针,不管表面存储器位于显示RAM还是系统RAM。存储器一般按线性风格排列,以便能简单地进行图像 数据存取。Unolock()函数在完成表面存储器的存取之后指定给DirectDraw。
对图像数据的直接存取必须付出代价。为了支持这种存取方式,DirectDraw在表面锁定的时候必须关闭基本的Windows机构。在Windows95状态下,如果忘记解锁表面,必定会损坏机器。
因此,表面锁定的时间应尽量缩短。测试前应仔细检查Lock()和Unlock()函数之间的程序调用。因为这一程序无法用传统的调试程序进行调试。
锁定表面不能被blt和翻转,因此试图保持表面处于锁定状态没有任何有益之处。而且,一旦表面解锁,由Lock()函数检索的指针就失效了。
表面锁定后不能再次被锁定。在表面锁定时也就无法调用Lock()函数。
7. GetDC()ReleaseDC()函数
对表面的直接存取占用很大内存,有时候把表面作为一个常规的Windows设备会更好。在此,DirectDrawSurface接口提供以下两个函数:
●GetDC()
●ReleaseDC()
GetDC()函数提供了一个DC(设备环境),可以用常规的Win32函数写到表面上。例如,DC可以用Win32的TextOut()函数在表面上绘制文本。用完DC后必须马上调用ReleaseDC()函数。
同Lock()和Unlock()函数使用一样,在调用完GetDC()函数后必须马上调用ReleaseDC()函数。这是因为GetDC()函数内部调用Lock函数,而ReleaseDC()函数内部调用Unlock()函数。
8. PageLock()和PageUnlock()函数
接下来,我们讨论另外两个与Lock()函数和Unlock()函数看上去非常相像的函数:
●PageLock()
●PageUnlock()
尽管这两个函数看上去很像Lock()和Unlock()函数,但它们却有完全不同的作用。PageLock()和PageUnlock()函数用于控制Windows对基于系统RAM的表面的处理方式。这两个函数由DirectDrawSurface2接口提供,而不是由DirectDrawSurface接口提供。
当Windows认为当前正在运行的其他应用程序或进程更适于使用内存时,Windows会向硬盘释放部分内存。这种缓冲对于整个系统内存都起作用,因此驻留在系统内存中的DirectDraw表面有可能被存到硬盘上。如果要用到这样的表面,Windows需要花费一定的时间从硬盘上读取表面数据。
PageLock()函数提示Windows哪些给定的表面不应该释放到硬盘上。这样,在使用表面时就不用耗费时间进行硬盘存取了。相反地,PageUnlock()函数用于告知Windows哪些表面内存可被释放。
过程调用PageLock()函数会减少缓冲内存的总量,从而导致Windows的速度大大降低。至于这种情况何时发生,取决于页面锁定系统内存量及机器提供的系统内存量。
PageLock()和PageUnlock()函数主要是由DirectDraw提供而非DirectDraw应用程序。举个例子来说,DirectDraw自动使用PageLock()函数,以确保运行blt操作时,基于系统RAM的表面不被释放到硬盘。
PageLock()函数可以被同一个表面多次调用。DirectDraw用参考计数法记录PageLock()函数被调用的次数,因此多次调用PageUnlock()函数就必须避免多次调用PageLock()函数。
PageLock()和PageUnlock()函数对于驻留在显示RAM中的表面不起作用。
9. IsLost()的Restore()函数
现在讨论两个与使用驻留在显示RAM中的表面有关的函数:
●IsLost()
●Restore()
让我们来看一看下面这种情况。应用程序正在运行时,尽量把表面分配到显示RAM中,剩下的创建到系统RAM中。应用程序在运行一段时间之后,用户执行或切换到另一个应用程序。该应用程序是任意的,可以是一个常规的Windows程序,如Windows开发程序或记事本。它也可以是另外的DirectDraw应用程序,该程序也试图将尽可能多地分配显示RAM。如果DirectDraw不接受显示RAM,那么新的应用程序就很可能根本运行不了。相反的情况就意味着,应用程序不允许分配到任何显示RAM中。
因此,DirectDraw可以随意将任何一个或者所有的基于显示RAM的表面从非激活应用程序中移走。这种情况就是所谓的表面丢失。从技术上讲,程序仍具有表面,但它们不再同任何内存相关。要使用丢失的表面会导致DDERR-SURFACELOST错误。IsLost()函数可用于确定一个表面是否丢失了内存。
表面内存丢失后可通过Restore()函数恢复,但只能在应用程序被重新激活之后才可恢复。这会导致应用程序无法将处于最小化状态的所有表面复原。
Restore()函数可恢复附属于表面的任一内存,但并不恢复内存的内容。表面被复原后,应用程序就可以恢复表面内容了。
注意,这种用法不适合利用系统RAM创建的表面。如果需要用到基于系统RAM的表面所占内存,那么Windows会立即将这些表面释放到硬盘上。Windows自动地处理存储和恢复,包括恢复表面的内容。
10. GetDDInterface()函数
GetDDInterface()函数可检索用于创建给定表面的DirectDraw接口的指针。由于程序中大多数情况下只有一个DirectDraw接口实例,所以GetDDInterface()函数并不常用。但是一个应用程序中有可能使用多个DirectDraw接口,在这种情况下,GetDDInterface()函数会起到重要作用。
11. 表面连接函数
DirectDrawSurface接口提供以下4个函数,用来维持表面间的连接:
●AddAttachedSurface()
●DeleteAttachedSurface()
●EnumAttachedSurfaces()
●GetAttachedSurface()
DirectDraw支持大量的用于表面间的连接情况。最常见的情况就是页面翻转。进行页面翻转时,两个或两个以上的表面连接成环状,每次调用Flip()函数时,都会使连成环状的表面中的下一个表面显现。
表面连接函数用于创建、检查或消除表面间的连接,但这些函数并非必不可少的。DirectDraw往往是自动创建连接表面。比如,当创建一个主翻转表面时,可以指定用于连接表面的后备缓冲区的数量。DirectDraw就会创建这些表面,并将它们连接起来。
12. 重叠函数
DirectDrawSurface接口用于支持重叠的函数如下:
●AddOverlayDirtyRect()
●EnumOverlayZOrders()
●GetOverlayPosition()
●SetOverlayPosition()
●UpdateOverlay()
●UpdateOverlayDisplay()
●UpdateOverlayZOrder()
GetOverlayPosition()和SetOverlayPosition()函数用于控制重叠的位置。UpdateOverlay()函数用于更新大量的重叠设置,包括重叠是否可见,以及重叠是以色彩键码还是用alpha混合到背景表面上。
UpdateOverlayDisplay()函数用于更新显示设置。该函数用于更新整个重叠显示,或者只更新由AddOverlayDirtyRect()函数指定的矩形重叠部分。EnumOverlayZOrders()函数可根据重叠的Z值(Z值控制哪一个重叠位于最上面)重复重叠。重叠可按从前到后或从后到前的顺序枚举。
13. 剪裁器函数
DirectDraw支持的剪裁是将DirectDrawClipper接口(该接口我们尚未讨论)的一个实例连接到表面。一旦连接完毕,剪裁器对象就会有规律地blt到表面。剪裁器/表面的连接由以下两个DirectDrawSurface函数控制:
●GetClipper()
●SetClipper()
SetClipper()函数用来将剪裁器对象连接到表面。GetClipper()函数用于向前一个连接的剪裁器对象返回一个指针。SetClipper()函数还可用于解除表面同剪裁器的连接,具体的做法是通过指定NULL来替代DirctDrawClipper接口指针。
14。 调色板函数
像剪裁器一样,调色板也可连接到表面。DirctDrawSurface接口为此提供以下两个函数:
●GetPalette()
●SetPalette()
SetPalette()函数用来将DirctDrawPalette接口(该接口我们接下来就要讨论)的一个实例连接到表面。GetPalette()函数用于检索前一个连接调色板的指针。
调色板可被连接到任何表面,但只有连接到主表面时,调色板才起作用。当与主表面连接时,调色板决定显示硬件调色板的设置。
第六节 DirectDrawPlette接口函数
DirctDraw提供DirctDrawPalette接口用于调色板显示模式和表面。尽管Windows支持几种低于8位像素深度的显示模式,但DirctDraw所支持的唯一的调色板显示模式是8位模式。
DirctDrawPalette接口实例由DirctDraw CreatePalette()函数创建。CreatePalette()函数用大量的标志来定义调色板属性。
DirctDrawPalette接口只提供以下3个函数:
●GetCaps()
●GetEntries()
●SetEntries()
GetCaps()函数用于检索有关调色板的信息,包括调色板项目数量,调色板是否支持同步垂直刷新,以及在8位调色板状态下是否所有的256个调色板项目都能被设定。
SetEntries()函数允许在程序中设置调色板的色彩值。该数据从文件中读取。而这些项目在运行过程中可被计算和设定。GetEntries()函数用于检索先前设定的调色板项目。
DirctDrawPalette()接口实例可利用DirctDrawSurface SetPalette()函数连接到表面。将不同调色板连接到主表面或利用SetEntries()函数改变调色板项目都可激活调色板。
第七节 DirectDrawClipper接口函数
DirctDrawClipper接口支持剪裁。将剪裁器对象连接到表面并在blt操作中将其当作目标表面就可以进行剪裁。
directDrawClipper实例由DirectDraw CreateClipper()函数创建。DirectDrawClipper接口支持以下5个函数:
●SetHWnd()
●GetHWnd()
●IsClipListChanged()
●SetClipList()
●GetClipList()
剪裁器对象一般用于出现在窗口中的DirctDraw应用程序必需的剪裁。要求剪裁器必须确保在blt操作过程中考虑到桌面上其他的窗口。比如,当应用程序的全部或一部分被另一个窗口遮蔽,剪裁就必须确保被遮蔽的窗口不被DirctDraw应用程序破坏。
桌面剪裁由SetWnd()函数完成。SetHWnd()函数将剪裁器对象连接到一个窗口句柄。这样就初始化了Windows和剪裁器对象之间的通讯。当桌面上的任何一个窗口发生变化时,剪裁器对象就会得到通知,并作出反应。GetHWnd()函数用于决定剪裁器同哪一个窗口句柄连接。IsClipListChanged()函数用于决定内部剪裁清单是否因桌面的改变而被更新。
SetClipList()和GetClipList()函数为DirectDrawClipper接口提供便利的定制使用。SetClipList()函数用于定义一些矩形区域,这些矩形区域用来定义blt操作的合法区域。GetClipList()函数用于检索剪裁器的内部剪裁数据。
一旦被连接到表面,Blt(),BltBatch()以及UpdateOverlay()函数所进行的blt操作将根据DirctDrawCliper接口中包含的数据被自动地剪裁。注意,Blt Fast()函数在此被忽略。BltFast()函数不支持剪裁。
第八节 附加DirectDraw接口
DirctDraw还提供了另外个我们没有讨论的接口,它们是:
●DDVideoPortContainer
●DirectDrawColorControl
●DirectDrawVideoport
这些接口由DirectX5介绍,它们提供低水平视频端口控制。这些接口还向DirctDraw表面提供流活动视频的方法。尽管利用这些接口可以为DirctDraw应用程序增加视频支持,但除非高水平视频APIs不能满足需要,否则最好不用这一方法。

第九节 DirectDraw结构
我们已讨论过DirctDraw接口及其成员函数了,接下来再看看DirctDraw定义的结构。DirctDraw总共定义了8全结构:
●DDBLTFX
●DDCAPS
●DDOVERLAYFX
●DDPIXELFORMAT
●DDSURFACEDESC
●DDSCAPS
●DDBLTBATCH
●DDCOLORKEY
我们已经见过其中的一些结构了,比如在讨论DirctDrawSurface SetColorKey()函数时我们就接触过DDCOLORKEY结构。在此,我们不详细讨论每一个结构的细节,但必须指出,DirctDraw quirk被忘记时会导致失败。
以上所列结构中的前5个有一个称作dwSize的字段。该字段用于存储结构的大小,设定该字段的工作由你来做。另外,该字段如果没有被正确设定的话,那么任何一个将这5个结构作为变量的DirctDraw函数都会失效。
以DDSURFACEDESC结构为例,使用结构的代码如下:
DDSURFACEDESC surfdesc;
surfdesc.dwSize=sizeof(surfdesc);
surf->GetSurfaceDesc(&surfdesc);
首先声明结构,然后用sizeof()关键字设定dwSize字段。最后结构传递给DirctDrawSurface GetSurfaceDesc()函数。忘记设定dwSize字段将导致这段代码失效。
到底为什么DirctDraw坚持要求给出它所定义的结构的大小?原因在于这5个包含dwSize字段的结构将来有可能会改变。DirctDraw会检查结构的大小,以便确定正在使用的版本。DirctDraw坚持要求给出一个有效的大小值,是为了让开发者提供有效的结构大小。这样做是有好处的,因为DirctDraw的新版本可以正确运用旧版本的DirctDraw程序。
在使用结构之前,最好将结构初始化为零。这样,前面的代码就变成:
DDSURFACEDESC surfdesc;
ZeroMemory (&surfdesc,sizeof(surfdesc));
surfdesc.dwSize=sizeof(surfdesc);
surf->GetSurfaceDesc(&surfdesc);
ZeroMemory()函数是一个Win32函数,它将作为第一个参数的存储器设定为零.ZeroMemory()函数的第二个参数表明有多少存储器应被初始化。这一做法的好处是,通过GetSurfaceDesc()函数调用可以知道结构的哪些字段被更新了。如果没有对结构进行初始化,就有可能将结构字段中不可预测的值当作DirectDraw的设定值。
第十节 窗口应用程序
DirctDraw应用程序主要有两种型式:窗口的和全屏的。窗口DirctDraw应用程序看起来就像一个常规的Windows程序。我们很快将讨论到全屏应用程序。
窗口应用程序包括窗口边界、标题框以及菜单,这些都是传统的Windows应用程序中常见的部分。由于窗口应用程序同其他窗口一起出现在桌面上,因此它们被迫使用Windows当前所使用的分辨率和比特深度。
窗口程序有一个主表面,但只在进行真实页面翻转时才显现。而且,主表面并不代表窗口的客户区域(该区域在窗口边界内)。主表面还代表整个桌面。这就是说,你的程序必须追踪窗口的位置和大小,以便在窗口内正确显示可见的输出。换言之,利用窗口化的应用程序中可以在整个桌面上进行绘图。
如果不允许页面翻转,那么图像就必须从离屏缓冲区blt到主表面上。这就增加了图像撕裂的可能性,因为blt比页面翻转速度慢。为了避免图像撕裂,blt操作可以与监视的刷新率保持同步。
如果与窗口客户区域同样大小的离屏缓冲区被创建到显示RAM中,窗口应用程序就可以很好地运行。这样,窗口的内容可利用离屏表面合成。然后离屏表面可以通过硬件加速很快地到主表面上。
由于显示存储器的缺乏而不得不将离屏缓冲区创建到系统RAM中时,会严重影响性能。不幸的是,这种情况常常发生,尤其是在只有2MB显示卡的时候,这是因为人们总希望为自己Windows的桌面设置高分辨的显示模式。例如,采用1024×768×16显示模式的主表面自己就要占用2MB的RAM。在一个2MB显示卡上,几乎没有显示RAM留给离屏表面。
窗口应用程序的另一个问题是剪裁。一个性能良好的应用程序必须有一个连接到主表面的剪裁对象。这是有损性能的,原因在于为了检查剪裁器的剪裁清单内容,blt操作只能在一个小的矩形部分内进行。而且,不能使用优化的BltFast()函数。Bltting必须用较慢的,(而且更笨重的)Blt()函数。
最后要讲的是,窗口应用程序不允许全调色板控制。由于Windows保留了20个调色板项,所以在256种颜色只有236种颜色可被设定。被Windows保留的颜色只用系统调色板的前10个项和后10个项。因此在给图像上色时,只能使用中间236个调色板项。
第十一节全屏应用程序
包含对显示设备进行专有存取的DirctDraw应用程序就是全屏应用程序。这种应用程序可以任意选取显示卡所支持的显示模式,并享有全调色板控制。另外,全屏应用程序还可进行页面翻转。因此同窗口应用程序相比,全屏应用程序速度更快,灵活性更好。
典型的全屏应用程序首先检查所支持的显示模式,并激活其中一个。然后创建具有一个或更多后备缓冲区的可翻转主表面,剩下的显示RAM用于创建离屏表面。当显示RAM耗尽时,就启用系统RAM。屏幕被在后备缓冲区中合成的第一个场景更新,然后进行页面翻转。即使主表面占用了所有的可用的显示RAM,全屏应用程序还可输出执行其窗口化的副本,这是因为全屏应用程序可进行真实页面翻转。
由于全屏应用程序不一定非得使用Windows的当前显示模式,所以显示RAM的可用与否不存在多少问题。如果只检测到2MB的显示RAM,就可使用低分辨率显示模式来保留内存。如果检测到4MB的显示RAM,应用程序就可使用要求的显示模式并仍有保持良好性能。
全调色板控制也是个有利因素。这样可以使用所有256个调色板项而无需根据Windows保留的20中颜色重新分配位图。
第十二节混合应用程序
混合应用程序既可以在全屏方式下运行也可在窗口方式下运行。混合应用程序内部非常复杂,但却可以提供良好的性能。用户可在窗口方式下运行,如果太慢,则可切换到全屏方式下运行。
编写混合应用程序最好的方法是编写一个定制库,该库包括那些与应用程序使用全屏方式还是窗口方式无关的函数。


 


游戏开发基础(6)



DirectSound

第一节 关于声音
声音是空气的一系列振荡,称为声波,一般可以用二维的波形图来表示。数字音频是指使用某种设备将声波记录下来并保存为一种数字化的文件。播放相应的文件就可以产生某种声音效果。数字音频的音质随着采样频率及所使用的位数不同而有很大的差异。因此,了解所使用音频文件格式的有关标准是很有必要的。例如,CD中的音频是16位,采样频率达到44.1MHz的立体声数字音频。
在所有声音文件的格式中,WAV是最普遍的。这是Windows平台上最常见的格式,由微软公司创造。支持8位和16位的音质、多样本、对立体声和单声道音频均可播放。它还支持多种音频压缩算法。
要在游戏中取得好的声音效果,例如,使用3D音效,可以有两种方法来实现:一是使用一定的工具软件对声音文件进行处理,生成播放效果足够好的文件,然后在游戏程序中直接将这样的文件播放。显然,这样比较简单,但是不灵活。如果需要音效随着游戏场景的变化而不断改变,且不受所具有声音文件数量的限制,就需要进行实时混音了。


第二节DirectSound结构
DirectSound的功能模块包括播放、声音缓冲区、三维音效、音频抓获、属性集等。
DirectSound playback建构于IDirectSound COM接口之上。IDirectSoundBuffer,IDirectSound3DBuffer和
IDirectSound3DListener接口则用以实现对声音缓冲区和三维音效的操作。
DirectSound capture建构于IDirectSoundCapture和IDirectSoundCaptureBuffer COM接口之上。
其它的COM接口,如IKsPropertySet,使应用程序能够从声卡的扩展功能中最大地受益。
最后,IDirectSoundNotify接口用于在播放或音频抓获达到一定地方时向产生一个事件。


第三节 播放功能概述
DirectSound缓冲区对象表示一个包含声音数据的缓冲区,这些数据以PCM格式被存储。该对象不仅可以用于开始、停止或暂停声音的播放,还能够设置声音数据中诸如频率和格式等属性。
缓冲区分为主缓冲区和副缓冲区。主缓冲区中是听者将要听到的音频信号,一般是将副缓冲区中信号混音后的结果。而副缓冲区中存放着许多单独的声音信号,有的可以直接播放,有的要混音,有的循环播放。主缓冲区由DirectSound自动创建,而副缓冲区需由应用程序来创建。DirectSound将副缓冲区中的声音混合后,存入主缓冲区,再输出到相应播放设备。
DirectSound中没有解析声音文件的功能,需要您自己在应用程序中将不同格式的声音信号改变过来(PCM)。
缓冲区可以在主板的RAM、波表存储器、DMA通道或虚拟存储器中。
多个应用程序可以用同一声音设备来创建DirectSound对象。当输入焦点在应用程序中发生变化时,音频输出将自动在各个应用程序的流之间切换。于是,应用程序不用在输入焦点改变中反复地播放和停止它们的缓冲区。

通过IDirectSoundNotify接口,当播放到了一个用户指定的地方,或播放结束时,DirectSound将动态地通知拥护这一事件。


第四节 音频抓获概述
DirectSoundCapture对象可以查询音频抓获设备的性能,并为从输入源抓获音频而创建缓冲区。
其实,在Win32中早已经有了抓获音频的功能,而目前的(版本5)DirectSoundCapture与只比较并没有什么新的功能。不过,DirectSoundCapture API使您能够编写使用相同接口的播放和音频抓获程序,而且,这也为将来可能出现的API改进提供了原始模型,使您可以从中受益。
DirectSoundCapture还能够抓获压缩格式的音频。
DirectSoundCaptureBuffer对象表示一个用于抓获音频的缓冲区。它可以循环利用,也就是说,当输入指针达到缓冲区的最后时,它会回到开始的地方。
DirectSoundCaptureBuffer对象的各种方式使您能够设定缓冲区的属性、开始或停止操作、锁定某部分存储器(这样就可以安全地将这些数据保存或用于其它目的)。
与播放类似,IDirectSoundNotify接口使在输入指针到达一定地方时通知用户。


第五节 初始化
对于一些简单的操作,可以使用缺省的首选设备。不过,在游戏的制作中,我们可能还是需要知道一些特定的声音设备。于是,您应该先列举出可用的声音设备。
在此之前,您需要先设定一个回收函数,在每一次DirectSound发现新设备后调用该函数。函数中您可以做任何事情,但您必须将它定义得与DSEnumCallback形式相同。如果希望列举继续,函数应返回真,否则返回假。
下面的例程来自光盘Example目录下的Dsenum.c文件。它列举可用的设备并在一个列表框中增加一条相应的信息。首先是他的回收函数:

BOOL CALLBACK DSEnumProc(LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID lpContext )
{
HWND hCombo = *(HWND *)lpContext;
LPGUID lpTemp = NULL;

if( lpGUID != NULL )
{
if(( lpTemp = LocalAlloc( LPTR, sizeof(GUID))) == NULL )
return( TRUE );

memcpy( lpTemp, lpGUID, sizeof(GUID));
}

ComboBox_AddString( hCombo, lpszDesc );

ComboBox_SetItemData( hCombo,
ComboBox_FindString( hCombo, 0, lpszDesc ), lpTemp );
return( TRUE );
}

当包含了列表框的对话框被初始化后,列举开始:

if (DirectSoundEnumerate((LPDSENUMCALLBACK)DSEnumProc, &hCombo) != DS_OK )
{
EndDialog( hDlg, TRUE );
return( TRUE );
}

创建DirectSound对象最简单的方法是使用DirectSoundCreate函数。其中的第一个参数为相应设备的全局独有标志符(GUID)。您可以通过列举声音设备得到GUID,或使用NULL来为缺省设备创建对象。

LPDIRECTSOUND lpDirectSound;
HRESULT hr;
hr = DirectSoundCreate(NULL, &lpDirectSound, NULL));

创建DirectSound对象后,应设置合作层。这是为了确定各个DirectSound应用程序被允许操作声音设备的范围,防止它们在错误的时间或通过错误的方式操作设备。
所使用的方式为IDirectSound::SetCooperativeLevel。这里hwnd参数是应用程序窗口的句柄:

HRESULT hr = lpDirectSound->lpVtbl->SetCooperativeLevel( lpDirectSound, hwnd, DSSCL_NORMAL);

这里确定的合作层为normal,这样使用声卡的应用程序可以顺序地进行切换。合作层包括Normal、Priority、Exclusive和Write-primary,级别依次增加。
正如在前面提到过,DirectSound可以充分发挥硬件的增强功能,因此,它需要先设法了解设备的特性。我们可以通过IDirectSound::GetCaps方式来达到这个要求。如下所示:

DSCAPS dscaps;

dscaps.dwSize = sizeof(DSCAPS);
HRESULT hr = lpDirectSound->lpVtbl->GetCaps(lpDirectSound, &dscaps);

DSCAPS结构接收关于声音设备性能和资源的信息。注意,初始化该结构中dwSize成员是调用它之前所必须的。
除此之外,您还可以查询和设定扬声器的设置,以及整理声音存储器使尽量获得最大的备用空间。


第六节 如何播放
初始化完成后,DirectSound将自动创建主缓冲区用于混音并传送至输出设备。而副缓冲区则需要您自己来创建了。
下面的例程演示了用IDirectSound::CreateSoundBuffer方式创建一个基本的副缓冲区:

BOOL AppCreateBasicBuffer( LPDIRECTSOUND lpDirectSound, LPDIRECTSOUNDBUFFER *lplpDsb)
{
PCMWAVEFORMAT pcmwf;
DSBUFFERDESC dsbdesc;
HRESULT hr;
// 设定声波格式结构
memset(&pcmwf, 0, sizeof(PCMWAVEFORMAT));
pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM;
pcmwf.wf.nChannels = 2;
pcmwf.wf.nSamplesPerSec = 22050;
pcmwf.wf.nBlockAlign = 4;
pcmwf.wf.nAvgBytesPerSec =
pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign;

pcmwf.wBitsPerSample = 16;
// 设置DSBUFFERDESC结构,用以设定缓冲区控制选项
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
// 要求缺省的控制
dsbdesc.dwFlags = DSBCAPS_CTRLDEFAULT;
// 3秒的缓冲区
dsbdesc.dwBufferBytes = 3 * pcmwf.wf.nAvgBytesPerSec;
dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf;
// 创建缓冲区
hr = lpDirectSound->lpVtbl->CreateSoundBuffer(lpDirectSound, &dsbdesc, lplpDsb, NULL);
if(DS_OK == hr) {
// 成功,获得的接口在*lplpDsb当中
return TRUE;
} else {
// 失败
*lplpDsb = NULL;
return FALSE;
}
}

您必须设定缓冲区的控制选项。这是使用DSBUFFERDESC结构中的dwFlags成员,具体细节请参见DirectX 5的帮助。
副缓冲区不支持混音等特效,因此,您需要能够直接操作主缓冲区。不过,当您获权写主缓冲区时,其它特性将失去作用,从而硬件加速混音失效。所以,大部分应用程序几少直接操作主缓冲区。
如果要求操作主缓冲区,可以在调用IDirectSound::CreateSoundBuffer方式时设定DSBUFFERDESC结构中的DSBCAPS_PRIMARYBUFFER标志符,而且,合作层必须是Write-primary。
下面的例程演示了如何得到对主缓冲区的写操作能力:

BOOL AppCreateWritePrimaryBuffer( LPDIRECTSOUND lpDirectSound, LPDIRECTSOUNDBUFFER *lplpDsb, LPDWORD lpdwBufferSize, HWND hwnd)
{
DSBUFFERDESC dsbdesc;
DSBCAPS dsbcaps;
HRESULT hr;
// 设置声波格式结构
memset(&pcmwf, 0, sizeof(PCMWAVEFORMAT));
pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM;
pcmwf.wf.nChannels = 2;
pcmwf.wf.nSamplesPerSec = 22050;
pcmwf.wf.nBlockAlign = 4;
pcmwf.wf.nAvgBytesPerSec = pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign;
pcmwf.wBitsPerSample = 16;
// 设置DSBUFFERDESC结构
memset(&lplpDsb, 0, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
// 缓冲区大小由声音硬件决定
dsbdesc.dwBufferBytes = 0;
dsbdesc.lpwfxFormat = NULL; // 对主缓冲区必须设为NULL

// 获得write-primary合作层

hr = lpDirectSound->lpVtbl->SetCooperativeLevel(lpDirectSound, hwnd, DSSCL_WRITEPRIMARY);
if (DS_OK == hr) {
// 成功,试图创建缓冲区
hr = lpDirectSound->lpVtbl->CreateSoundBuffer(lpDirectSound, &dsbdesc, lplpDsb, NULL);
if (DS_OK == hr) {
// 成功,设定主缓冲区为desired格式
hr = (*lplpDsb)->lpVtbl->SetFormat(*lplpDsb, &pcmwf);
if (DS_OK == hr) {

// 如果希望得知缓冲区大小,调用GetCaps
dsbcaps.dwSize = sizeof(DSBCAPS);
(*lplpDsb)->lpVtbl->GetCaps(*lplpDsb, &dsbcaps);
*lpdwBufferSize = dsbcaps.dwBufferBytes;
return TRUE;
}
}
}
// 设定合作层失败
// 创建缓冲区,或设定结构
*lplpDsb = NULL;
*lpdwBufferSize = 0;
return FALSE;
}

播放一段声音的过程包括以下四个步骤:
1 锁定(IDirectSoundBuffer::Lock)副缓冲区的一部分。由您设定的偏移量决定下一步写操作的起始点;
2 写数据;
3 解锁(IDirectSoundBuffer::Unlock);
4 将声音传送给主缓冲区,并由那里输出(IDirectSoundBuffer::Play)。
下面的C程序向缓冲区中写入数据,由dwOffset指定开始时的偏移量:

BOOL AppWriteDataToBuffer( LPDIRECTSOUNDBUFFER lpDsb, // DirectSound缓冲区
DWORD dwOffset, // 自己的写标记位置
LPBYTE lpbSoundData, // 数据的起点
DWORD dwSoundBytes) // 拷贝块的大小
{
LPVOID lpvPtr1;
DWORD dwBytes1;
LPVOID lpvPtr2;
DWORD dwBytes2;
HRESULT hr;
// 得到被写块的地址
hr = lpDsb->lpVtbl->Lock(lpDsb, dwOffset, dwSoundBytes, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);

// 如果返回DSERR_BUFFERLOST,释放并重试锁定
if(DSERR_BUFFERLOST == hr) {
lpDsb->lpVtbl->Restore(lpDsb);
hr = lpDsb->lpVtbl->Lock(lpDsb, dwOffset, dwSoundBytes, &lpvPtr1, &dwAudio1, &lpvPtr2, &dwAudio2, 0);
}
if(DS_OK == hr) {
// 写到指针
CopyMemory(lpvPtr1, lpbSoundData, dwBytes1);
if(NULL != lpvPtr2) {
CopyMemory(lpvPtr2, lpbSoundData+dwBytes1, dwBytes2);
}
// 释放
hr = lpDsb->lpVtbl->Unlock(lpDsb, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
if(DS_OK == hr) {
// 成功
return TRUE;
}
}
// 某步骤失败
return FALSE;
}


 


游戏开发基础(7)



第七章 游戏编程的特点
第一节 概述:

电脑游戏在计算机发展使用中可以说扮演了一个极为有趣的角色,一方面不为很多人所赞同,认为是一种浪费;而另一方面电脑游戏却是推动计算机的各项技术迅速发展的最有力的力量之一。

这一点,可以从3d类游戏对硬件无止境的需求,游戏迷对游戏图像的质量、游戏的交互性、人机界面的友好性等方面的需求体现出来(当然游戏迷对游戏的的构思、创意的要求也是苛刻且无止境的,但这一点只有靠您自己的想象力,我们是爱莫能助了)。

从游戏近期的发展中,我们从3d游戏的发展,可以看到从Doom到现在的古墓丽影2、雷神之锤2,3d游戏的画面从生硬单调的多边形到今天柔和复杂几进乱真的场景、道具、怪物,敌人从只会疯狂向你冲来到今天会偷袭、会审时度势地采取合适的方式方法向你进攻;游戏无论从硬件支持还是编程技术方面都有突飞猛进的进展。在游戏发展的过程中,很多技术也随之发展起来了,例如各种图形加速卡的出现和发展,directx的出现,和各个成功游戏中采用的各种优化技术都推动了计算机技术的发展。

游戏可以说是集合了每个时期计算机行业中最先进的硬件技术和最新的编程思想,比如近期的游戏都是采用了面向对象的编程思想的基于Windows的软件,大部分图象要求高的游戏都要求或支持图形加速卡。同时游戏编程中也有自己基本的方式方法、结构和理论,在这一章的学习中我们将讨论这些问题。
在这一章中我们将讨论下面几个问题:

程序入口 即是游戏获取外部操作的讯息,得到下次刷新所需的新参数的手段。如同一般的SDK Windows应用程序一样,程序的入口为WINMAIN()。

游戏初始化 包括创建标准的WINDOWS程序所需的初始化程序以及游戏内部的初始化程序,例如游戏系统初始化、游戏图形的装入、游戏声音的装入等。

游戏内部循环: 游戏的循环入口是WINDOWS消息循环内部的一个函数调用,游戏内部循环包括刷新游戏单位、画游戏单位两部分。

刷新游戏单位: 用于每一帧刷新游戏单位的状态,例如改变游戏单位的状态、改变游戏单位的位置、获取外部信息等。

画游戏单位: 用于每一帧往屏幕上画游戏单位的图象,并进行特殊处理以提高速度。

计算机人工智能: 主要用于受计算机处理的游戏单位的行为控制算法,程序部分位于刷新计算机游戏单位部分中。

游戏内存管理: 这一部分对于优质高效的游戏软件是十分重要的,内存管理不当会导致游戏性能的降低,甚至引起死机。

游戏交互设计: 交互设计是游戏可玩性的关键,友好的交互界面和交互方式可以使游戏增色不少。

游戏图象底层设计: 游戏软件的主要处理时间花在处理图象和画图象上,所以游戏图象底层的设计对于游戏的最终效果是十分重要的。

游戏多媒体设计: 主要包括图形界面设计、游戏音乐音效设计、游戏动画设计、游戏影象设计的几个方面,更广泛的说还包括游戏所有运行过程的功能设计。


第二节 程序入口
这个标题看起来似乎很难理解,它的意思就是当游戏被启动时,计算机从什么地方开始运行程序的。在Windows的应用程序上,Winmain()函数一般就是程序入口。游戏开始后,就调用Winmain()函数,然后再按语句的顺序或所接受到的消息调用相应的函数。

从第三章Windows编程基础中我们了解到Winmain()函数的的结构、运行过程,现在我们就游戏编程的角度来讨论Winmain()函数的编制。

int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow )
{
MSG msg;

while( lpCmdLine[0] == '-' || lpCmdLine[0] == '/')
{
lpCmdLine++;

switch (*lpCmdLine++)
{
case 'e':
bUseEmulation = TRUE;
break;
case 'w':
bFullscreen = FALSE;
break;
case 'f':
bFullscreen = TRUE;
break;
case '1':
CmdLineBufferCount = 1;
break;
case '2':
case 'd':
CmdLineBufferCount = 2;
break;
case '3':
CmdLineBufferCount = 3;
break;
case 's':
bStretch = TRUE;
break;
case 'S':
bWantSound = FALSE;
break;
case 'x':
bStress= TRUE;
break;
case '?':
bHelp= TRUE;
bFullscreen= FALSE; // give help in windowed mode
break;
}

while( IS_SPACE(*lpCmdLine) )
{
lpCmdLine++;
}
}

GameMode.cx = getint(&lpCmdLine, 640);
GameMode.cy = getint(&lpCmdLine, 480);
GameBPP = getint(&lpCmdLine, 8);

/*
* create window and other windows things
*/
if( !initApplication(hInstance, nCmdShow) )
{
return FALSE;
}

/*
* Give user help if asked for
*
* This is ugly for now because the whole screen is black
* except for the popup box. This could be fixed with some
* work to get the window size right when it was created instead
* of delaying that work. see ddraw.c
*
*/

if( bHelp )
{
MessageBox(hWndMain,
"F12 - Quit\n"
"NUMPAD 2 - crouch\n"
"NUMPAD 3 - apple\n"
"NUMPAD 4 - right\n"
"NUMPAD 5 - stop\n"
"NUMPAD 6 - left\n"
"NUMPAD 7 - jump\n"
"\n"
"Command line parameters\n"
"\n"
"-e Use emulator\n"
"-S No Sound\n"
"-1 No backbuffer\n"
"-2 One backbuffer\n"
"-4 Three backbuffers\n"
"-s Use stretch\n"
"-x Demo or stress mode\n",
OUR_APP_NAME, MB_OK );
}

/*
* initialize for game play
*/

if( !InitGame() )
{
return FALSE;
}
dwFrameTime = timeGetTime();

while( 1 )
{
if (PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE))
{
if (!GetMessage( &msg, NULL, 0, 0))
{
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else if (!bPaused && (bIsActive || !bFullscreen))
{
ProcessFox(lastInput);
lastInput=0;
}
else
{
WaitMessage();
}
}

if (AveFrameRateCount)
{
AveFrameRate = AveFrameRate / AveFrameRateCount;
Msg("Average frame rate: %d", AveFrameRate);
}

return msg.wParam;

} /* WinMain */


我们知道在消息循环之中只有一个消息----WM_QUIT可以结束这个循环,退出WINDOWS。所以我们要在消息循环之前完成所有的工作即所有的初始化。关于初始化这个概念在下一节我们将详细讨论。在我们提供的例程(在光盘的sample目录中)中的foxbear.c里的WMain()中我们可以看到在消息循环之前先运行DDint()函数对DirectDraw进行初始化,检测命令行参数并对相关的参数进行赋值和确定显示模式,进行窗口的初始化,检测bhelp的值以确定是否显示帮助对话框,进行游戏的初始化。
在一个游戏的消息循环中除了包含一般Windows应用程序的消息循环所应包含的部分外还应有调用有关检测游戏单位状态位置、刷新游戏单位和重画新图以及有关人工智能的函数的部分。在例程中的消息循环部分包含了一个关于检测游戏单位状态位置、刷新游戏单位和重画新图函数的调用。

在这些调用中一般有两种方法:
1.在消息循环中直接调用有关函数。比如说在一个RPG的游戏中每个循环都检测主角的的位置是否发生改变,若改变了则在新位置上重画主角的图。
2.通过检测WM_TIMER消息,以决定是否调用有关函数。即是每隔一段时间(若干个时钟周期),检测一次,然后决定函数的调用与否。

在上面的两种方法里,第一种是现在较常用的,它的缺点是CPU资源占用相对较多,但对不同的机型适应性较强,较稳定。第二种在一些较老的游戏或对速度要求不高的游戏中较常见,与第一种相比它的CPU资源占用相对较少,但在不同的机型中表现差异极大。

在谈WinMain()的编制时,窗口函数(WINPROC)的编制是必须说的。窗口函数可以说是把功能不同的函数通过Switch-Case结构连起来组成一个复杂程序的线索。它的基本编写方法在Windows编程基础中我们就已经谈到了。仔细阅读例程中的MainWndProc()函数相信对您是有相当大的帮助的。

/*
* MainWndProc
*
* Callback for all Windows messages
*/
long FAR PASCAL MainWndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
PAINTSTRUCT ps;
HDC hdc;

switch( message )
{
case WM_SIZE:
case WM_MOVE:
if (IsIconic(hWnd))
{
Msg("FoxBear is minimized, pausing");
PauseGame();
}

if (bFullscreen)
{
SetRect(&rcWindow, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
}
else
{
GetClientRect(hWnd, &rcWindow);
ClientToScreen(hWnd, (LPPOINT)&rcWindow);
ClientToScreen(hWnd, (LPPOINT)&rcWindow+1);
}
Msg("WINDOW RECT: [%d,%d,%d,%d]", rcWindow.left, rcWindow.top, rcWindow.right, rcWindow.bottom);
break;

case WM_ACTIVATEAPP:
bIsActive = (BOOL)wParam && GetForegroundWindow() == hWnd;

if (bIsActive)
Msg("FoxBear is active");
else
Msg("FoxBear is not active");

//
// while we were not-active something bad happened that caused us
// to pause, like a surface restore failing or we got a palette
// changed, now that we are active try to fix things
//
if (bPaused && bIsActive)
{
if (RestoreGame())
{
UnPauseGame();
}
else
{
if (GetForegroundWindow() == hWnd)
{
//
// we are unable to restore, this can happen when
// the screen resolution or bitdepth has changed
// we just reload all the art again and re-create
// the front and back buffers. this is a little
// overkill we could handle a screen res change by
// just recreating the front and back buffers we dont
// need to redo the art, but this is way easier.
//
if (InitGame())
{
UnPauseGame();
}
}
}
}
break;

case WM_QUERYNEWPALETTE:
//
// we are getting the palette focus, select our palette
//
if (!bFullscreen && lpPalette && lpFrontBuffer)
{
HRESULT ddrval;

ddrval = IDirectDrawSurface_SetPalette(lpFrontBuffer,lpPalette);
if( ddrval == DDERR_SURFACELOST )
{
IDirectDrawSurface_Restore( lpFrontBuffer );

ddrval= IDirectDrawSurface_SetPalette(lpFrontBuffer,lpPalette);
if( ddrval == DDERR_SURFACELOST )
{
Msg(" Failed to restore palette after second try");
}
}

//
// Restore normal title if palette is ours
//

if( ddrval == DD_OK )
{
SetWindowText( hWnd, OUR_APP_NAME );
}
}
break;

case WM_PALETTECHANGED:
//
// if another app changed the palette we dont have full control
// of the palette. NOTE this only applies for FoxBear in a window
// when we are fullscreen we get all the palette all of the time.
//
if ((HWND)wParam != hWnd)
{
if( !bFullscreen )
{
if( !bStress )
{
Msg("***** PALETTE CHANGED, PAUSING GAME");
PauseGame();
}
else
{
Msg("Lost palette but continuing");
SetWindowText( hWnd, OUR_APP_NAME
" - palette changed COLORS PROBABLY WRONG" );
}
}
}
break;

case WM_DISPLAYCHANGE:
break;

case WM_CREATE:
break;

case WM_SETCURSOR:
if (bFullscreen && bIsActive)
{
SetCursor(NULL);
return TRUE;
}
break;

case WM_SYSKEYUP:
switch( wParam )
{
// handle ALT+ENTER (fullscreen)
case VK_RETURN:
bFullscreen = !bFullscreen;
ExitGame();
DDDisable(TRUE); // destroy DirectDraw object
GameMode.cx = 320;
GameMode.cy = 200;
InitGame();
break;
}
break;

case WM_KEYDOWN:
switch( wParam )
{
case VK_NUMPAD5:
lastInput=KEY_STOP;
break;
case VK_DOWN:
case VK_NUMPAD2:
lastInput=KEY_DOWN;
break;
case VK_LEFT:
case VK_NUMPAD4:
lastInput=KEY_LEFT;
break;
case VK_RIGHT:
case VK_NUMPAD6:
lastInput=KEY_RIGHT;
break;
case VK_UP:
case VK_NUMPAD8:
lastInput=KEY_UP;
break;
case VK_HOME:
case VK_NUMPAD7:
lastInput=KEY_JUMP;
break;
case VK_NUMPAD3:
lastInput=KEY_THROW;
break;
case VK_F5:
bShowFrameCount = !bShowFrameCount;
if( bShowFrameCount )
{
dwFrameCount = 0;
dwFrameTime = timeGetTime();
}
break;

case VK_F6:
{
static i;
//
// find our current mode in the mode list
//
if(bFullscreen)
{
for (i=0; i{
if (ModeList[i].bpp == (int)GameBPP &&
ModeList[i].w == GameSize.cx &&
ModeList[i].h == GameSize.cy)
{
break;
}
}
}else
{
for (i=0; i{
if (ModeList[i].w == GameSize.cx &&
ModeList[i].h == GameSize.cy)
{
break;
}
}
}
//
// now step to the next mode, wrapping to the first one.
//
if (++i >= NumModes)
{
i = 0;
}
Msg("ModeList %d %d",i,NumModes);
GameMode.cx = ModeList[i].w;
GameMode.cy = ModeList[i].h;
GameBPP = ModeList[i].bpp;
bStretch = FALSE;
InitGame();
}
break;
case VK_F7:
GameBPP = GameBPP == 8 ? 16 : 8;
InitGame();
break;

case VK_F8:
if (bFullscreen)
{
bStretch = !bStretch;
InitGame();
}
else
{
RECT rc;

GetClientRect(hWnd, &rc);

bStretch = (rc.right != GameSize.cx) ||
(rc.bottom != GameSize.cy);

if (bStretch = !bStretch)
SetRect(&rc, 0, 0, GameMode.cx*2, GameMode.cy*2);
else
SetRect(&rc, 0, 0, GameMode.cx, GameMode.cy);

AdjustWindowRectEx(&rc,
GetWindowStyle(hWnd),
GetMenu(hWnd) != NULL,
GetWindowExStyle(hWnd));

SetWindowPos(hWnd, NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}
break;
case VK_F9: DevIndex ++;
bUseEmulation = FALSE;
if (DevIndex >= MaxDevIndex)
DevIndex = 0;

ExitGame();
DDDisable(TRUE); // destroy DirectDraw object
InitGame();
break;
case VK_F4:
// treat F4 like ALT+ENTER (fullscreen)
PostMessage(hWnd, WM_SYSKEYUP, VK_RETURN, 0);
break;

case VK_F3:
bPaused = !bPaused;
break;

case VK_ESCAPE:
case VK_F12:
PostMessage(hWnd, WM_CLOSE, 0, 0);
return 0;
}
break;

case WM_PAINT:
hdc = BeginPaint( hWnd, &ps );
if (bPaused)
{
char *sz = "Game is paused, this is not a bug.";
TextOut(ps.hdc, 0, 0, sz, lstrlen(sz));
}
EndPaint( hWnd, &ps );
return 1;

case WM_DESTROY:
hWndMain = NULL;
lastInput=0;
DestroyGame(); // end of game
DDDisable(TRUE); // destroy DirectDraw object
PostQuitMessage( 0 );
break;
}

return DefWindowProc(hWnd, message, wParam, lParam);

} /* MainWndProc */


第三节 游戏初始化

游戏初始化包括三部分:
1.Windows的初始化。
2.游戏工具的初始化。
3.游戏的初始化。

在这三部分中Windows的初始化,也就是对窗口的注册、定义和初始化。我们在Win- dows编程基础中已经谈过,这里就不再详述了。

游戏工具的初始化,是指对游戏程序中用到的工具进行初始化。对于一个游戏而言我们需要针对游戏的需要使用一些对图形或声音管理绘制或播放的以及其他功能的系统,这些系统就是我们所说的游戏工具(有时人们也称之为游戏引擎)。这些工具有时是由一些游戏公司提供的,比如MICROSOFT的DirectX5 SDK,有时是自己针对游戏需要编制的或使用上一部作品中用过的系统。在本例程中是指对Directdraw和DirectSound进行初始化,您可以通过阅读DDinit()和 InitSound()以及InitGame()函数的一部分的原代码以及阅读我们提供有关Directdraw和DirectSound的章节来理解。

DDinit()和 InitSound()以及InitGame()函数的代码:
/*
* InitGame
*
* Initializing current game
*/
BOOL InitGame( void )
{
ExitGame();

GameSize = GameMode;

/*
* initialize sound
*/
InitSound( hWndMain );

/*
* init DirectDraw, set mode, ...
* NOTE GameMode might be set to 640x480 if we cant get the asked for mode.
*/
if( !PreInitializeGame() )
{
return FALSE;
}

if (bStretch && bFullscreen)
{
GameSize.cx = GameMode.cx / 2;
GameSize.cy = GameMode.cy / 2;
GameRect.left = GameMode.cx - GameSize.cx;
GameRect.top = GameMode.cy - GameSize.cy;
GameRect.right = GameMode.cx;
GameRect.bottom = GameMode.cy;

if (lpStretchBuffer)
Msg("Stretching using a system-memory stretch buffer");
else
Msg("Stretching using a VRAM->VRAM blt");
}
else
{
GameRect.left = (GameMode.cx - GameSize.cx) / 2;
GameRect.top = (GameMode.cy - GameSize.cy) / 2;
GameRect.right = GameRect.left + GameSize.cx;
GameRect.bottom = GameRect.top + GameSize.cy;
}

/*
* setup our palette
*/
if( GameBPP == 8 )
{
lpPalette = ReadPalFile( NULL ); // create a 332 palette

if( lpPalette == NULL )
{
Msg( "Palette create failed" );
return FALSE;
}

IDirectDrawSurface_SetPalette( lpFrontBuffer, lpPalette );
}

/*
* load all the art and things.
*/
if( !InitializeGame() )
{
return FALSE;
}

/*
* init our code to draw the FPS
*/
makeFontStuff();

/*
* spew some stats
*/
{
DDCAPS ddcaps;
ddcaps.dwSize = sizeof( ddcaps );
IDirectDraw_GetCaps( lpDD, &ddcaps, NULL );
Msg( "Total=%ld, Free VRAM=%ld", ddcaps.dwVidMemTotal, ddcaps.dwVidMemFree );
Msg( "Used = %ld", ddcaps.dwVidMemTotal- ddcaps.dwVidMemFree ); }

return TRUE;

} /* InitGame */

/*
* InitSound
*
* Sets up the DirectSound object and loads all sounds into secondary
* DirectSound buffers. Returns FALSE on error, or TRUE if successful
*/
BOOL InitSound( HWND hwndOwner )
{
int idx;
DSBUFFERDESC dsBD;
IDirectSoundBuffer *lpPrimary;

DSEnable(hwndOwner);

if (lpDS == NULL)
return TRUE;

/*
* Load all sounds -- any that can't load for some reason will have NULL
* pointers instead of valid SOUNDEFFECT data, and we will know not to
* play them later on.
*/
for( idx = 0; idx < NUM_SOUND_EFFECTS; idx++ )
{
if (SoundLoadEffect((EFFECT)idx))
{
DSBCAPS caps;

caps.dwSize = sizeof(caps);
IDirectSoundBuffer_GetCaps(lpSoundEffects[idx], &caps);

if (caps.dwFlags & DSBCAPS_LOCHARDWARE)
Msg( "Sound effect %s in hardware", szSoundEffects[idx]);
else
Msg( "Sound effect %s in software", szSoundEffects[idx]);
}
else
{
Msg( "cant load sound effect %s", szSoundEffects[idx]);
}
}

/*
* get the primary buffer and start it playing
*
* by playing the primary buffer, DirectSound knows to keep the
* mixer active, even though we are not making any noise.
*/

ZeroMemory( &dsBD, sizeof(DSBUFFERDESC) );
dsBD.dwSize = sizeof(dsBD);
dsBD.dwFlags = DSBCAPS_PRIMARYBUFFER;

if (SUCCEEDED(IDirectSound_CreateSoundBuffer(lpDS, &dsBD, &lpPrimary, NULL)))
{
if (!SUCCEEDED(IDirectSoundBuffer_Play(lpPrimary, 0, 0, DSBPLAY_LOOPING)))
{
Msg("Unable to play Primary sound buffer");
}

IDirectSoundBuffer_Release(lpPrimary);
}
else
{
Msg("Unable to create Primary sound buffer");
}

return TRUE;

} /* InitSound */


/*
* DDInit
*/
BOOL DDInit( void )
{
DirectDrawEnumerate(&DDEnumCallback,NULL);
DDEnumCallback((GUID *)DDCREATE_EMULATIONONLY, "Hardware Emulation Layer", "", NULL);
return TRUE;
}


游戏的初始化是指调入游戏中的图象、声音等资源和游戏中的角色、道具的属性、
初始位置、状态等并画出初始画面的图象以及游戏的系统、操作方法的定义、游戏的规则等。比如说在一个RPG游戏之中,在游戏开始时内存中就应装入主角的图象组(比如走时的几幅图,状态对话框中的图)、状态(级别、HP、MP、DP等等)、属性(性别、职业等)等,描述整个游戏世界的图,NPC的各种属性、游戏的规则(各种攻击方式的效果、升级所需的经验值等)等等,总之要装入您所设计的游戏世界的一切。在例程的InitGame()中调用的函数InitializeGame()就完成了这个任务。

InitializeGame()的代码:

/*
* InitializeGame
*/
BOOL InitializeGame ( void )
{
Splash();

hBitmapList = LoadBitmaps();
if( hBitmapList == NULL )
{
return FALSE;
}

InitTiles( &hTileList, hBitmapList, C_TILETOTAL );

InitPlane( &hForeground, &hForePosList, "FORELIST", C_FORE_W, C_FORE_H, C_FORE_DENOM );
TilePlane( hForeground, hTileList, hForePosList );

InitPlane( &hMidground, &hMidPosList, "MIDLIST", C_MID_W, C_MID_H, C_MID_DENOM );
TilePlane( hMidground, hTileList, hMidPosList );

InitPlane( &hBackground, &hBackPosList, "BACKLIST", C_BACK_W, C_BACK_H, C_BACK_DENOM );
TilePlane( hBackground, hTileList, hBackPosList );

InitSurface( &hSurfaceList, "SURFLIST", C_FORE_W, C_FORE_H );
SurfacePlane( hForeground, hSurfaceList );

InitFox( &hFox, hBitmapList );
InitBear( &hBear, hBitmapList );
InitApple( &hApple, hBitmapList );

DDClear(); // clear all the backbuffers.

return TRUE;

} /* InitializeGame */


在现在的大部分游戏中游戏世界中的每个组成部分通常是用结构或类分别定义储存的。比如一个即时战略游戏中,各种建筑物放在一个类中,而每个建筑物的属性就放在该类的一个子类中;各种武器放在一个类中,每种武器放在该类的一个子类中。

class Weapon
{
WEAPON_TYPE Type;
char Name;
DWORD Id;
WORD Defend;
WORD Attack;
...
};

第四节 游戏内部循环

游戏内部循环包括刷新游戏单位、画游戏单位两部分。它的实现过程是这样的:检测状态,作出判断,绘出新图。看起来这并不是一个循环,对吗?是的,游戏内部循环并不是一个真正的循环,它实际上是由消息循环完成循环作用的。让我们从例程中看看这是如何实现的吧!

在消息循环中的第一个else if语句是这样的
else if (!bPaused && (bIsActive || !bFullscreen))
{
ProcessFox(lastInput);
lastInput=0;
}

if 后的表达式的含义是:当游戏没有被暂停时(bPause为FLASE)或以窗口模式显示(bFullscreen为FLASE)且窗口处于活动状态(bIsActive为TRUE)时执行
{
ProcessFox(lastInput);
lastInput=0;
}
语句段。而函数ProcessFox(lastInput)通过调用ProcessInput()和NewGameFrame( )达成刷新游戏单元和重画新图的功能。(这三个函数的原代码见例程foxbear.c和gameproc.c两文件)。

ProcessFox(lastInput):

/*
* ProcessFox
*/
BOOL ProcessFox(SHORT sInput)
{
if ((lpFrontBuffer && IDirectDrawSurface_IsLost(lpFrontBuffer) == DDERR_SURFACELOST) ||
(lpBackBuffer && IDirectDrawSurface_IsLost(lpBackBuffer) == DDERR_SURFACELOST))
{
if (!RestoreGame())
{
PauseGame();
return FALSE;
}
}


ProcessInput(sInput);
NewGameFrame();
return TRUE;

} /* ProcessFox */

static HFONT hFont;

DWORD dwFrameCount;
DWORD dwFrameTime;
DWORD dwFrames;
DWORD dwFramesLast;
SIZE sizeFPS;
SIZE sizeINFO;
int FrameRateX;
char szFPS[] = "FPS %02d";
char szINFO[] = "%dx%dx%d%s F6=mode F8=x2 ALT+ENTER=Window";
char szINFOW[] = "%dx%dx%d%s F6=mode F8=x2 ALT+ENTER=Fullscreen";

char szFrameRate[128];
char szInfo[128];

COLORREF InfoColor = RGB(0,152,245);
COLORREF FrameRateColor = RGB(255,255,0);
COLORREF BackColor = RGB(255,255,255);

/*
* initNumSurface
*/
void initNumSurface( void )
{
HDC hdc;
RECT rc;
int len;

dwFramesLast = 0;

len = wsprintf(szFrameRate, szFPS, 0, 0);

if( lpFrameRate && IDirectDrawSurface_GetDC(lpFrameRate, &hdc ) == DD_OK )
{
SelectObject(hdc, hFont);
SetTextColor(hdc, FrameRateColor);
SetBkColor(hdc, BackColor);
SetBkMode(hdc, OPAQUE);
SetRect(&rc, 0, 0, 10000, 10000);
ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, szFrameRate, len, NULL);
GetTextExtentPoint(hdc, szFrameRate, 4, &sizeFPS);
FrameRateX = sizeFPS.cx;
GetTextExtentPoint(hdc, szFrameRate, len, &sizeFPS);

IDirectDrawSurface_ReleaseDC(lpFrameRate, hdc);
}

if (bFullscreen)
len = wsprintf(szInfo, szINFO,
GameSize.cx, GameSize.cy, GameBPP,bStretch ? " x2" : "");
else
len = wsprintf(szInfo, szINFOW,
GameSize.cx, GameSize.cy, GameBPP,bStretch ? " x2" : "");

if( lpInfo && IDirectDrawSurface_GetDC(lpInfo, &hdc ) == DD_OK )
{
SelectObject(hdc, hFont);
SetTextColor(hdc, InfoColor);
SetBkColor(hdc, BackColor);
SetBkMode(hdc, OPAQUE);
SetRect(&rc, 0, 0, 10000, 10000);
ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, szInfo, len, NULL);
GetTextExtentPoint(hdc, szInfo, len, &sizeINFO);

IDirectDrawSurface_ReleaseDC(lpInfo, hdc);
}

} /* initNumSurface */

NewGameFrame( ):

/*
* NewGameFrame
*/
int NewGameFrame( void )
{

SetSpriteX( hFox, 0, P_AUTOMATIC );
SetSpriteY( hFox, 0, P_AUTOMATIC );

SetPlaneVelX( hBackground, GetSpriteVelX(hFox), P_ABSOLUTE );
SetPlaneVelX( hMidground, GetSpriteVelX(hFox), P_ABSOLUTE );
SetPlaneVelX( hForeground, GetSpriteVelX(hFox), P_ABSOLUTE );

SetPlaneX( hBackground, 0, P_AUTOMATIC );
SetPlaneX( hMidground, 0, P_AUTOMATIC );
SetPlaneX( hForeground, 0, P_AUTOMATIC );

SetSpriteX( hBear, 0, P_AUTOMATIC );
SetSpriteX( hApple, 0, P_AUTOMATIC );
SetSpriteY( hApple, 0, P_AUTOMATIC );

/*
* once all sprites are processed, display them
*
* If we are using destination transparency instead of source
* transparency, we need to paint the background with the color key
* and then paint our sprites and planes in reverse order.
*
* Since destination transparency will allow you to only write pixels
* on the destination if the transparent color is present, reversing
* the order (so that the topmost bitmaps are drawn first instead of
* list) causes everything to come out ok.
*/
if( bTransDest )
{
gfxFillBack( dwColorKey );

DisplayFrameRate();

DisplaySprite( hBuffer, hApple, GetPlaneX(hForeground) );
DisplaySprite( hBuffer, hBear, GetPlaneX(hForeground) );
DisplaySprite( hBuffer, hFox, GetPlaneX(hForeground) );

DisplayPlane( hBuffer, hForeground );
DisplayPlane( hBuffer, hMidground );
DisplayPlane( hBuffer, hBackground );
}
else
{
DisplayPlane( hBuffer, hBackground );
DisplayPlane( hBuffer, hMidground );
DisplayPlane( hBuffer, hForeground );

DisplaySprite( hBuffer, hFox, GetPlaneX(hForeground) );
DisplaySprite( hBuffer, hBear, GetPlaneX(hForeground) );
DisplaySprite( hBuffer, hApple, GetPlaneX(hForeground) );

DisplayFrameRate();
}

gfxSwapBuffers();

return 0;

} /* NewGameFrame */


第五节 刷新游戏单元
刷新游戏单位的作用是在每一桢刷新游戏单位的状态。请您先阅读一下下面的ProcessInput()函数的部分代码,然后再看看下面这两个例子。

ProcessInput()函数的部分代码:

/*
* ProcessInput
*/
BOOL ProcessInput( SHORT input )
{
static BOOL fBearPlaying = FALSE;
LONG foxSpeedX;
LONG foxSpeedY;
LONG foxX;
LONG foxY;
LONG bearX;
LONG bearY;
LONG appleX;
LONG appleY;
ACTION foxAction;
DIRECTION foxDir;
BOOL cont = TRUE;

foxSpeedX = GetSpriteVelX( hFox );
foxAction = GetSpriteAction( hFox );
foxDir = GetSpriteDirection( hFox );

if( (GetSpriteActive(hFox) == FALSE) && (input != 4209) )
{
input = 0;
}
switch( input )
{
case KEY_DOWN:
if( foxAction == STOP )
{
break;
}
else if( foxAction == STILL )
{
SetSpriteAction( hFox, CROUCH, SAME );
}
else if( foxAction == WALK )
{
SetSpriteAction( hFox, CROUCHWALK, SAME );
}
break;

case KEY_LEFT:
if( foxAction == STOP )
{
break;
}
else if( foxSpeedX == 0 )
{
if( foxAction == STILL )
{
if( foxDir == RIGHT )
{
ChangeSpriteDirection( hFox );
SetPlaneSlideX( hForeground, -C_BOUNDDIF, P_RELATIVE );
SetPlaneSlideX( hMidground, -C_BOUNDDIF, P_RELATIVE );
SetPlaneSlideX( hBackground, -C_BOUNDDIF, P_RELATIVE );
SetPlaneIncremX( hForeground, C_BOUNDINCREM, P_ABSOLUTE );
SetPlaneIncremX( hBackground, C_BOUNDINCREM, P_ABSOLUTE );
SetPlaneIncremX( hMidground, C_BOUNDINCREM, P_ABSOLUTE );
}
else
{
SetSpriteAction( hFox, WALK, LEFT );
SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
SetSpriteVelX( hFox, -C_FOX_XMOVE, P_RELATIVE );
}
}
else if( foxAction == CROUCH )
{
if( foxDir == RIGHT )
{
ChangeSpriteDirection( hFox );
SetPlaneSlideX( hForeground, -C_BOUNDDIF, P_RELATIVE );
SetPlaneSlideX( hMidground, -C_BOUNDDIF, P_RELATIVE );
SetPlaneSlideX( hBackground, -C_BOUNDDIF, P_RELATIVE );
SetPlaneIncremX( hForeground, C_BOUNDINCREM, P_ABSOLUTE );
SetPlaneIncremX( hBackground, C_BOUNDINCREM, P_ABSOLUTE );
SetPlaneIncremX( hMidground, C_BOUNDINCREM, P_ABSOLUTE );
}
else
{
SetSpriteAction( hFox, CROUCHWALK, LEFT );
SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
SetSpriteVelX( hFox, -C_FOX_XMOVE, P_RELATIVE );
}
}
else
{
SetSpriteVelX( hFox, -C_FOX_XMOVE, P_RELATIVE );
}
} else {
SetSpriteVelX( hFox, -C_FOX_XMOVE, P_RELATIVE );
}
break;

case KEY_RIGHT:
.
.
.
case KEY_STOP:
if( foxAction == STOP )
{
break;
}
else if( (foxAction == RUN) || (foxAction == BLURR) )
{
SetSpriteAction( hFox, STOP, SAME );
SetSpriteAccX( hFox, -foxSpeedX / 25, P_ABSOLUTE );
SoundPlayEffect( SOUND_STOP );
} else {
SetSpriteVelX( hFox, 0, P_ABSOLUTE );
}
break;

case KEY_UP:
if( foxAction == STOP )
{
break;
}
else if( foxAction == CROUCH )
{
SetSpriteAction( hFox, STILL, SAME );
}
else if( foxAction == CROUCHWALK )
{
SetSpriteAction( hFox, WALK, SAME );
}
break;

case KEY_JUMP:
if( foxAction == STOP )
{
break;
}
else
if( (foxAction == STILL) || (foxAction == WALK) ||
(foxAction == RUN) || (foxAction == CROUCH) ||
(foxAction == CROUCHWALK) )
{
SetSpriteAction( hFox, JUMP, SAME );
SetSpriteSwitchType( hFox, TIME );
SetSpriteSwitch( hFox, C_FOX_JUMPSWITCH, P_ABSOLUTE );
SetSpriteVelY( hFox, -C_FOX_JUMPMOVE, P_ABSOLUTE );
SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
SoundPlayEffect( SOUND_JUMP );
}
break;

case KEY_THROW:
if( foxAction == STOP )
{
break;
}
else if( (foxAction == STILL) || (foxAction == WALK) ||
(foxAction == RUN) || (foxAction == CROUCH) ||
(foxAction == CROUCHWALK) )
{
SetSpriteAction( hFox, THROW, SAME );
SetSpriteSwitch( hFox, C_FOX_THROWSWITCH, P_ABSOLUTE );
SetSpriteVelX( hFox, 0, P_ABSOLUTE );
SetSpriteSwitchType( hFox, TIME );
}
else if( foxAction == JUMP )
{
SetSpriteAccY( hFox, 0, P_ABSOLUTE );
SetSpriteSwitch( hFox, C_FOX_THROWSWITCH, P_ABSOLUTE );
SetSpriteAction( hFox, JUMPTHROW, SAME );
SetSpriteVelY( hFox, 0, P_ABSOLUTE );
SetSpriteSwitchDone( hFox, FALSE );
SetSpriteSwitchForward( hFox, TRUE );
}
break;

default:
break;
}

/*
* Fox actions follow...
*/
if( GetSpriteActive(hFox) == FALSE )
{
goto bearActions;
}

if( abs(GetSpriteVelX( hFox )) < C_FOX_XMOVE )
{
SetSpriteVelX( hFox, 0, P_ABSOLUTE );
}

foxAction = GetSpriteAction( hFox );

if( GetSpriteVelY(hFox) == 0 )
{
if( GetSurface( hForeground, hFox ) == FALSE )
{
if( (foxAction == WALK) || (foxAction == RUN) ||
(foxAction == CROUCHWALK) )
{
SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
}
else if( foxAction == STOP )
{
SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
SetSpriteAccX( hFox, 0, P_ABSOLUTE );
}
}
}
else if( GetSpriteVelY(hFox) > 2 * C_UNIT )
{
if( (foxAction == WALK) || (foxAction == RUN) ||
(foxAction == CROUCHWALK) )
{
SetSpriteSwitchForward( hFox, FALSE );
SetSpriteAction( hFox, JUMP, SAME );
SetSpriteSwitchType( hFox, TIME );
SetSpriteSwitch( hFox, C_FOX_JUMPSWITCH, P_ABSOLUTE );
}
if( foxAction == STOP )
{
SetSpriteAction( hFox, STUNNED, SAME );
SetSpriteAccX( hFox, -GetSpriteVelX(hFox) / 25, P_ABSOLUTE );
SoundPlayEffect( SOUND_STUNNED );
}
}

foxSpeedX = GetSpriteVelX( hFox );
foxSpeedY = GetSpriteVelY( hFox );
foxAction = GetSpriteAction( hFox );
foxDir = GetSpriteDirection( hFox );

switch( foxAction ) {
case STUNNED:
if( (GetSpriteVelY(hFox) >= 0) &&
(!GetSurface( hForeground, hFox ) == FALSE) )
{
SetSpriteAccY( hFox, 0, P_ABSOLUTE );
SetSpriteAction( hFox, STOP, SAME );
SetSpriteVelY( hFox, 0, P_ABSOLUTE );
SetSpriteAccX( hFox, -foxSpeedX / 25, P_ABSOLUTE );
// SetSurface( hForeground, hFox );
SoundPlayEffect( SOUND_STOP );
}
break;

case CROUCHWALK:
if( foxSpeedX == 0 )
{
SetSpriteAction( hFox, CROUCH, SAME );
}
else if( foxSpeedX > C_FOX_WALKMOVE )
{
SetSpriteVelX( hFox, C_FOX_WALKMOVE, P_ABSOLUTE );
}
else if( foxSpeedX < -C_FOX_WALKMOVE )
{
SetSpriteVelX( hFox, -C_FOX_WALKMOVE, P_ABSOLUTE );
}
break;

case STOP:
if( foxSpeedX == 0 )
{
SetSpriteAction( hFox, STILL, SAME );
SetSpriteAccX( hFox, 0, P_ABSOLUTE );
}
break;

case RUN:
if( (foxSpeedX < C_FOX_WALKTORUN ) && (foxSpeedX > 0) )
{
SetSpriteAction( hFox, WALK, RIGHT );
SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
}
else if( foxSpeedX > C_FOX_RUNTOBLURR )
{
SetSpriteAction( hFox, BLURR, RIGHT );
SetSpriteSwitch( hFox, C_FOX_BLURRSWITCH, P_ABSOLUTE );
}
else if( (foxSpeedX > -C_FOX_WALKTORUN ) && (foxSpeedX < 0) )
{
SetSpriteAction( hFox, WALK, LEFT );
SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
}
else if( foxSpeedX < -C_FOX_RUNTOBLURR )
{
SetSpriteAction( hFox, BLURR, LEFT );
SetSpriteSwitch( hFox, C_FOX_BLURRSWITCH, P_ABSOLUTE );
}
break;

case WALK:
if( foxSpeedX == 0 )
{
SetSpriteAction( hFox, STILL, SAME );
}
else if( foxSpeedX > C_FOX_WALKTORUN )
{
SetSpriteAction( hFox, RUN, RIGHT );
SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
}
else if( foxSpeedX < -C_FOX_WALKTORUN )
{
SetSpriteAction( hFox, RUN, LEFT );
SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
}
break;

case BLURR:
if( (foxSpeedX < C_FOX_RUNTOBLURR ) && (foxSpeedX > C_FOX_WALKTORUN) )
{
SetSpriteAction( hFox, RUN, RIGHT );
SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
}
else if( (foxSpeedX > -C_FOX_RUNTOBLURR ) && (foxSpeedX < -C_FOX_WALKTORUN) )
{
SetSpriteAction( hFox, RUN, LEFT );
SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
}
break;

case JUMPTHROW:
if( !GetSpriteSwitchDone(hFox) == FALSE )
{
SetSpriteSwitchForward( hFox, FALSE );
SetSpriteAction( hFox, JUMP, SAME );
SetSpriteSwitch( hFox, C_FOX_JUMPSWITCH, P_ABSOLUTE );
SetSpriteSwitchDone( hFox, FALSE );
SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
SoundPlayEffect( SOUND_THROW );
}
else
if( (GetSpriteBitmap(hFox) == 1) &&
(GetSpriteDirection(hFox) == RIGHT) )
{
SetSpriteActive( hApple, TRUE );
SetSpriteX( hApple, GetSpriteX(hFox) + 60 * C_UNIT, P_ABSOLUTE );
SetSpriteY( hApple, GetSpriteY(hFox) + 30 * C_UNIT, P_ABSOLUTE );
SetSpriteVelX( hApple, 8 * C_UNIT, P_ABSOLUTE );
SetSpriteVelY( hApple, -4 * C_UNIT, P_ABSOLUTE );
SetSpriteAccX( hApple, 0, P_ABSOLUTE );
SetSpriteAccY( hApple, C_UNIT / 4, P_ABSOLUTE );
}
else if( (GetSpriteBitmap(hFox) == 1) &&
(GetSpriteDirection(hFox) == LEFT) )
{
SetSpriteActive( hApple, TRUE );
SetSpriteX( hApple, GetSpriteX(hFox) + 15 * C_UNIT, P_ABSOLUTE );
SetSpriteY( hApple, GetSpriteY(hFox) + 30 * C_UNIT, P_ABSOLUTE );
SetSpriteVelX( hApple, -8 * C_UNIT, P_ABSOLUTE );
SetSpriteVelY( hApple, -4 * C_UNIT, P_ABSOLUTE );
SetSpriteAccX( hApple, 0, P_ABSOLUTE );
SetSpriteAccY( hApple, C_UNIT / 4, P_ABSOLUTE );
}
break;

case THROW:
if( !GetSpriteSwitchDone(hFox) == FALSE )
{
SetSpriteAction( hFox, STILL, SAME );
SetSpriteSwitchType( hFox, HOR );
SetSpriteSwitch( hFox, 0, P_ABSOLUTE );
SetSpriteSwitchDone( hFox, FALSE );
SoundPlayEffect( SOUND_THROW );
}
else if( (GetSpriteBitmap(hFox) == 1) &&
(GetSpriteDirection(hFox) == RIGHT) )
{
SetSpriteActive( hApple, TRUE );
SetSpriteX( hApple, GetSpriteX(hFox) + 60 * C_UNIT, P_ABSOLUTE );
SetSpriteY( hApple, GetSpriteY(hFox) + 50 * C_UNIT, P_ABSOLUTE );
SetSpriteVelX( hApple, 8 * C_UNIT, P_ABSOLUTE );
SetSpriteVelY( hApple, -4 * C_UNIT, P_ABSOLUTE );
SetSpriteAccX( hApple, 0, P_ABSOLUTE );
SetSpriteAccY( hApple, C_UNIT / 4, P_ABSOLUTE );
}
else if( (GetSpriteBitmap(hFox) == 1) &&
(GetSpriteDirection(hFox) == LEFT) )
{
SetSpriteActive( hApple, TRUE );
SetSpriteX( hApple, GetSpriteX(hFox) + 20 * C_UNIT, P_ABSOLUTE );
SetSpriteY( hApple, GetSpriteY(hFox) + 50 * C_UNIT, P_ABSOLUTE );
SetSpriteVelX( hApple, -8 * C_UNIT, P_ABSOLUTE );
SetSpriteVelY( hApple, -4 * C_UNIT, P_ABSOLUTE );
SetSpriteAccX( hApple, 0, P_ABSOLUTE );
SetSpriteAccY( hApple, C_UNIT / 4, P_ABSOLUTE );
}
break;

case JUMP:
if( (foxSpeedY >= 0) && (!GetSpriteSwitchForward( hFox ) == FALSE) )
{
SetSpriteSwitchForward( hFox, FALSE );
}
else if( GetSpriteSwitchForward( hFox ) == FALSE )
{
if( (!GetSurface( hForeground, hFox ) == FALSE) ||
(!GetSurface( hForeground, hFox ) == FALSE) )
{
if( foxSpeedX >= C_FOX_RUNMOVE )
{
SetSpriteAction( hFox, RUN, SAME );
SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
}
else if( foxSpeedX == 0 )
{
SetSpriteAction( hFox, STILL, SAME );
SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
}
else
{
SetSpriteAction( hFox, WALK, SAME );
SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
}

SetSpriteAccY( hFox, 0, P_ABSOLUTE );
SetSpriteVelY( hFox, 0, P_ABSOLUTE );
SetSpriteSwitchType( hFox, HOR );
SetSpriteSwitchForward( hFox, TRUE );
// SetSurface( hForeground, hFox );
SetSpriteSwitchDone( hFox, FALSE );
}
}
break;

}

/*
* Bear Actions
*/
bearActions:

foxX = GetSpriteX( hFox );
foxY = GetSpriteY( hFox );
bearX = GetSpriteX( hBear );
bearY = GetSpriteY( hBear );
appleX = GetSpriteX( hApple );
appleY = GetSpriteY( hApple );

switch( GetSpriteAction( hBear ) ) {
case STRIKE:
if( GetSpriteBitmap( hBear ) == 2 )
{
if( (bearX > foxX - C_UNIT * 30) && (bearX < foxX + C_UNIT * 40) &&
(bearY < foxY + C_UNIT * 60) )
{
SetSpriteActive( hFox, FALSE );
if( !fBearPlaying )
{
SoundPlayEffect( SOUND_BEARSTRIKE );
fBearPlaying = TRUE;
}
}
else
{
SetSpriteAction( hBear, MISS, SAME );
SetSpriteSwitch( hBear, C_BEAR_MISSSWITCH, P_ABSOLUTE );
SetSpriteSwitchDone( hBear, FALSE );
}
}
else if( !GetSpriteSwitchDone( hBear ) == FALSE )
{
SetSpriteAction( hBear, CHEW, SAME );
SetSpriteSwitchDone( hBear, FALSE );
chewCount = 0;
fBearPlaying = FALSE;
}
break;

case MISS:
if( !fBearPlaying )
{
SoundPlayEffect( SOUND_BEARMISS );
fBearPlaying = TRUE;
}
if( !GetSpriteSwitchDone( hBear ) == FALSE )
{
SetSpriteAction( hBear, WALK, SAME );
SetSpriteVelX( hBear, -C_BEAR_WALKMOVE, P_ABSOLUTE );
SetSpriteSwitch( hBear, C_BEAR_WALKSWITCH, P_ABSOLUTE );
SetSpriteSwitchType( hBear, HOR );
fBearPlaying = FALSE;
}
break;

case WALK:
if( (!GetSpriteActive(hApple) == FALSE) && (appleX > bearX) &&
(appleX > bearX + 80 * C_UNIT) && (appleY > bearY + 30 * C_UNIT) )
{
SetSpriteAction( hBear, STRIKE, SAME );
SetSpriteVelX( hBear, 0, P_ABSOLUTE );
SetSpriteSwitchType( hBear, TIME );
SetSpriteSwitch( hBear, C_BEAR_STRIKESWITCH, P_ABSOLUTE ); SetSpriteSwitchDone( hBear, FALSE );
}
else if( (bearX > foxX - C_UNIT * 30) &&
(bearX < foxX + C_UNIT * 30) &&
(bearY < foxY + C_UNIT * 60) )
{
SetSpriteAction( hBear, STRIKE, SAME );
SetSpriteVelX( hBear, 0, P_ABSOLUTE );
SetSpriteSwitchType( hBear, TIME );
SetSpriteSwitch( hBear, C_BEAR_STRIKESWITCH, P_ABSOLUTE );
SetSpriteSwitchDone( hBear, FALSE );
}
break;

case CHEW:
++chewCount;
if( chewCount >= 200 )
{
SetSpriteAction( hBear, STRIKE, SAME );
SetSpriteSwitch( hBear, C_BEAR_STRIKESWITCH, P_ABSOLUTE );
SetSpriteVelX( hBear, 0, P_ABSOLUTE );
SetSpriteSwitchDone( hBear, FALSE );

if( GetSpriteDirection(hFox) == RIGHT )
{
SetPlaneSlideX( hForeground, -C_BOUNDDIF, P_RELATIVE );
SetPlaneSlideX( hMidground, -C_BOUNDDIF, P_RELATIVE );
SetPlaneSlideX( hBackground, -C_BOUNDDIF, P_RELATIVE );
}

chewDif = GetSpriteX(hFox);

SetSpriteActive( hFox, TRUE );
SetSpriteAction( hFox, STUNNED, LEFT );
SetSpriteX( hFox, GetSpriteX(hBear), P_ABSOLUTE );
SetSpriteY( hFox, GetSpriteY(hBear), P_ABSOLUTE );
SetSpriteAccX( hFox, 0, P_ABSOLUTE );
SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
SetSpriteVelX( hFox, -8 * C_UNIT, P_ABSOLUTE );
SetSpriteVelY( hFox, -10 * C_UNIT, P_ABSOLUTE );
SetSpriteSwitch( hFox, 0, P_ABSOLUTE );
SoundPlayEffect( SOUND_STUNNED );

chewDif -= GetSpriteX(hFox);

SetPlaneSlideX( hForeground, -chewDif, P_RELATIVE );
SetPlaneSlideX( hMidground, -chewDif, P_RELATIVE );
SetPlaneSlideX( hBackground, -chewDif, P_RELATIVE );
SetPlaneIncremX( hForeground, C_BOUNDINCREM, P_ABSOLUTE );
SetPlaneIncremX( hMidground, C_BOUNDINCREM, P_ABSOLUTE );
SetPlaneIncremX( hBackground, C_BOUNDINCREM, P_ABSOLUTE );
}
break;
}

/*
* Apple actions...
*/
if( (GetSpriteVelY(hApple) != 0) && (GetSpriteY(hApple) >= 420 * C_UNIT) )
{
SetSpriteX( hApple, 0, P_ABSOLUTE );
SetSpriteY( hApple, 0, P_ABSOLUTE );
SetSpriteAccX( hApple, 0, P_ABSOLUTE );
SetSpriteAccY( hApple, 0, P_ABSOLUTE );
SetSpriteVelX( hApple, 0, P_ABSOLUTE );
SetSpriteVelY( hApple, 0, P_ABSOLUTE );
SetSpriteActive( hApple, FALSE );
}

return cont;

} /* ProcessInput */



在射击游戏中的子弹的发射,每一帧都要检测上一帧时子弹的位置a然后确定当前帧子弹的位置b然后将该位置传给重画游戏单元的部分,在当前帧b的位置贴上子弹的图象。

在即使战略游戏中两军对战时,程序在每一帧都要根据上一帧每个战斗单位的位置和该战斗单位移动的目的、到该目的之间的障碍物的位置以及一定的路径算法确定在当前帧该战斗单位的新位置;还有要取得在上一帧时该战斗单位的生命值和所受的打击次数及强度,以确定该战斗单位的生命值。

通过阅读ProcessInput()函数的代码,我想您一定已理解了刷新游戏单元的概念。而从上面的两个例子中,您也一定发现用例程的方法很难实现这两类游戏的要求。我们不可能对每一颗子弹,每一个战斗单位进行操作,而且我们并不知道游戏者会一次发射多少颗子弹也不知道游戏者会造多少个战斗单位。我们应该怎么办呢?

考虑到每一个战斗单位(或每一颗子弹)都有相似(或相同)的属性,那么我们可以采用结构数组来储存每一个战斗单位的位置和状态。这个办法好象可行!但是仔细想想,我们又遇到了上面谈到的问题我们并不知道游戏者会一次发射多少颗子弹也不知道游戏者会造多少个战斗单位。当然我们可以采用Age of Empire的方式----限制单位的数量(我并不是说Age of Empire采用的是这种办法)。但是这意味什么呢!意味着,如果我们限定数量为50的话,在游戏者只有一个士兵时,计算机却需要为这个士兵分配50倍的内存!而且游戏者还不一定造出50个士兵。显然这并不是一个好办法!

我们应该怎么办呢?链表!链表能满足我们的要求。

class Node
{
//双向链表的指针。
Node* Next;
Node* Pre;

//节点数据。
NODE_DATA data;
...
};

链表是一种结构体的集合。在链表中的每一个结构体都包含了一个元素或指针,它指向链表中的另一个结构体。这个指针用作两个结构体之间的联系。这个概念与数组有些相似,但它允许链表的动态增长。现在的游戏中凡是遇到这种问题的一般都是采用链表的。关于链表的更多的信息请阅读有关的资料。


第六节 画游戏单元
画游戏单位的作用是在每一桢往屏幕上画游戏单位的图象。

这就是本例程中画游戏单元的主函数:
/*
* NewGameFrame
*/
int NewGameFrame( void )
{
//这里是设置游戏单元的位置:
SetSpriteX( hFox, 0, P_AUTOMATIC );
SetSpriteY( hFox, 0, P_AUTOMATIC );

SetPlaneVelX( hBackground, GetSpriteVelX(hFox), P_ABSOLUTE );
SetPlaneVelX( hMidground, GetSpriteVelX(hFox), P_ABSOLUTE );
SetPlaneVelX( hForeground, GetSpriteVelX(hFox), P_ABSOLUTE );

SetPlaneX( hBackground, 0, P_AUTOMATIC );
SetPlaneX( hMidground, 0, P_AUTOMATIC );
SetPlaneX( hForeground, 0, P_AUTOMATIC );

SetSpriteX( hBear, 0, P_AUTOMATIC );
SetSpriteX( hApple, 0, P_AUTOMATIC );
SetSpriteY( hApple, 0, P_AUTOMATIC );

//将游戏单元的图形贴到BackBuffer上:
if( bTransDest )
{
gfxFillBack( dwColorKey );

DisplayFrameRate();

DisplaySprite( hBuffer, hApple, GetPlaneX(hForeground) );
DisplaySprite( hBuffer, hBear, GetPlaneX(hForeground) );
DisplaySprite( hBuffer, hFox, GetPlaneX(hForeground) );

DisplayPlane( hBuffer, hForeground );
DisplayPlane( hBuffer, hMidground );
DisplayPlane( hBuffer, hBackground );
}
else
{
DisplayPlane( hBuffer, hBackground );
DisplayPlane( hBuffer, hMidground );
DisplayPlane( hBuffer, hForeground );

DisplaySprite( hBuffer, hFox, GetPlaneX(hForeground) );
DisplaySprite( hBuffer, hBear, GetPlaneX(hForeground) );
DisplaySprite( hBuffer, hApple, GetPlaneX(hForeground) );

DisplayFrameRate();
}

//更新前景:
gfxSwapBuffers();

return 0;

} /* NewGameFrame */

画游戏单元的顺序为:
1。清BackBuffer;

这是清BackBuffer的函数:
/*
* gfxFillBack
*/
void gfxFillBack( DWORD dwColor )
{
DDBLTFX ddbltfx;

ddbltfx.dwSize = sizeof( ddbltfx );
ddbltfx.dwFillColor = dwColor;

IDirectDrawSurface_Blt(
lpBackBuffer, // dest surface
NULL, // dest rect
NULL, // src surface
NULL, // src rect
DDBLT_COLORFILL | DDBLT_WAIT,
&ddbltfx);

} /* gfxFillBack */

2。检查游戏单元图形的Surface是否丢失;

这是检查游戏单元图形的Surface是否丢失的函数:
/*
* gfxRestoreAll
*
* restore the art when one or more surfaces are lost
*/
BOOL gfxRestoreAll()
{
GFX_BITMAP *curr;
HWND hwndF = GetForegroundWindow();

Splash();

for( curr = lpVRAM; curr != NULL; curr = curr->link)
{
if (curr->lpSurface &&
(fForceRestore || IDirectDrawSurface_IsLost(curr->lpSurface) == DDERR_SURFACELOST))
{
if( !gfxRestore(curr) )
{
Msg( "gfxRestoreAll: ************ Restore FAILED!" );
return FALSE;
}
}
}

DDClear();
fForceRestore = FALSE;
return TRUE;

} /* gfxRestoreAll */

3。将游戏单元的图形画到BackBuffer中;

这是画游戏单元图形的函数之一:
/*
* DisplayPlane
*/
BOOL DisplayPlane ( GFX_HBM hBuffer, HPLANE *hPlane )
{
USHORT n;
USHORT i;
USHORT j;
USHORT x1;
USHORT y1;
USHORT x2;
USHORT y2;
USHORT xmod;
USHORT ymod;
POINT src;
RECT dst;


x1 = (hPlane->x >> 16) / C_TILE_W;
y1 = (hPlane->y >> 16) / C_TILE_H;
x2 = x1 + C_SCREEN_W / C_TILE_W;
y2 = y1 + C_SCREEN_H / C_TILE_H;
xmod = (hPlane->x >> 16) % C_TILE_W;
ymod = (hPlane->y >> 16) % C_TILE_H;

for( j = y1; j < y2; ++j )
{
for( i = x1; i <= x2; ++i )
{
n = (i % hPlane->width) + j * hPlane->width;
if( hPlane->hBM[n] != NULL )
{
if( i == x1 )
{
dst.left = 0;
dst.right = dst.left + C_TILE_W - xmod;
src.x = xmod;
}
else if( i == x2 )
{
dst.left = (i - x1) * C_TILE_W - xmod;
dst.right = dst.left + xmod;
src.x = 0;
} else {
dst.left = (i - x1) * C_TILE_W - xmod;
dst.right = dst.left + C_TILE_W;
src.x = 0;
}

if( j == y1 )
{
dst.top = 0;
dst.bottom = dst.top + C_TILE_H - ymod;
src.y = ymod;
}
else if( j == y2 )
{
dst.top = (j - y1) * C_TILE_H - ymod;
dst.bottom = dst.top + ymod;
src.y = 0;
} else {
dst.top = (j - y1) * C_TILE_H - ymod;
dst.bottom = dst.top + C_TILE_H;
src.y = 0;
}

gfxBlt(&dst,hPlane->hBM[n],&src);
}
}
}

return TRUE;

} /* DisplayPlane */

4。将BackBuffer和FrontBuffer进行翻转;

这是全屏幕模式下的页面翻转函数:
/*
* gfxFlip
*/
BOOL gfxFlip( void )
{
HRESULT ddrval;

ddrval = IDirectDrawSurface_Flip( lpFrontBuffer, NULL, DDFLIP_WAIT );
if( ddrval != DD_OK )
{
Msg( "Flip FAILED, rc=%08lx", ddrval );
return FALSE;
}
return TRUE;

} /* gfxFlip */

这是窗口模式下的页面翻转函数:
/*
* gfxUpdateWindow
*/
BOOL gfxUpdateWindow()
{
HRESULT ddrval;

ddrval = IDirectDrawSurface_Blt(
lpFrontBuffer, // dest surface
&rcWindow, // dest rect
lpBackBuffer, // src surface
NULL, // src rect (all of it)
DDBLT_WAIT,
NULL);

return ddrval == DD_OK;

} /* gfxUpdateWindow */


第七节 计算机人工智能

计算机人工智能

记得吗?在第五节刷新游戏单元中我们谈到在刷新游戏单元时,说到在取得游戏单位的的位置后要经过一些算法的判断再确定游戏单位的新位置。包含这些算法的部
分就是游戏中实现人工智能的部分。

对于游戏中的人工智能,我比较赞同下面这个定义:一个非游戏者控制的对象在基于各种复杂因素时的决策行为就象时由真正的人作出的,这是通过使用 一个决策算法来完成的,这个决策算法根据设计者确定的规则和提供给程序的信息进行处理。

现在在大部分的游戏中采用的的方式主要有以下几种:

检索
许多人工智能的算法中都涉及到对所有可能性的检索。这个算法的实现方式是这样的,首先您应让您的程序列一个选项表,例如一个士兵到目的之间所有可能的路径。然后再使用其他的人工智能技术如排除法等来找一个最优的选择。

排序
排序与检索都是基本的人工智能技术,您可以用排序来确定最佳的决策次序。比 如,在战略游戏中计算机对手不断地根据当前的环境修改行动的优先级。

专家系统
专家系统是指运用“if then”语句的逻辑表达式来表示所有的基本规则,然后计算机根据这些规则作出智能决策。比如,在制作一个足球游戏时,就可以请一个足球专家,记下他的的足球经验,他会说明在各种情况下,他采取的踢球方式。根据这些信息,建立一套规则库,在游戏中计算机就可以按照这些规则作出决策。

其他的方式还有:机器学习和和神经网络系统,这两种方式的效果相当不错。但是却很不容易掌握,这里我们就不再详述了。


第八节 游戏内存管理

游戏内存管理

这一部分对于优质高效的游戏软件是十分重要的,内存管理不当会导致游戏性能的降低,甚至引起死机。现在的很多游戏都使用了大量的图象和复杂的规则,需要大量的内存。这就需要我们对游戏者所用机器的内存进行精心的分配和组织了。首先,我们应当调查一下现在的主流机型的内存是多少,再与达到游戏的设计目标所需的内存量之间权衡一下,然后确定一个粗略的分配方案。

这个方案一般可以这样指定:

1.这个游戏从情节上可以分为几个部分,在开始时将每个部分所共有的资料调入,然后根据情节的发展将该部分不用的资料所占用的内存释放再调入该部分所特有的资料。比如说可以分为几关或者RPG游戏中的“世界”的地图可以分成几个场景。然后在游戏者到达某一关或进入某一场景时再调入相应的图象或相应的资料。

2.在每个部分中有很多并不常用而且调用时没有严格的速度限制同时调用时并不需要太多时间(通常1秒左右即可完成)的资料,也可以在使用时调用。比如角色从大地图上走入一个城市,这个城市的图象和游戏规则等资料就可以在走入这个城市时调入。

在完成这个方案后,我们就完成了内存和硬盘之间数据交换的规划了,接下来就应考虑运行时内存内部的管理了。

在这一步中主要应注意两个问题:

1.迅速释放存放无效资料的内存;

例如:

描述GAMEWORLD的指针temp在初始化时分配了内存空间。
GAMEWORLD *temp=new GAMEWORLD(Init value);
。。。

在程序结束时要释放内存空间。
delete temp;

2.严禁使用空指针(这样会导致系统错误,甚至死机)。这里没有什么技巧,只有靠您自己的认真和仔细了。

例如:
当在程序中已经释放了temp;
下面的调用就可能导致死机:
temp->Function();

这两个问题的解决方法为:

GAMEWORLD *temp=new GAMEWORLD(Init value);
...

if(temp)
delete temp;
temp=NULL;
...

if(temp)
{
temp->Function();
...
}
else
{
提示temp为空指针。
}


第九节 游戏交互设计

游戏交互设计

交互设计,实际上就是您想让游戏者怎么去操纵游戏的发展。说简单了交互设计就是游戏者怎样去控制游戏角色的行动,在例程中对键盘的设置??用小建盘上的“456237”控制狐狸的行为就是一种简单的交互设计。说复杂了呢!就是您提供了一种什么样的方式让游戏者进入游戏之中成为游戏中的一员??他就是游戏中英勇无敌、侠肝义胆的剑客,他就是游戏中足智多谋、威震天下的将军……这就是好游戏的一个重要的要素??好的交互性。

交互性就是设计者创造的一个诱使人去玩的游戏所拥有的提供故事线索、情绪感染、真实的声音和其他创造性的媒介所给予的特性。交互设计的目的就是让游戏者进入“幻觉状态”,幻觉状态是游戏界的一个术语,它的意思是指游戏者的意识融入到游戏世界中,这样,他或她就不是在玩游戏,而是在体验另一个世界。

怎样作到这一点呢?作为编程人员应考虑的是:

第一步考虑输入设备问题,设备即是游戏者控制游戏的手段,也就是输入设备的选择和设置的问题。在这一步中应该考虑是选择键盘、鼠标、游戏杆还是几种结合的方式,或是其他类型的输入设备。然后是设置各种操作所代表的含义(就象例程中
小键盘的“4”代表左行,“5”代表停止等等,或是鼠标单击、双击某个区域及拖动时代表的含义)这些设置主要是考虑一个操作的方便性的问题。

typedef enum enum_ACTION {
NONE,
STILL,
WALK,
RUN,
JUMP,
THROW,
CROUCH,
STOP,
STUNNED,
JUMPTHROW,
CROUCHWALK,
BLURR,
STRIKE,
MISS,
CHEW,
} ACTION;


WinMainProc中:

case WM_KEYDOWN:
switch( wParam )
{
case VK_NUMPAD5:
lastInput=KEY_STOP;
break;
case VK_DOWN:
case VK_NUMPAD2:
lastInput=KEY_DOWN;
break;
case VK_LEFT:
case VK_NUMPAD4:
lastInput=KEY_LEFT;
break;
case VK_RIGHT:
case VK_NUMPAD6:
lastInput=KEY_RIGHT;
break;
case VK_UP:
case VK_NUMPAD8:
lastInput=KEY_UP;
break;
case VK_HOME:
case VK_NUMPAD7:
lastInput=KEY_JUMP;
break;
case VK_NUMPAD3:
lastInput=KEY_THROW;
break;
case VK_F5:
bShowFrameCount = !bShowFrameCount;
if( bShowFrameCount )
{
dwFrameCount = 0;
dwFrameTime = timeGetTime();
}
break;

第二步考虑信息返回的问题,这里主要是一个界面的设计的问题。这个问题我们在
第一章第六节游戏的界面设计中已经讨论过了,这里就不详述了。


第十节 游戏图形底层设计

在游戏中,计算机主要花时间在处理图象和画图象上,所以我们应尽力使这些操作适合主流机型的硬件水平或尽量少占用系统资源,这就是游戏图形底层设计的目的。在前面讲的DirectDraw和DirectX5 SDK中的Direct3D都是图形底层,还有ID在 QUAKE发行后提供的QUAKE C也是一种不错的图形底层。建立一套游戏图形底层需要大量的关于图形编程的知识和很多的时间精力,而且效果不一定好,同时在市场上也有很多图形底层可供选择。所以对于一般的游戏开发者来说,只要作的游戏使用的图象并没有使计算机不负重荷或并没有使用现有的底层所不支持的特性,我建议还是使用现有的底层。

本例程的图形底层十分简单,采用DirectDraw提供的IDirectDrawSurface_BltFast和IDirectDrawSurface_Blt函数:

if (pbm->lpSurface)
{
if( pbm->bTrans )
bltflags = bTransDest ? DDBLTFAST_DESTCOLORKEY : DDBLTFAST_SRCCOLORKEY;
else
bltflags = bTransDest ? DDBLTFAST_DESTCOLORKEY : DDBLTFAST_NOCOLORKEY;
ddrval = IDirectDrawSurface_BltFast(
lpBackBuffer, x, y,
pbm->lpSurface, &rc, bltflags | DDBLTFAST_WAIT);

if (ddrval != DD_OK)
{
Msg("BltFast failed err=%d", ddrval);
}
}
else
{
DDBLTFX ddbltfx;

rc.left = x;
rc.top = y;
rc.right = rc.left + dx;
rc.bottom = rc.top + dy;

ddbltfx.dwSize = sizeof( ddbltfx );
ddbltfx.dwFillColor = pbm->dwColor;

ddrval = IDirectDrawSurface_Blt(
lpBackBuffer, // dest surface
&rc, // dest rect
NULL, // src surface
NULL, // src rect
DDBLT_COLORFILL | DDBLT_WAIT,
&ddbltfx);
}

由于DirectDraw的通用性要求,所以虽然它提供的函数的速度很快,但是对特效的支持比较少。深入的图形底层应包括大量的高效的特效处理功能,所以我们应该能够直接对显存操作。

DirectDraw的DirectSurface提供了这个入口,它的DDSURFACEDESC结构中的变量
lpSurface就是显存映射的入口指针。

typedef struct _DDSURFACEDESC {
DWORD dwSize;
DWORD dwFlags;
DWORD dwHeight;
DWORD dwWidth;
union
{
LONG lPitch;
DWORD dwLinearSize;
};
DWORD dwBackBufferCount;
union
{
DWORD dwMipMapCount;
DWORD dwZBufferBitDepth;
DWORD dwRefreshRate;
};
DWORD dwAlphaBitDepth;
DWORD dwReserved;

LPVOID lpSurface;
DDCOLORKEY ddckCKDestOverlay;
DDCOLORKEY ddckCKDestBlt;
DDCOLORKEY ddckCKSrcOverlay;
DDCOLORKEY ddckCKSrcBlt;
DDPIXELFORMAT ddpfPixelFormat;
DDSCAPS ddsCaps;
} DDSURFACEDESC;

但是使用它之前,必须调用DirectDrawSurface3::Lock函数将此图形内存锁住,
在处理后,调用DirectDrawSurface3::Unlock函数将此内存交还给系统。
 


 


游戏开发基础(8)



第八章 例程之初始化部分
第一节DDInit():
DDInit()的作用是枚举驱动程序,它的具体运行过程如下:
首先调用DirectDrawEnumerate(),这个函数的作用在第五章 DirectDraw深入篇第三节选择DirectDraw驱动程序中已经谈到了。在本例程中这个函数的参数是&DDEnumCallback和NULL,&DDEnumCallback是指回调函数DDEnumCallback()的地址,NULL是指没有这个指向应用程序数据的指针。

回调函数DDEnumCallback()的作用是将枚举过的的驱动程序的GUDI、描述和名字存入一个结构数组aDDDevs[]中。待以后选择。

第二节定义命令行参数
1.根据命令行参数确定运行方式
首先,使用while( lpCmdLine[0] == '-' || lpCmdLine[0] == '/')检测命令行参数的标识符,然后再使用switch (*lpCmdLine++)对部分参数的含义进行定义:
-e Use emulator(使用软件模拟)
-S No Sound(无声)
-1 No backbuffer(不使用后备缓冲区)
-2 One backbuffer(一个后备缓冲区)
-4 Three backbuffers(三个后备缓冲区)
-s Use stretch(使用拉伸算法,即是在窗口模式下改变窗口的形状时对图形使用拉伸算法使图象比较匀称。)
-x Demo or stress mode(使用重音模式)

2.根据命令行参数确定显示模式
对显示模式的横轴方向的像素数GameMode.cx、纵轴方向的像素数GameMode.cy及颜色数GameMode.BPP调用getint(char**p, int def)取得命令行参数对这些项目的设定。
getint(char**p, int def)函数的运行过程为:
先检测命令行参数第一个字符是否是“”、“\r”、“\t”、“\n”或“x”。如果是就使指针p自加1并继续检测,否则检测该字符是否是小于9大于0的数。如果该字符不是小于9大于0的数则返回默认值,反之则通过
while (IS_NUM(**p)
i = i*10 + *(*p)++ - '0'
将输入的字符的ASCII值转变为数值。然后通过
while (IS_SPACE(**p))
(*p)++;
检测后面的字符是否是“”、“\r”、“\t”、“\n”或“x”,如果是就使指针p自加1并继续检测,直到出现其他字符或字符串结束。

第三节初始化Windows
程序在这一部分调用了initApplication( HINSTANCE hInstance, int nCmdShow )函数来初始化Windows。

initApplication( HINSTANCE hInstance, int nCmdShow )首先定义窗口类为:
style:指明了类风格为向窗口发送一个鼠标双击的消息。
  1pfnWndProc:指明了指向窗口函数的指针,该指针指向MainWndProc。
  cbClsExtra:指定在窗口类结构后面分配的字节数为0。
  cbWndExtra:指定在窗口实例后面分配的字节数为0。
  hInstance:注册窗口类的应用程序实例句柄是hInstance。
  hIconhIcon:划定利用窗口最小化时显示的图标通过调用LoadIcon( hInstance, MAKEINTATOM(FOX_ICON))获得。
  hCursorhCursor:定义应用程序使用的光标通过调用LoadCursor( NULL, IDC_ARROW )获得。
hbrBackground:背景刷子的标识符通过调用GetStockObject(BLACK_BRUSH)获得。
1pszMenuName:菜单的资源名的指针为NULL。
1pszClassName:窗口类的名字为WinFoxClass。
然后用
if( !rc )
{
return FALSE;
}

注册这个窗口类,并在注册失败时结束程序。
接着用hWndMain = CreateWindowEx(……)创建窗口并将窗口的句柄赋给hWndMain 。该窗口被创建为:
窗口的扩展格式为 WS_EX_APPWINDOW
窗口类为 "WinFoxClass"
窗口名为 OUR_APP_NAME
窗口格式为 WS_VISIBLE |WS_SYSMENU |WS_POPUP(创建一个初始态可见的标题条上有系统菜单的重叠窗口或弹出式窗口
窗口左上角的X坐标 0
窗口左上角的Y坐标 0
窗口宽度 GetSystemMetrics(SM_CXSCREEN)(屏幕宽度)
窗口高度 GetSystemMetrics(SM_CYSCREEN)(屏幕高度)
父窗口的句柄 NULL,
窗口菜单的句柄 NULL,
窗口类的应用程序的实例句柄是 hInstance
32位附加信息为 NULL
然后,用
if( !hWndMain )
{
return FALSE;
}
检测窗口创建是否成功,并在创建失败时结束程序。
最后调用UpdateWindow( hWndMain )和SetFocus( hWndMain )显示窗口和将键盘输入限定在hWndMain所指的窗口(即游戏窗口)中。

第四节帮助信息的显示
在这里调用了MessageBoxA(HWND hWnd ,LPCSTR lpText,LPCSTR lpCaption,UINT uType)函数。它的调用方式如下:
if( bHelp )
{
MessageBox(hWndMain,
"F12 - Quit\n"
"NUMPAD 2 - crouch\n"
"NUMPAD 3 - apple\n"
"NUMPAD 4 - right\n"
"NUMPAD 5 - stop\n"
"NUMPAD 6 - left\n"
"NUMPAD 7 - jump\n"
"\n"
"Command line parameters\n"
"\n"
"-e Use emulator\n"
"-S No Sound\n"
"-1 No backbuffer\n"
"-2 One backbuffer\n"
"-4 Three backbuffers\n"
"-s Use stretch\n"
"-x Demo or stress mode\n",
OUR_APP_NAME, MB_OK );
}

它的含义是:如果bHelp为TURE,则在hWndMain所指向的窗口创建一个消息框。这个消息框的内容是“”所包含的部分,标题是OUR_APP_NAME(在foxbear.c中有#define OUR_APP_NAME "Win Fox Application"语句),并且这个消息框显示一个OK按钮。

第五节初始化游戏
这个部分只是调用了InitGame( void )函数,它的调用方式为:
if( !InitGame() )
{
return FALSE;
}
即如果调用失败,则结束程序。
InitGame( void )是整个初始化部分最庞大的部分,它完成了剩下的初始化的工作。在本章的后面几节中我们将分几部分讲解这个函数

第六节内存刷新
由于在窗口过程中会接受被定义为改变显示模式的消息这时需要对游戏进行重新初始化,于是就会对InitGame()函数进行调用(比如在消息VK_F8时就调用了该函数)。这时的调用会是在游戏运行时发生的,这时在游戏运行中较重要的指针都已被使用(指向了一定的内存区域),同时内存已被分配。如果这时不释放内存和将指针指向NULL(即不指向任何一个内存区域),以后的初始化将会出现错误 。故在该函数的运行过程中调用了ExitGame()函数已进行刷新指针的工作。
ExitGame()的运行过程为:

首先,检测lpFrameRate、lpInfo、lpPalette三个指针,若非0,则释放其所指的内存,并令该指针为NULL使其恢复为初始状态(通过利用IUuknown接口提供的函数Release()递减指针的内部引用值,直至引用值为0释放所分配的内存)。
然后,调用函数DestroyGame()继续释放内存。DestroyGame()的运行过程为:首先,检测hBuffer是否为非0,如果非0则调用下列函数:
DestroyTiles( hTileList )??释放句柄hTileList所指的局部内存块。
DestroyPlane( hForeground )??释放句柄hForeground和hForeground->hBM所指向的内存块。
DestroyPlane( hMidground )??释放句柄hMidground和hMidground->hBM所指向的内存块。
DestroyPlane( hBackground )??释放句柄hBackground和hBackground->hBM所指向的内存块。
DestroyBuffer( hBuffer )??释放所有位图所占用的内存及lpClipper、 lpBackBuffer、 lpFrontBuffer所指向过的内存区域。
DestroySound()??释放所有音效所占用的内存及lpDS所指向过的内存区域。

最后令hTileList、 hForeground、hMidground、hBackground和hBuffer指向空。


第七节初始化声音
这个部分主要调用函数IinitSound()进行对DirectSound进行初始化和声音的播放的工作。它的工作过程为:
调用DSEnable(hwndOwner)进行DirectSound的初始化:先用bUseDSound = GetProfileInt("FoxBear", "use_dsound", bWantSound)选择播放设备;再通过
if (!bUseDSound)
{
lpDS = NULL;
return TRUE;
}

if (lpDS != NULL)
{
Msg( "DSEnable, already enabled" );
return TRUE;
}
检测选择是否成功和声音是否已被初始化。然后是
dsrval = DirectSoundCreate(NULL, &lpDS, NULL);

if (dsrval != DS_OK)
{
Msg("DirectSoundCreate FAILED");
return FALSE;
}


dsrval = IDirectSound_SetCooperativeLevel(lpDS, hwnd, DSSCL_NORMAL);

if (dsrval != DS_OK)
{
DSDisable();
Msg("SetCooperativeLevel FAILED");
return FALSE;
}
用于创建DirectSound的对象并检测创建是否成功和设置合作层并检测设置是否成功。在这里为默认设备创建了一个对象,将合作层设置为普通(DSSCL_NORMAL)即使用声卡的应用程序可以顺序地进行切换。
调用DSEnable()之后用语句:
for( idx = 0; idx < NUM_SOUND_EFFECTS; idx++ )
{
if (SoundLoadEffect((EFFECT)idx))
{
DSBCAPS caps;

caps.dwSize = sizeof(caps);
IDirectSoundBuffer_GetCaps(lpSoundEffects[idx], &caps);

if (caps.dwFlags & DSBCAPS_LOCHARDWARE)
Msg( "Sound effect %s in hardware", szSoundEffects[idx]);
else
Msg( "Sound effect %s in software", szSoundEffects[idx]);
}
else
{
Msg( "cant load sound effect %s", szSoundEffects[idx]);
}
}
将音效调入(利用函数SoundLoadEffect((EFFECT)idx))和检测设备的特性并将之存放在结构caps中的成员dwsize里(利用函数IDirectSoundBuffer_GetCaps(lpSoundEffects[idx], &caps)。
最后是一部分是播放,这部分主要是设定dsBD.dwFlags为DSBCAPS_PRIMARYBUFFER确定可对主缓冲区进行操作,使用语句if (SUCCEEDED(IDirectSound_CreateSoundBuffer(lpDS, &dsBD, &lpPrimary, NULL)))来创建副缓冲区并检测成功与否,使用语句 if (!SUCCEEDED(IDirectSoundBuffer_Play(lpPrimary, 0, 0, DSBPLAY_LOOPING)))播放并检测成功与否。

第八节DirectDraw的设置
这个部分完成了对DirectDraw的设置工作和游戏开始时初始化画面的显示。它的运行过程如下:
if( !PreInitializeGame() )
{
return FALSE;
}
在PreInitializeGame()函数中只有语句return InitBuffer( &hBuffer)。该语句调用了InitBuffer( &hBuffer)函数又只调用了gfxBegin()函数,并在调用失败后结束程序。函数gfxBegin()完成了对显示模式的设置工作(通过对DDEnable( void )函数的调用)、创建表面的工作(通过调用函数DDCreateFlippingSurface( void )、初始化画面的显示(通过调用函数Splash())。
函数DDEnable( void )的运行过程如下:
1.获取系统信息(运用GetProfileInt()函数)和决定是否使用软件模拟(应用DirectDrawCreate() 为该驱动方式建立一个对象再用DirectDraw_QueryInterface()查询该对象是否被支持)。
2.检测显示模式。首先判断是否使用全屏模式,若是则使用IDirectDraw_SetCooperativeLevel(( lpdd, hWndMain,DDSCL_ALLOWMODEX | DDSCL_EXCLUSIVE |DDSCL_FULLSCREEN )创建一个对显示设备能够进行最大化控制的合作层,然后用IDirectDraw_EnumDisplayModes(lpdd, DDEDM_STANDARDVGAMODES, NULL, 0, EnumDisplayModesCallback)枚举VGA模式下的显示模式。如果枚举失败,则调用IDirectDraw_EnumDisplayModes(lpdd, 0, NULL, 0, EnumDisplayModesCallback)(枚举所有的显示模式)。若不使用全屏模式,则调用 ddrval = IDirectDraw_SetCooperativeLevel( lpdd, hWndMain,DDSCL_NORMAL )(创建一个DDSCL_NORMAL 模式下的合作层即使用一般Windows应用程序的方式)。随后将设定的显示模式放入结构数组ModeList[]中。若合作层创建失败则返回错误信息并结束游戏。
接下来是选择显示模式部分。首先判断是否使用全屏模式,若是则判断使用哪种显示大小和颜色数。若没选用全屏模式则获取系统当前的设备及所采用的颜色数,设定窗口模式下的窗口的属性及判断是否使用拉伸算法并根据判断结果设定用户区的大小(利用SetRect(&rc, 0, 0, GameMode.cx*2, GameMode.cy*2)及SetRect(&rc, 0, 0, GameMode.cx, GameMode.cy)),然后根据用户区的大小和窗口风格的窗口属性设定窗口的大小,并由SetWindowPos()确定窗口在多窗口的情况下的位置。
利用IDirectDraw_GetCaps( lpdd, &ddcaps, NULL )函数获取设备所支持的特性,并确定那些特征由软件模拟,那些特征由硬件支持。由nBufferCount的值确定缓冲区的数量。最后由设备所支持的特性确定最大显示模式。
函数DDCreateFlippingSurface( void )的运行过程:
首先取得硬件支持的特性(应用IDirectDraw_GetCaps( lpDD, &ddcaps, NULL ) )。再将缓冲区中全部填上0。在全屏模式下和缓冲区数大于1时,创建一个以DD_CAPS为标志的表面功能区及一个以DDSD_BACKBUFFERCOUNT为标志的后备缓冲记数区,并给表面功能区分配DDSCAPS_PRIMARYSURFACE、DDSCAPS_FLIP和DDSCAPS_COMPLEX三个标志(关于这三个标志,请详细阅读DirectDraw深入篇)。接下来用 ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
ddrval = IDirectDrawSurface_GetAttachedSurface(
lpFrontBuffer,
&ddscaps,
&lpBackBuffer );
取得后备缓冲区的指针。如果使用了拉伸算法或ddcaps.dwCaps的标志为DDCAPS_BANKSWITCHED时则创建再一个用于拉伸算法的缓冲区(在您详细阅读函数DDCreateSurface(DWORD width,DWORD height,BOOL sysmem,BOOL trans )后会发现该缓冲区的创建方法与上面谈到的缓冲区的创建是差不多的,只不过少了后备缓冲记数区而多了两个关于高度和宽度的缓冲区,并使用了系统内存,请注意这个技巧)。下面是在全屏模式和有一个缓冲区的情况下,如何进行创建表面的工作。在这一步中可以看到程序只创建了一个表面功能区而无后备缓冲记数区,同时取得后备缓冲区的指针的部分被
IDirectDrawSurface_AddRef(lpFrontBuffer);
lpBackBuffer = lpFrontBuffer;
替代。然后是在窗口模式下的缓冲区的设置,首先创建一个表面功能区(在VRAM中)
然后用lpBackBuffer = DDCreateSurface( GameSize.cx, GameSize.cy, FALSE, FALSE )创建一个后备缓冲区(在系统内存中)。接下来是对剪切板的设置,先创建一个剪切板(ddrval = IDirectDraw_CreateClipper(lpDD, 0, &lpClipper, NULL)),再取得剪切板的指针( ddrval = IDirectDrawClipper_SetHWnd(lpClipper, 0, hWndMain)),最后将剪切板连接到表面上( ddrval = IDirectDrawSurface_SetClipper(lpFrontBuffer, lpClipper))。下面是对颜色的操作,首先使用IDirectDrawSurface_GetPixelFormat(lpFrontBuffer, &ddpf)取得表面的像素格式,然后设置ColorKey,若是8位色则ColorKey为256反之则取16位色。

第九节用户区尺寸及调色板设置
1.用户区尺寸设置
在这部分中首先是对全屏模式或使用拉伸算法的情况下的用户区的大小设置,然后是在窗口模式下的设置。在这些设置工作中可能需要解释的是 GameRect.left为什么等于GameMode.cx - GameSize.cx,这是由于GameRect.left(或GameRect.right)是指用户区的左上角(右下角)的横坐标长度,而GameMode.cx是指整个用户区的宽度(全屏)而且GameSize.cx= GameMode.cx / 2 。所以GameRect.left=GameMode.cx - GameSize.cx。其他的比如GameRect.top、GameRect.right和GameRect.bottom的计算式的含义都和GameRect.left的计算方法差不多。
2调色板设置
这一部分程序的功能主要是由函数ReadPalFile()完成的,这个函数的功能是引入一个调色板文件,如果找不到指定的文件则使用默认的调色板,而这个默认的调色板的建立是使用了下面的语句:
for( i=0; i<256; i++ )
{
pal.ape[i].peRed = (BYTE)(((i >> 5) & 0x07) * 255 / 7);
pal.ape[i].peGreen = (BYTE)(((i >> 2) & 0x07) * 255 / 7);
pal.ape[i].peBlue = (BYTE)(((i >> 0) & 0x03) * 255 / 3);
pal.ape[i].peFlags = (BYTE)0;
}/*在结构数组pal.ape[i]中存放256组由四个数值组成的颜色值*/
和函数 ddrval = IDirectDraw_CreatePalette(lpDD,DDPCAPS_8BIT,pal.ape,&ppal,NULL )。从这些语句中我们可以看到建立了一个256色的调色板(8位色)。
在调用调色板之后用IDirectDrawSurface_SetPalette()将调色板连接到表面上。

第十节调入图象
这个部分是通过调用InitializeGame()函数实现的。InitializeGame()函数的运行过程为:首先调用 Splash()函数,该函数的作用是将后备缓冲区全部写为蓝色并测试全部缓冲区(用DDClear()函数)和向屏幕输出FoxBear is loading...... Device。然后用LoadBitmaps( void )函数从foxbear.art文件中将所有的位图读出并存入结构数组hBitmapList[]中。接着是:
InitTiles( &hTileList, hBitmapList, C_TILETOTAL );

InitPlane( &hForeground, &hForePosList, "FORELIST", C_FORE_W, C_FORE_H, C_FORE_DENOM );
TilePlane( hForeground, hTileList, hForePosList );

InitPlane( &hMidground, &hMidPosList, "MIDLIST", C_MID_W, C_MID_H, C_MID_DENOM );
TilePlane( hMidground, hTileList, hMidPosList );

InitPlane( &hBackground, &hBackPosList, "BACKLIST", C_BACK_W, C_BACK_H, C_BACK_DENOM );
TilePlane( hBackground, hTileList, hBackPosList );

InitSurface( &hSurfaceList, "SURFLIST", C_FORE_W, C_FORE_H );
SurfacePlane( hForeground, hSurfaceList );

InitFox( &hFox, hBitmapList );
InitBear( &hBear, hBitmapList );
InitApple( &hApple, hBitmapList );
这些语句将 hBitmapList[]中的位图分类存入相应的列表中并建立各自的内存区。如:InitPlane( &hForeground, &hForePosList, "FORELIST", C_FORE_W, C_FORE _H, C_FORE_DENOM )在主缓冲区建立了一个存放前景的区域和一个存放前景的位图的区域(第一帧)。
TilePlane( hForeground, hTileList, hForePosList )在主缓冲区建立一个存放前景中其他位图的区域。
最后用DDClear()将所有后备缓冲区清空。

第十一节输出设备信息
在游戏的过程中会显示出当前的FPS值及显示模式和“ALT+ENTER=WINDOWS”的信息。这部分的功能就是输出这些信息。
这一功能的是通过函数makeFontStuff()的调用而实现的。makeFontStuff()的实现过程为:删除已有的字体句柄,创建一个新的字体,调用initNumSurface( )为FPS及FPS的值和显示模式及字符串“ALT+ENTER=WINDOWS”设定输出字体的大小、颜色、在用户区的位置等等属性。然后各为FPS 和显示模式等的信息创建一个内存区域以存放它们。然后用
if( bTransDest )
BackColor = RGB(255,255,255);
else
BackColor = RGB(128,64,255);

ddck.dwColorSpaceLowValue = DDColorMatch(lpInfo, BackColor);
ddck.dwColorSpaceHighValue = ddck.dwColorSpaceLowValue;

IDirectDrawSurface_SetColorKey( lpInfo, DDCKEY_SRCBLT, &ddck);
IDirectDrawSurface_SetColorKey( lpFrameRate, DDCKEY_SRCBLT, &ddck);
来设定FPS信息和显示模式信息显示的色彩键码。
最后,再次调用initNumSurface( )显示这些信息。
 


 


游戏开发基础(9)



第一节窗口的移动和改变大小时
case WM_SIZE:
case WM_MOVE:
if (IsIconic(hWnd))
{
Msg("FoxBear is minimized, pausing");
PauseGame();
}

if (bFullscreen)
{
SetRect(&rcWindow, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
}
else
{
GetClientRect(hWnd, &rcWindow);
ClientToScreen(hWnd, (LPPOINT)&rcWindow);
ClientToScreen(hWnd, (LPPOINT)&rcWindow+1);
}
Msg("WINDOW RECT: [%d,%d,%d,%d]", rcWindow.left, rcWindow.top, rcWindow.right, rcWindow.bottom);
break;

case WM_SIZE:是在窗口的大小被改变时发送的。

case WM_MOVE:是在窗口被移动时发送的。

当收到这两个消息后,首先检测窗口是否最小化了,如果是则暂停游戏。否则再检测游戏是否转为全屏模式,若是则改变用户区的大小为全屏。如果只是改变了窗口的大小但并没有使显示模式变成全屏或窗口最小化,则先取得窗口的用户区坐标(左上角的x、y及右下角的x、y的值)将之存入结构rcWindows,再将rcWindows中的坐标转换为屏幕坐标。请注意rcWindows是RECT型的结构,但ClientToScreen()要求的是POINT型的结构,在结构RECT中存放的是一个矩形的左上角的x、y及右下角的x、y的值而结构POINT中存放的就只是x和y的值。故此在
ClientToScreen(hWnd, (LPPOINT)&rcWindow);
ClientToScreen(hWnd, (LPPOINT)&rcWindow+1);
中先对rcWindow进行强制类型转换,而且两次调用该函数,同时在第二次掉用时使rcWindow的地址自加1。


第二节窗口被激活时
case WM_ACTIVATEAPP:
bIsActive = (BOOL)wParam && GetForegroundWindow() == hWnd;

if (bIsActive)
Msg("FoxBear is active");
else
Msg("FoxBear is not active");

if (bPaused && bIsActive)
{
if (RestoreGame())
{
UnPauseGame();
}
else
{
if (GetForegroundWindow() == hWnd)
{
if (InitGame())
{
UnPauseGame();
}
}
}
}
break;
case WM_ACTIVATEAPP:当不同于当前窗口的应用程序的窗口被激活时发送本消息。
当接收到这个消息时,首先令bIsActive 等于 (BOOL)wParam && GetForegroundWindow() == hWnd,然后检测bIsActive是否为TURE。这里的意思就是检测游戏的窗口是否被激活且处于当前系统中最高优先级。接下来先检测游戏窗口是否是从暂停状态到激活的状态,若是则运行RestoreGame()从新开始,若RestoreGame()成功则运行UnPauseGame(),如果不是从暂停状态到激活的状态,则检测本程序的窗口是否拥有最高的优先级,若有则从新初始化游戏(运行InitGame()),初始化成功后则运行UnPauseGame()。


第三节 实现逻辑调色板时
case WM_QUERYNEWPALETTE:

if (!bFullscreen && lpPalette && lpFrontBuffer)
{
HRESULT ddrval;

ddrval = IDirectDrawSurface_SetPalette(lpFrontBuffer,lpPalette);
if( ddrval == DDERR_SURFACELOST )
{
IDirectDrawSurface_Restore( lpFrontBuffer );

ddrval= IDirectDrawSurface_SetPalette(lpFrontBuffer,lpPalette);
if( ddrval == DDERR_SURFACELOST )
{
Msg(" Failed to restore palette after second try");
}
}

if( ddrval == DD_OK )
{
SetWindowText( hWnd, OUR_APP_NAME );
}
}
break;
WM_QUERYNEWPALETTE:在窗口收到输入焦点前发出,当窗口收到输入焦点后将返回显示它是否能实现逻辑调色板。
在这个消息获得后,程序先检测是否运行于窗口模式且调色板和前缓冲区已设定,然后设定一次调色板,若失败则恢复前缓冲区然后再试一次,如果仍然失败则输出错误信息。两次设定若成功一次则将标题改为“Win Fox Application”。


第四节 改变系统调色板时
case WM_PALETTECHANGED:
if ((HWND)wParam != hWnd)
{
if( !bFullscreen )
{
if( !bStress )
{
Msg("***** PALETTE CHANGED, PAUSING GAME");
PauseGame();
}
else
{
Msg("Lost palette but continuing");
SetWindowText( hWnd, OUR_APP_NAME " - palette changed COLORS PROBABLY WRONG" )
}
}
}
break;
WM_PALETTECHANGED:本消息在拥有输入焦点的当前窗口实现其逻辑调色板时送往所有的窗口。这时,系统调色板被改变,本消息允许不带输入焦点的窗口使用调色板去实现自己的逻辑调色板和更新其用户区域。
在得到这个消息后,程序首先是否是当前窗口改变了系统调色板,如果是则直接跳出窗口过程,若不是则再检测是否是全屏模式,若是则直接跳出窗口过程,若非则先检测bStress是否为FLASE,若为FLASE则暂停游戏,若为TURE则将标题条改为“ - palette changed COLORS PROBABLY WRONG”。


第五节当操作键按下时

WM_KEYDOWN消息是在一个非系统键按下时产生的,非系统键是指没有按下ALT键时按下的键,或是当某窗口已有输入焦点时按下的键。
在该消息的wParam参数中包含了识别所按下的键的虚键码,由不同的虚键码就可以完成键盘对游戏的操作。我们知道键盘对游戏的操作中所按的键可以分为:操作键和功能键两类。下面让我们先看看例程中是如何定义操作键的吧。
在上一章我们就介绍过在本例程中操作键是小建盘上的“2345678”,但一直没有谈到如何实现的,现在就让我们来看一看。
对操作键的功能的定义是在
case VK_NUMPAD5:
lastInput=KEY_STOP;
break;
case VK_DOWN:
case VK_NUMPAD2:
lastInput=KEY_DOWN;
break;
case VK_LEFT:
case VK_NUMPAD4:
lastInput=KEY_LEFT;
break;
case VK_RIGHT:
case VK_NUMPAD6:
lastInput=KEY_RIGHT;
break;
case VK_UP:
case VK_NUMPAD8:
lastInput=KEY_UP;
break;
case VK_HOME:
case VK_NUMPAD7:
lastInput=KEY_JUMP;
break;
case VK_NUMPAD3:
lastInput=KEY_THROW;
break;
您可以看到在得到每个虚键码之后都对lastInput进行赋值,这时就完成了对操作键的定义了,至于操作键是如何其作用的,在本章的第八节中您可以看到。


第六节 当功能键按下时
在本游戏中的功能键是F3、F4、F5、F6、F7、F8、F9。这些键的作用时什么,是如何实现的呢?下面就让我们一个一个的看看吧!
1.F3的作用是暂停游戏和解除暂停。在程序中这个作用是这样实现的:
case VK_F3:
bPaused = !bPaused;
break;
2.F4的作用是实现ALT+ENTER的作用。在程序中是使用
case VK_F4:
PostMessage(hWnd, WM_SYSKEYUP, VK_RETURN, 0);
break;
4.F6的作用是逐个使用在显示模式列表中的显示模式。
case VK_F6:
{
static i;
if(bFullscreen)
{
for (i=0; i {
if (ModeList[i].bpp == (int)GameBPP && ModeList[i].w == GameSize.cx && ModeList[i].h == GameSize.cy)
{
break;
}
}
}else
{
for (i=0; i {
if (ModeList[i].w == GameSize.cx &&ModeList[i].h == GameSize.cy)
{
break;
}
}
}
if (++i >= NumModes)
{
i = 0;
}
Msg("ModeList %d %d",i,NumModes);
GameMode.cx = ModeList[i].w;
GameMode.cy = ModeList[i].h;
GameBPP = ModeList[i].bpp;
bStretch = FALSE;
InitGame();
}
break;
在收到VK_F6的消息后,程序设立一个初值为0的变量,并以该变量为存放显示模式列表的数组的下标,然后令其自加直至找到一个与当前模式相同的。然后令其自加1,取以该值为下标的元素为显示模式,最后重新初始化游戏。
5.F7的作用是改变颜色数。
case VK_F7:
GameBPP = GameBPP == 8 ? 16 : 8;
InitGame();
break;
6.F8的作用是决定是否使用拉伸算法。
case VK_F8:
if (bFullscreen)
{
bStretch = !bStretch;
InitGame();
}
else
{
RECT rc;

GetClientRect(hWnd, &rc);

bStretch = (rc.right != GameSize.cx) || (rc.bottom != GameSize.cy);

if (bStretch = !bStretch)
SetRect(&rc, 0, 0, GameMode.cx*2, GameMode.cy*2);
else
SetRect(&rc, 0, 0, GameMode.cx, GameMode.cy);

AdjustWindowRectEx(&rc,GetWindowStyle(hWnd),GetMenu(hWnd) != NULL,GetWindowExStyle(hWnd));

SetWindowPos(hWnd, NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top,SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}
break;


这部分的运行过程为:若为全屏模式则对 bStretch取非,然后初始化游戏。若为窗口模式,则取得当时窗口的特征,以确定bStretch的值,然后从新显示窗口。

7.F9的作用是取消使用软件模拟并逐个使用已有的驱动程序
case VK_F9:
DevIndex ++;
bUseEmulation = FALSE;
if (DevIndex >= MaxDevIndex)
DevIndex = 0;

ExitGame();
DDDisable(TRUE); // destroy DirectDraw object
InitGame();
break;

第七节 其他消息
case WM_DISPLAYCHANGE:
break;

case WM_CREATE:
break;
这两个消息收到后,将不做任何反应。

case WM_SETCURSOR:
if (bFullscreen && bIsActive)
{
SetCursor(NULL);
return TRUE;
}
break;
该信息在光标随鼠标的移动而输入未被捕获时发出。
由于本游戏不需鼠标,故在随后的检测是否是全屏模式或是被激活的语句,获得肯定的答案后,将光标从屏幕上删除。
case WM_PAINT:
hdc = BeginPaint( hWnd, &ps );
if (bPaused)
{
char *sz = "Game is paused, this is not a bug.";
TextOut(ps.hdc, 0, 0, sz, lstrlen(sz));
}
EndPaint( hWnd, &ps );
return 1;
该消息在请求重新绘制应用程序窗口是发出 。
程序在接收到这个消息后就调用BeginPaint()为hWnd所指的窗口作好绘画准备。然后判断游戏是否暂停,若是则向屏幕输出Game is paused, this is not a bug.最后调用EndPaint()
case WM_DESTROY:
hWndMain = NULL;
lastInput=0;
DestroyGame(); // end of game
DDDisable(TRUE); // destroy DirectDraw object
PostQuitMessage( 0 );
break;
该消息在要撤消某窗口时向该窗口发送。
在收到这个消息后程序令指向窗口的句柄为NULL,对游戏的刷新单元的下一次输入设为0,然后清除游戏及游戏工所占的内存。

第八节 刷新游戏单元
在讨论完窗口过程后,我们应开始介绍这个游戏的消息循环部分了。在这个游戏的消息循环部分中大部分在第二章 windows编程基础中已经谈到过了,所以在这里我们将只介绍刷新游戏单元和重画游戏单元部分。在消息循环中程序是调用函数ProcessFox(SHORT sInput)来进行这两部分的工作的。在ProcessFox(SHORT sInput)函数中,先对游戏是否正在运行或恢复游戏运行是否成功作了一个检测,然后就先调用ProcessInput(sInput)函数进行刷新游戏单元部分,再调用NewGameFrame()进行重画游戏单元的工作。下面就让我们先看看刷新游戏单元部分吧。
这一部分的运行过程为:

1.狐狸的行为刷新
首先取得当前狐狸的的速度、行为和方向。然后检测是否获得狐狸的位置或输入是否是4209,若检测的表达式为TURE则输入为0,即没有输入,若为FLASE则开始对各种输入进行响应。在响应的过程中程序先对狐狸的当前状态进行判断,然后根据狐狸的当前状态决定下一帧狐狸的基本状态。比如“↓”键或小键盘的“2”被按下时的响应过程为
case KEY_DOWN:
if( foxAction == STOP )
{
break;
}
else if( foxAction == STILL )
{
SetSpriteAction( hFox, CROUCH, SAME );
}
else if( foxAction == WALK )
{
SetSpriteAction( hFox, CROUCHWALK, SAME );
}
break;
在“↓”键被按下时,如果狐狸的动作是在急停的,则跳出;如果狐狸是静止的,则狐狸蹲下不动;若狐狸是在移动,则狐狸的行动改为爬行。
在对输入的初步处理后,程序便结合狐狸行为的其他属性,确定下一帧狐狸的状态。在这里同样是用switch??case语句和大量的if??else语句完成的。

2.熊和苹果的行为刷新
这两部分的运行过程实际上是一致的,都是首先进行碰撞检测,然后再根据检测结果决定角色新的行为属性。

第九节 重画游戏单元
这部分的工作是由NewGameFrame()完成的。这个函数首先调用SetSpriteX() 、SetSpriteY()两函数设定角色的新位置,再调用SetPlaneVelX()和SetPlaneX()函数设定三层背景的移动速度和下一帧的位置。然后通过检测bTransDest的值决定是先将角色的位图粘贴到Backbuffer还是先将背景的位图粘贴到BackiBuffer,
最后调用函数gfxSwapBuffers()进行页面翻转将后备缓冲区中的图显示到屏幕上。该函数是这样实现的:
BOOL gfxSwapBuffers( void )
{
if( bFullscreen )
{
if( bStretch )
{
gfxStretchBackbuffer();
}

if (nBufferCount > 1)
return gfxFlip();
else
return TRUE;
}
else
{
return gfxUpdateWindow();
}
它的运行过程是这样的:在全屏模式下且使用拉伸算法时用gfxStretchBackbuffer()对后备缓冲区和拉伸缓冲区之间进行blt操作,然后在有一个以上(不含一)的后备缓冲区时用gfxFlip()调用IDirectDrawSurface_Flip( lpFrontBuffer, NULL, DDFLIP_WAIT )进行一次页面翻转操作将后备缓冲区中的图显示到屏幕上。如果是窗口模式下,则通过gfxUpdateWindow()调用IDirectDrawSurface_Blt( lpFrontBuffer,&rcWindow,lpBackBuffer,NULL,DDBLT_WAIT,NULL)直接将后备缓冲区中的内容blt到主缓冲区中。这样就完成了重画游戏单元的任务了。
 

 





 


游戏开发基础(10)



第一节 3D类游戏
设计3D光线投射游戏包括从高解析度图形到快速动画的许多技术。尽管3D场景在这些
不同的游戏中可能变化很大,玩游戏的方法和设计游戏的基本技术却是类似的。场景中较明显的组成部分包括生成的墙壁、地板、天棚、可移动的物体和静止的物体。当然,这些游戏也充分利用了大量的其它技术如声效和音乐、3D动画、故事、难度级别、秘密门等等。
1.速度、力量和简单性原则
在设计像Doom这样的游戏过程中,关键的因素看来都很简单:使用一个强大快速的光线投射引擎以提供优秀的3D图形和一个优秀但简单的操作环境。增加动态的多游戏者能力和难度级别设计以使您的游戏不断前进。当然,说的容易做起来难。正像Doom“看起来” 像是设计得比较简单而实际上并不是。
2.图像力量
在Doom初次登台之后,大量的仿制品出现了。尽管有些游戏也很有自己的特点,但玩的时候似乎没有人获得Doom的感觉。这是一个很好的例子,证明在一个由图形力量占主要地位的游戏引擎中,设计与难度级别编辑是具有决定性的。
据说,ID尽管使用了大量的优秀编辑人员和工具设计了很多新的Doom难度级别,可是只有少量(可能少到百分之一)的难度级别投入实际的游戏使用中。
当然,在游戏开发中只重视图像质量还是远远不够的,只有提供整体水平很高的游戏才能让游戏老不断玩您的游戏。
3.尽在射线中
光线投射技术是一种确定游戏者移动时一个物体应该出现在场景中什么位置的技术。它的原理是:当人在场景中移动时,射线改变它们的位置并和场景中不同的点相交。相交的点就是墙壁和其它物体被显示的地方。通常,射线是沿着一个弧线投射出去的,这样在正前方的墙壁和物体与在边上的可以同样被发现。
4.在迷官中迷失
许多游戏开发者认为3D光线投射游戏和迷宫游戏差不多。因为这类游戏经常发生在类
似迷宫的环境中。创建像Doom这样的3D游戏包括建立一个迷宫和随后将各种不同的对像放在迷宫中。
在一个3D世界中迷宫的组成部分是房间、墙壁、地板、天花板等等。在迷宫中房间是由墙
壁分隔形成的。墙壁实质上是静态的物体,只能置于迷宫网格的边界上。
5.多难度级别
大多数3D游戏提供多难度级别,使游戏者玩上几天甚至几个星期。每级游戏都可以建立在不同的迷宫中。当一个游戏者以自己的方式成功地玩到了某一级的终点时,游戏继续进行到更高的一级,在那里游戏者要接受新的挑战。多级别非常重要,它可以让新的布景和迷宫出现并且使游戏看起来不让人很厌烦。在这个方面,许多3D游戏是与2D Arcade游戏相同的。它们让您在迷宫中搜寻到足够的物品,达到一定的分数才可以进入下一关。您可以设计允许游戏者使用秘技,如穿过一个秘密门自动进入下一级或得到一个隐藏的物体以获得特殊能力。如果
您真的很音欢暴力,您也可设计一个让游戏者不得不杀完所有怪物才能进入下一级的游戏。
未来展望
未来的3D光线投射游戏不但会加进多人游戏功能,更快速的3D引擎也会开发出来。准备好吧,这一类游戏的未来蒸蒸日上。

第二节探险游戏
探险游戏设计包括下面几个概念,这些概念不是具体某个游戏本身具有的概念,而是关于这类游戏的基本要素。
1.情节和引擎
很多游戏都是使用了公司内部建造的游戏引擎和游戏创建系统开发出来的产品。使用这些游戏引擎处理大量的图像和其它琐碎的工作可以让游戏公司真正集中力量用于推进故事情节和角色特征的开发。
首先说着建立一个核心引擎,并确定您的探险游戏可能需要的一般内容,包括地图、动作、对话接口等等。然后,在这些基础上再编写您自己的游戏。但是记住,用相同的引擎开发出来的好游戏必须有一个新鲜复杂的故事情节,而且要有足够的特征使它从同系列中的其它游戏中脱颖而出。这意味着您的引擎要能适应开发宽范围的创意和有可能进行的升级。
简单他说,需要建立一个系统以支持您创建大量的故事情节。有了这个武器,探险游戏开发才会向成功的方向前进。
2.成熟的内容
揉险游戏设计的关键是内容设计。玩这些游戏的大都是受过良好教育的成年人,他们喜欢井有能力理解复杂的故事情节。
传统上,大多数探险游戏与其它大部分游戏一样,倾向于避免某些不好的主题,但因为玩游戏变得更加普及了,所以创建一个成人级的探险游戏的可能性更大了。
未来展望
可以说探险游戏是最接近交互式故事的东西。所以,这一类游戏未来的形式就是变得更富于交互性。
探险游戏毫无疑问将继续成为主要的游戏类型之一,而且随着优秀开发人员编写出更有趣的创意,我们将在这个领域看到更令人激动的发展。

第三节寓教于乐游戏设计问题
寓教于乐游戏设计包括一些独特的技术。当您开发这一类游戏时,您必须努力对付许多问题,包括游戏类型、年龄特征、性别特征。让我们仔细看看这些问题。
1.寓教于乐游戏分类
寓教于乐产品有两大类:传统的和非传统的。我们先看看按教育内容的多少来分类传统类型:
1)操作与训练游戏
在这一类游戏中,孩子们要做完一定数量的训练题目,在全部做完之后通常有一段动画作为奖励。
这些游戏对于孩子们是相当合适的。还没有在学校被作业压死的小孩子可以把这些练习当做一个游戏来做。
2)教育和游戏各占一半的游戏
有些游戏在游戏中混合了教育内容。例如在一个叫做“圣地亚哥在哪里?”的游戏中,游戏内容分为教育和游戏两部分,在教育部分中,游戏者作为负责审问嫌疑犯的角色必须收集到足够多的线索以进一步探索凶犯的下落。游戏部分则表现出它的挑战性:游戏者只有有限的时间用来搜寻和查找下一个地点的线索。如果游戏者在错误的地点浪费了时间,他就无法完成游戏。在游戏中提供了地理资料,所以游戏者玩游戏时不知不觉地学到了东西。
3)内容游戏
这些游戏在训练和探险游戏中处于中间位置,它们包括三个关键的成分:探索、冒险和搜集。(后面详细讨论这些成分。)教育内容仍然很清晰但在游戏进行时已处于第二位了。孩子(特别是大点的孩子)把这些练习看作游戏而不是训练。
4)发现式游戏
现在美国公立教育的热点问题是探索和问题解决。这个思想是在给予孩子们结构化的练习之后,他们靠自己的力量发现信息并最终获得学术技能和知识。在我看来,这近似于给孩子一台电脑然后让他们写一篇小文章,然而不先教他们如何打字。在这类游戏中孩子用鼠标点各种热区,然后等待结果。无论是播放一段动画,阅读一个故事,还是开始一段游戏,这些都很适合学龄前儿童。
非传统的寓教于乐游戏是那些因为本身固有教育价值的原因,即使它们并不是专门设计
用于教育目的,但都有教育效果。
2.年龄的考虑
大概在设计寓教于乐游戏产品中最关键的因素是确定您的年龄分组。孩子们变化太快,所以您必须对您要面对的年龄组有一个清晰的认识。
注意分组有些是重叠在一起的,同一组中不是所有的孩子都达到了相同的程度,也有孩子会适合相邻年龄组的某些游戏。详细的年龄组讨论可以参阅第一章。
3.许可特征
使用迪斯尼角色(如骗蝠侠)的游戏含有“注册许可特性”。出版商出一笔大约游戏出售总收入的百分之十用于注册,以保证可以使用游戏中角色的名字等权利。反过来,开发者必须承诺坚持他们关于内容、外观和角色特征方面的标准。事实上,大部分商标持有者都要求开发者将剧本、艺术处理等有关内容上交审核。为什么您要使用别人的角色而让商标持有者赚大钱呢?因为注册许可的角色会给您的游戏现成的知名度。任何其它的地方(电视、电影、T恤衫等等)建立的知名度会给您的计算机游戏带来潜在的销售额。您的出版商会很高兴地发现因为这个知名度游戏非常容易进行零售。
4.谁买走游戏?
孩子们的寓教于乐游戏难于走位的一个原因是买游戏的人(孩子的父母)不是玩游戏的人。因此,您必须在两类很不同的顾客之间进行游戏对像确定的工作。这对于小于七岁的年龄组尤其重要。
另外,您还必须投合教师的心意。如果您的游戏被学校采纳,虽然这不会使您发财,但学校的知名度和权威性会对顾客的购买产生影响。
未来展望
未来的寓教于乐游戏会进一步发展引人入胜的学习体验,将游戏与教育融合起来。在向这个目标迈进的过程中,更多的多媒体内容、联机成分和更多精练的游戏成分将会加入到教育软件中。寓教于乐游戏最大的发展可能是将所有主要的软件种类结合到完整的学习环境中。不久的将来,学生会发现某一学科的多媒体参考书,或是可以与其它学生在网络上互相交流娱乐,这些教育游戏将有助于他们深刻理解所学的知识。


第五节打斗游戏设计问题
打斗游戏设计包括几个概念,如:角色创建、无故技法、暴力等。
1.角色创建
在打斗游戏中最动人的是使用了真实的、令人信服的角色特征。例如在真人快打中,开发者在创建每一个具有不同体力、智力和个性的敌手时表现出令人难以置信聪明才智。尽管在游戏中角色的特征被壮丽的图像和动画掩盖了,作为设计者仍然要仔细地开发设计出好的角色。
2.无故技法
打斗游戏的一个关键设计是“无敌技法”,有时也被称为“致命技法”或者“最终技法”。每个角色都具有打、踢、摔和跳等特别动作,他们也具有各自不同的几个特殊攻击手段。例如,当游戏者依次快速按下跳起、左移和右移键时,角色就会跳到空中然后像一阵旋风旋转着向对手进攻,只要与对手的距离足够远,每个游戏都有很多这样的按键组合!
致命技法与打、踢、跳等待别动作之间的不同之处在于:致命技法是不公开。在游戏手册上是没有的。致命技法是不会轻易被发现的,一般需要玩几个星期甚至是几个月才能发现。致命技法也是游戏吸引人的一个部分。
当设计您自己的游戏时,记住这些技巧。您要让您的游戏者对您的游戏感兴趣,并且能吸引他们。如果游戏者掌握角色的特别动作和致命技法或秘技时,他们就会玩上瘾。但是,如果游戏太简单,游戏者很快就学会使用致命技法赢得胜利时,游戏对他们就失去吸引力。
3.暴力
不用说,打斗游戏中的主要成分是暴力。暴力是必需的吗?对于很多国家来说,这是个问题。不过如果您不在乎将您的游戏列为“少儿不宜”就没什么可怕的了。
4,持续的创造力
打斗游戏的生存依赖于开发者持续的创造力。大部分游戏在故事发展中都很少有自己的创造,全都是一对一的打斗场景。也许您会创造出更好的打斗游戏,并使它提高到一个新的阶段。
未来展望
开始我就说过,运动捕捉和3D技术会给打斗游戏带来新的活力;没错,现在正是这样。未来的打斗游戏将在以下几个方面发展:
1.联机对手
计算机时代的虚拟打斗已经开始了。多人联机对打游戏将是打斗游戏的新发展。联机版本给游戏老一试身手的机会。打斗游戏会是众多联机游戏中最流行的一种。
2。身临其境的虚拟现实
打斗游戏的一个主要趋势是倾向于允许游戏者进入神奇的虚拟现实模拟世界。确实,未来的所有游戏都将在虚拟现实环境中获得自己的新生,而打斗游戏尤其令人振奋。
3.集成更大的游戏系统
在1995年举办的计算机游戏开发者会议中,我参加了一个关于RPG前途的座谈会。会议的一个议题是围绕在传统打斗游戏的战斗系统中制作一个RPG游戏。虽然大多数RPG迷不太喜欢动作游戏,但这个概念表明打斗游戏将被集成为复杂的线索故事并成为更大的游戏的寻一个组成部分的可能性。


第六节主管类游戏
主管类游戏设计包括几个概念如建模和模拟。这些概念并非是某个具体主管类游戏特有的,而是这类游戏的基本要素。主管类游戏是模拟游戏的最基本形式。“模拟游戏?”您会问“是不是类似飞行模拟的游戏?”是的,但那些游戏模拟的只是军事或非军事的硬件设备,而不是像主管类游戏模拟一个就像人类社会或蚂蚁社会的实体系统。
主管类游戏很难建立一个与现实完全相同的模型。设计昔花费大量的时间和情力用于研究社会,例如,人们进入或离开城市的原因,等等。这些努力没有白费,您得到的是一个好玩而且真实的游戏。但不要为设计者而悲伤,因为在一个主管游戏中,设计者才是最终的上帝!
未来展望
未来是非常明朗的。更复杂的人工智能技术和建模技术能让设计者创建更具智能的系统,这将导致更真实的游戏,毫无疑问,这类游戏还将进入网络。



第七节联机游戏
联机游戏中不需要人工智能了。这对吗?不对。事实上在许多多人游戏中人工游戏者变得更加重要了。原因很简单,因为您的游戏具有多人游戏功能,但并不意味着多个游戏者一直登录在线。例如在一个多人赛车游戏中,如果只有一个游戏者参加游戏,您就需要控制其它的赛车以提供一个真实的游戏环境。
另外,如果一个游戏者中途退出,他的赛车也不能立刻就消失。我的结论是在一个大规模的在线游戏中,您仍然需要人工角色在您创造的世界中参与游戏。
未来展望
可能比游戏发展更快的是支持游戏的联机服务设备和Internet。
多人游戏还只是刚刚开始。新的创意不断出现,但新的问题也很多。如果您想开发此类游戏,您就要有冒大风险的准备。

第八节RFG设计问题
RPG设计包括以下几个概念:保持连续和谐、与众不同的世界和NPC。
1.保持连续和谐
RPG是非常值得开发的产品,因为它们能产生续篇,当然首先您的第一个游戏应获得成功!如果您用一个好故事创建了一个动态的世界,那么您可以不断地用新的创意来更新这个世界,而游戏者对您的游戏也会很喜欢。看看主要的RPG游戏就会发现它们都是成系列的。
当您创建您最初的游戏时,考虑加点将来的新版本中可以扩展的创意。例如,您可以介绍一个在稍后的游戏中作为对手出现的龟色。
您也应该仔细考虑您创建的世界。您要让游戏者投入到这个世界中,并且要让他们喜欢这里发生的每一件事情。下一个主题就是设计这样的世界。?
2.与众不同的世界
因为计算机探险游戏已经越来越复杂化了,创建世界就变得更加重要了。在创建世界过程叭设计者追寻创建一个完整的运转着的社会,其中包括全部角色和他们那些由情节决定的“命运”。基本思想是将故事放到一个环境中,而不是让环境适应故事。
另外,建造的世界要能产生小小的难题。尽管世界是假想的,但它必需真实。游戏老会这样评价您的世界:
这个假想的新世界是否与众不同而又很有趣?
这个世界中的系统是否运转得很真实?(例如,当我将货物卖到一个城镇时,货物的价格
是否会降下来?)
3.NPC交互和交流
设计RPG的最大问题是非游戏者角色(NPC-Non-player Character)的交互作用。简单说就是,如果RPG角色不能产生动态交互作用,那么游戏的深度就会降低。
解决的办法是很显然的:在内部仿真引擎中增加灵活性并在游戏中加入人工智能功能。目的是通过他们前面的动作将NPC与游戏者联系起来,使他们之间的交互更多,让游戏者通过自然的交互投入到虚拟的世界中。对于像天气和佩带武器这方面,开发者也应该考虑一下。开发者同样要力争创造出更好的RPG对话,要使角色具有记忆能力,不同的对话要与不同的游戏内容相适应。
当设计您的RPG时,应仔细考虑如何实现逐步增加难度级别和对话交互。
4.战斗和战争
在几乎每一类的RPG中最关键的要素都是游戏中的战斗类型。战斗是划分RPG和探险
游戏的主要差别。尽管大多数探险游戏都有一定次序的战斗,但战斗并不是重复出现的成分。
RPG游戏中的战斗部分就像一个小型战争游戏。游戏者控制他们的角色,用武器或魔法武装他们。然后,在各种选择和统计预测的基础上,战斗开始了。活下来的角色通常战斗力得到了提高。
更具体的还有,设计者必需决定让游戏者确定战斗的策略还是把战略部署留给程序去做。
某些开发者设计出允许游戏者在任何时候重新控制战斗的游戏。另一个实现是给游戏者许多战术选项以供挑选,如移动、攻击类型等等。还有一些游戏提供了“自动”方式,它允许游戏者观看计算机控制他(或她)的军队的战略部署。自动方式的目的有两个:开始游戏者通过观看可以学习如何战斗;而具有绝对优势的游戏者可以让计算机操作战斗,以减少自己控制的工作,并尽快结束战斗。”
未来展望
RPG现在看来处于一种不稳定的状态,但是数量可能比其它任何一种类型的游戏都多。


第九节模拟游戏

模拟游戏在飞行模拟器的带动下曾经是最强有力的游戏之一。模拟常常与某种类型的军
事硬件有联系,例如F16战斗机或坦克、潜艇、直升飞机模拟器。另外,除了这些战争工具模拟器,还有驾驶模拟器与核动力飞机模拟器。
我们只讨论那些基于“交通工具”轨迹的模拟器,包括飞行和其它军事模拟器、驾驶游戏和宇宙飞船战斗游戏。
1.模拟游戏设计问题
1)模拟对象
现有模拟游戏很容易分为两类,如下:
A。军事的与非军事的交通工具模拟器
主要的模拟器产品集中在军事交通工具模拟器上。像Microprose和spectrum Holy-byte等公司几乎整个公司的力量都投在飞机、坦克和战舰等军事模拟器上。
B字航模拟器
大量的模拟器产品,特别是那些为虚构世界设计的产品,与宇宙空间有些联系。太空
是最后的未知领域,可以也产生了令人难以置信的模拟器游戏。
2.似乎真实的细节
游戏设计者要充分考虑细节。有些游戏需要的细节很少,但有些需要的就多。许多模拟器
设计者在设计的全过程中都能很好地把握这个问题。但是也有些设计者感到模拟的世界意味着产品必须尽可能与现实接近。然而,有时为真实世界奋斗的结果会使一个好游戏变成烦人的或很难玩的东西。
例如,您想设计一个包括起飞前检查等各种细节的飞行模拟器,这真的对整个游戏有好处
吗?这对局部精确度也许有好处,但是它损害了整体的操作特性。
最后,您和测试者必须决定多少细节是必要的,但是无论细节多么真实,无用的细节必须
去除。别忘记,人们玩游戏就是为了从现实世界的琐碎中解脱出来。
3.令人难受的传道区
许多流行的模拟游戏在游戏中设计一些“传道区”(以讲解或说明情况),除了赞许,此外并没听到别的什么。当您设计您的模拟器时,考虑在您的产品上设计各种各样的传道区,并把最主要的故事线索结合到您的产品中。
4.引擎
模拟游戏也经常用复杂的3D图像引擎来开发。许多游戏具有续集或是使用相同的引擎
来开发其它的模拟类型游戏是不奇怪的。生产一系列优秀模拟优秀的关键是要有一个优秀的3D图像引擎和发现一个潜在的模拟游戏领域来应用那些技术。
5.寻找新的机会
在游戏世界中模拟游戏是一个对图像很敏感的类型。因此,开发者通过提高图像质量就可
以很“容易”并有效地提高这些游戏的质量。您只要看看市场上的大量飞行模拟器就知道了。但是,更流行的摸拟器是把精力集中在新的创意上的。


第十节 战争类游戏
战争类游戏可以被分为即时战略游戏和回合制游戏两类。
在即时战略游戏中主要要考虑的问题是人工智能和力量均衡的问题。对于人工智能,主要是电脑对手的智能和双方战斗单位的智能的设计问题。大部分游戏者是不会接受一个太蠢的只会作弊的对手??人工智能设计太差,也不会有太多耐性去挑战一个功防之间简直毫无破绽的“专业级”的对手??太专业的人工智能设计,也不会容忍自己的手下只会走直线??简陋的路径搜索算法。所以对人工智能部分应仔细的考虑和测试。在力量均衡问题上主要是考虑各种高级武器和基本武器间性能的差异、各种高级武器的造价、建造时间、出现时间及条件的设置和个民族所特有的武器的特点的构思。
相对于即时战略游戏,回合制游戏在设计时仅仅可以不考虑路径算法的问题,其他的问题是基本相似的。


第十一节 总结
上面谈到的游戏类型仅仅是从技术上而言的,在现在的大部分游戏中实际上是包含了这些类型之中的一种以上的特性,或与其相似。比如说文明和七个王国就包含了主管类游戏和即时战略游戏的特性,现在大部分的即时战略游戏都有连线作战的能力等等。所以您在设及您的游戏是应考虑所有可加入的特性。
好我们的介绍到此就算是结束了,但对于您来说,还有很长的路要走。游戏中可以说包含了整个计算机技术的方方面面,作为一个游戏开发的程序员来说您还需要了解很多的知识掌握很多的技巧。
俗话说“师傅领进们,修行靠个人”,我们就只能言尽于此了,剩下的就靠您自己了。
祝您开发出世界级的游戏,成为振兴中国游戏业的功臣。


关注者