A9VG电玩部落论坛

 找回密码
 注册
搜索
查看: 19334|回复: 31

[翻译]任天堂 NDS 游戏开发入门指南

[复制链接]

退伍者

本命年换兔子头保平安

精华
3
帖子
11433
威望
14 点
积分
12723 点
种子
9 点
注册时间
2004-2-3
最后登录
2023-12-12
 楼主| 发表于 2010-3-22 22:04  ·  北京 | 显示全部楼层 |阅读模式
作者:Jaeden Amero
Copyright ?2006-2008 Jaeden Amero
翻译:A9VG_sunhaolun
原文地址:http://patater.com/manual
声明:在下仅仅不过是电子通信这一方面的小小学生,英文什么的也只是刚刚入门,本文本来仅仅是作为毕业翻译作业而进行的。文中肯定有翻译得不到位的地方,还请各位能多多包涵,有错误的地方,请尽管提出来,在下将会一并改之。
**禁止转载,A9独占,水平有限,还请海涵**

[B]版本更新历史[/B][/ALIGN]
版本 6.1[/ALIGN]
2008年12月14日[/ALIGN]
jna[/ALIGN]
更新了采用libnds 1.3.1开发的部分。[/ALIGN]
版本 6.0[/ALIGN]
2008年1月3日[/ALIGN]
jna[/ALIGN]
这个版本中更新了Nintendo DS下多重像素、多重背景、多重调色板、触摸屏、烧录卡的使用等等内容。手册经过重新编写,并且涵盖了许多以往没有的内容。[/ALIGN]
版本 5.0[/ALIGN]
2007年3月31日[/ALIGN]
jna[/ALIGN]
排版方式进行改动。手册添加了DocBook格式。[/ALIGN]
版本 4.0[/ALIGN]
32006年10月31日[/ALIGN]
jna[/ALIGN]
新增VRAM(显存)附录,更新页面排版,更正一些拼写错误。[/ALIGN]
版本 3.0[/ALIGN]
2006年9月23日[/ALIGN]
jna[/ALIGN]
增加音频章节。[/ALIGN]
版本 2.2[/ALIGN]
2006年7月12日[/ALIGN]
jna[/ALIGN]
更正拼写错误。[/ALIGN]
版本 2.1[/ALIGN]
2006年5月13日[/ALIGN]
jna[/ALIGN]
修正代码,拼写错误,以及多余的话。[/ALIGN]
版本 2.1[/ALIGN]
2006年3月21日[/ALIGN]
jna[/ALIGN]
完成了很多章节部分,并且添加优化的代码。[/ALIGN]




[B]目录[/B][/ALIGN]
前言 [/ALIGN]
主要问题 [/ALIGN]
解决办法 [/ALIGN]
如何使用本手册[/ALIGN]
[U]1. [/U][U]任天堂DS自制软件的法律相关[/U] [/ALIGN]
[U]背景资料[/U] [/ALIGN]
自制软件是否合法?[/ALIGN]
2. 何为以及如何使用Passthrough? [/ALIGN]
使用Passthrough的目的 [/ALIGN]
[U]PassMe[/U][U]工作原理[/U] [/ALIGN]
[U]Passthrough[/U][U]历史[/U] [/ALIGN]
[U]未来就在眼下,眼下,眼下。[/U] [/ALIGN]
关于 NoPass [/ALIGN]
关于 Slot-1卡槽的烧录设备 [/ALIGN]
如何得到Passthrough [/ALIGN]
[U]应该购买何种Passthrough[/U][/ALIGN]
[U]如何选择老版Passthrough?[/U] [/ALIGN]
PassMe 2 购买提示 [/ALIGN]
如何使用 Passthrough [/ALIGN]
[U]如何使用 Passthrough [/U][/ALIGN]
3. 如何将程序写入任天堂DS中? [/ALIGN]
方法 [/ALIGN]
[U]我应该买什么样的烧录设备?[/U][/ALIGN]
[U]DLDI[/U][U]是什么?[/U] [/ALIGN]
[U]Slot-1[/U][U]设备优劣评判标准[/U] [/ALIGN]
[U]R4DS[/U][U]烧录设备[/U] [/ALIGN]
[U]M3 Real[/U][U]烧录设备[/U][/ALIGN]
Cyclo DS Evolution[U]烧录设备[/U] [/ALIGN]
如何购买烧录设备? [/ALIGN]
[U]采用Slot-2槽的设备挑选[/U] [/ALIGN]
运行多个软件[/ALIGN]
4. 如何编程? [/ALIGN]
[U]关于devkitPro开发部件[/U] [/ALIGN]
[U]Libnds[/U][U]的精彩世界[/U][/ALIGN]
[U]安装devkitARM开发包[/U] [/ALIGN]
[U]从源代码安装libnds[/U] [/ALIGN]
[U]下一步[/U][/ALIGN]
[U]5. [/U][U]如何显示背景图片?[/U] [/ALIGN]
[U]一些关于背景的资料[/U] [/ALIGN]
2D图像引擎 [/ALIGN]
第五模式 [/ALIGN]
[U]精细[/U][U]的仿射背景[/U] [/ALIGN]
[U]以本手册来[/U][U]进行编程[/U] [/ALIGN]
硬件初始化 [/ALIGN]
[U]配置VRAM Bank[/U] [/ALIGN]
[U]仿射背景的建立[/U] [/ALIGN]
定点数入门 [/ALIGN]
DMA基础知识 [/ALIGN]
[U]Makefile[/U][U]使用介绍[/U] [/ALIGN]
[U]速成教程[/U] [/ALIGN]
[U]放入星空[/U] [/ALIGN]
编译[/ALIGN]
[U]6. [/U][U]精灵是什么,如何使用?[/U] [/ALIGN]
何为精灵图像? [/ALIGN]
OAM介绍 [/ALIGN]
精灵图像显示的硬件之信息 [/ALIGN]
精灵图在内存中的存储方式 [/ALIGN]
精灵图像的属性 [/ALIGN]
更新OAM [/ALIGN]
OAM初始化 [/ALIGN]
旋转精灵图 [/ALIGN]
显示隐藏的精灵图 [/ALIGN]
移动精灵图 [/ALIGN]
设定精灵图的优先级 [/ALIGN]
精灵图的使用 [/ALIGN]
为精灵图像设置VRAM显存 [/ALIGN]
精灵图像的寻址方式 [/ALIGN]
[U]精灵图像的读入[/U] [/ALIGN]
断言是什么? [/ALIGN]
[U]精灵图的显示[/U] [/ALIGN]
编译[/ALIGN]
[U]7. [/U][U]太空飞船射击游戏类型的基本开发过程[/U] [/ALIGN]
面向对象编程语言的重要性 [/ALIGN]
[U]飞船的设定[/U] [/ALIGN]
制作飞船 [/ALIGN]
构造函数 [/ALIGN]
加速度 [/ALIGN]
飞船的移动 [/ALIGN]
[U]飞船方向[/U][U]改变的方法[/U] [/ALIGN]
[U]飞船的旋转[/U] [/ALIGN]
飞船位置的获取 [/ALIGN]
飞船角度的获取 [/ALIGN]
[U]将飞船添加进我们的程序[/U] [/ALIGN]
[U]创建主游戏循环函数[/U][/ALIGN]
[U]编译[/U][/ALIGN]
[U]8. [/U][U]任天堂DS输入系统[/U] [/ALIGN]
概述 [/ALIGN]
按键输入 [/ALIGN]
触摸! [/ALIGN]
编写一个输入更新函数 [/ALIGN]
编写一个输入控制函数 [/ALIGN]
[U]再次创建主游戏循环函数[/U] [/ALIGN]
编译[/ALIGN]
9. 音频的实现 [/ALIGN]
音频理论 [/ALIGN]
硬件部分 [/ALIGN]
[U]音频的制作[/U] [/ALIGN]
音频的使用 [/ALIGN]
添加音频的代码 [/ALIGN]
[U]功能强大的M[/U][U]axmod[/U] [/ALIGN]
编译[/ALIGN]
[B]图示目录[/B][/ALIGN]
2.1. 早期PassMe图片(左)和FPGA(右) [/ALIGN]
2.2. [U]插入DS卡槽的P[/U][U]assMe[/U][/ALIGN]
3.1. [U]GBAMP[/U][U]([/U][U]左[/U][U])[/U][U]与GBA烧录卡的比较[/U] [/ALIGN]
5.1. 光栅显示 [/ALIGN]
5.2. libnds 仿射背景的应用程序接口 [/ALIGN]
5.3. [U]使用整数变量来表现分数[/U] [/ALIGN]
5.4. 程序运行时的样子 [/ALIGN]
6.1. 上面的文档显示了使用无tile背景的信息。下面的文档显示了在图像模式中相同数据、tile的使用情况。 [/ALIGN]
6.2. 同时输出背景和精灵图像。 [/ALIGN]
8.1. 橘红色***的包围显示. [/ALIGN]
9.1. [U]加入音效后的橘红色***的包围显示[/U][/ALIGN]
[B]列表目录[/B][/ALIGN]
2.1. [U]弹出游戏[/U][U]同时显示固件版本[/U] [/ALIGN]
5.1. 第五模式信息 [/ALIGN]
5.2. VRAM显存 Bank 信息 [/ALIGN]
7.1. ***优先级及功能列表 [/ALIGN]
8.1. libnds的关键定义[/ALIGN]
[B]前言 [/B][/ALIGN]
[B]内容目录[/B][/ALIGN]
主要问题 [/ALIGN]
解决办法 [/ALIGN]
如何使用本手册[/ALIGN]
[B]主要问题[/B][/ALIGN]
大家肯定都很喜欢玩任天堂DS游戏。每一款游戏都设计的很巧妙有趣,让你能够回味良久。你也许在想如何才能独自开发一款游戏或者是一款软件,也许你会有很多喜欢的游戏想要和大家分享。[/ALIGN]
但是要如何才能开发一款DS游戏呢?想到这,恐怕很多人都不知所措。应该从哪里入手?在这个神奇的双屏小盒子里究竟有多少秘密呢?本手册就是为了帮助各位能够将自己的想法实现于任天堂DS上。花上一些功夫和时间,也许你就能够用自己的方式开发出一款属于你的游戏。加入自制软件开发者的行列吧。与开发者们一起合作,体验游戏开发的乐趣以及面对无数程序代码时的压力吧。 [/ALIGN]
[B]解决办法[/B][/ALIGN]
首先本手册将会讲述如何入手。其中,我会说明任天堂DS的基础开发方法、自制软件的法律相关知识、烧录设备的使用/挑选、设置开发环境、背景的显示、使用像素图、基础游戏开发技术。所有的内容都将配以简图以及相关程序例子来进行说明(一艘橘黄色的小飞船)。 [/ALIGN]
[B]如何使用本手册[/B][/ALIGN]
可能各位多少了解过C或者C++语言编程。如果没有的话,稍微花上20个小时左右来试一下小的程序进行练手吧。程序是不是你自己写的并不要紧,只要你能够保证完全明白程序的架构、级数、栈运行、循环、按位操作以及逻辑执行即可。以下我推荐两个网址可以进行此方面的上手学习: http://www.cplusplus.com/doc/tutorial/[/ALIGN]
http://www.cppreference.com/ [/ALIGN]
接下来,按部就班地按照本手册一步一步地进行学习,确保你能够完全掌握每一个章节。代码部分以灰色字进行标注。以本手册提供的代码尝试着编写自己的程序吧。 [/ALIGN]
.

退伍者

本命年换兔子头保平安

精华
3
帖子
11433
威望
14 点
积分
12723 点
种子
9 点
注册时间
2004-2-3
最后登录
2023-12-12
 楼主| 发表于 2010-3-22 22:06  ·  北京 | 显示全部楼层
[B]章节1.[/B] [B]任天堂DS自制软件的法律相关[/B][/ALIGN]
[B]内容目录[/B][/ALIGN]
背景资料 [/ALIGN]
[U]自制软件是否合法?[/U][/ALIGN]
[B]背景资料[/B][/ALIGN]
自从任天堂DS发售以来,对该主机喜爱非常的孩童或是成年人可能都有想到做一款属于他们自己的DS游戏或者是DS程序。甚至也把DS笑称为“Developer`s System(开发用系统)”。并且也有相关的开发包提供给开发人员。但是大部分人可能并不能接触到开发工具,并且也得不到开发任天堂DS游戏的特殊许可。要想得到开发工具,需要提供自己开发的这款游戏的价值所在或者是部分程序给任天堂。还需要具备一定的资金基础,并且有足够多的金钱来购买开发工具。可就连很多游戏公司都未必能够做到这一点。现在市面上卖的游戏大部分都是由发行商代理发行的。游戏开发商需要先提供一部分游戏内容给发行商,然后发行商(已经拥有任天堂DS开发工具)将会赞助开发商一笔钱,然后等到游戏正式开发完毕之后再将这款游戏推向市场。这个过程极大地增加了普通爱好者开发独立游戏的困难程度。[/ALIGN]
所以自制软件才会如火如荼地开展了起来。一些专业爱好者花费数周时间来反编译任天堂DS的引擎就是为了能够使普通的玩家也可以开发出游戏(相对于官方开发包来说要更便宜)。这些爱好者从事各种不同的行业,其中很多人都是欧洲人和美国人,利用他们所掌握的知识来实现这一目标。正是这样一批人,造就了如今的自制软件热潮。[/ALIGN]
[B]自制软件是否合法?[/B][/ALIGN]
自制软件肯定是合法的,并且理由非常之多。玩家购买了主机,自己反编译了引擎并且利用它来做自己想做的事情,只要不违法那便是被允许的。那么什么样才是违法呢?其中有破坏版权保护部分来制作盗版游戏,发行一些包含了隐藏商业信息的软件,总之以此来获利的话那肯定就是违法的了。因为自制软件的制作水准比起官方开发包来说要相对低一些,所以并不会影响官方开发包的市场。即便你开发出一款非常非常棒的软件,也不可能将其发行开卖。但是总有一些厂商利用自制软件来获利,将自制软件制作成盗版并贩卖,这种行为非常让人讨厌。自制软件开发者们也尝试着各种方式来与盗版商们进行对抗。[/ALIGN]
如果你付钱购买了主机,那么你就拥有对这台主机的所有权。这也就意味着你可以拆开它看看到底是怎么回事,或是反编译它的引擎部分甚至其他什么的。但是这样可能就无法得到保修了,为了学到相关的硬件知识或是软件知识,这点学费还是应该交的。但如果你要将其中的某些具有专利的部分或是通过破坏防盗版机制偷取来的软件代码并进行买卖那就肯定是属于违法行为了。而进行反编译来学习研究这一硬件系统的话则完全没问题。[/ALIGN]
自制软件工具包要比游戏厂商所提供的官方游戏开发包稍逊。首先游戏开发商有具体的硬件资料手册,而自制软件开发者们只能够通过不断地试验来掌握硬件性能。[/ALIGN]
想要贩卖利用自制软件开发工具开发出来的游戏基本是不可能的,因为任天堂可不会给你的游戏提供授权。而其他的游戏厂商也不会违反任天堂的意愿来把你的游戏发行出去。其他的游戏平台也是如此。[/ALIGN]
厂商一般不会找自制软件的麻烦,因为它提供了玩家对于其游戏主机另一部分需求,而且还能帮助厂商来了解消费者的需求。XBOX主机就是一个很好的例子。如果不是自制软件的出现,微软根本不会意识到玩家们很喜欢那些独立游戏、老游戏、甚至是跑个linux系统什么的。在XB360主机上,微软已经很好的将这些功能集成了进去(当然肯定不包括运行linux系统),玩家们可以在利用XNA游戏开发工具来开发独立游戏,也可以在XBOX live上玩到经典的老游戏。如果厂商要打压自制软件的话,那将明显同时打压了核心fans的热情(而这对于厂商来说没有丝毫好处)。自制软件使得微软看到了这部分玩家的需求并且完全将其正式化。[/ALIGN]
自制软件的缺点就是经常有盗版商利用自制软件来制作盗版并进行盈利,因此还有一些游戏厂商和自制软件对着干,但这显然是徒劳的。遗憾的是盗版在任何产业都是不可避免。由于盗版软件的疯狂,很多发行商可能不会在某盗版横行的游戏平台上发行游戏,这极大地破坏了游戏开发商的开发热情。自制软件开发人员也深知这一点,而且也体验过这样的痛苦。因此虽然自制软件开发人员虽然知道如何破解版权保护但一般都不会这么做,因为他们不想看到自己喜爱的游戏主机因为盗版而过早地退出市场。[/ALIGN]

退伍者

本命年换兔子头保平安

精华
3
帖子
11433
威望
14 点
积分
12723 点
种子
9 点
注册时间
2004-2-3
最后登录
2023-12-12
 楼主| 发表于 2010-3-22 22:06  ·  北京 | 显示全部楼层
[B]章节 2. 何为以及如何使用Passthrough?[/B][/ALIGN]
[B]内容目录[/B][/ALIGN]
使用Passthrough的目的 [/ALIGN]
PassMe工作原理 [/ALIGN]
Passthrough历史 [/ALIGN]
未来就在眼下,眼下,眼下。 [/ALIGN]
关于 NoPass [/ALIGN]
关于 烧录设备 [/ALIGN]
如何得到Passthrough [/ALIGN]
[U]应该购买何种Passthrough?[/U] [/ALIGN]
如何选择老版Passthrough? [/ALIGN]
PassMe 2 购买提示 [/ALIGN]
如何使用 Passthrough [/ALIGN]
如何使用 Passthrough  [U][/U][/ALIGN]
[B]使用Passthrough的目的[/B][/ALIGN]
使用Passthrough的目的就是为了使任天堂DS利用机身上的GBA卡槽来运行程序。由于早期任天堂DS上的DS卡槽读取部分是经过加密的,所以只能利用GBA卡槽来实现。由于破解加密手段是违法的并且可能会导致盗版大量出现,所以我们通过其他的方式来运行自制软件,而且对于当时来讲,显然绕过加密检测要比破解它难很多。[/ALIGN]
[B]PassMe[/B][B]工作原理[/B][/ALIGN]
当DS启动的时候,会首先读取NDS卡带的引导程序,这段程序将指引DS完成游戏的初始化操作,其中可能包含着游戏的相关信息(包括游戏名称、发售日期、厂商、图标、以及其他一些硬件信息)。这个引导程序包含一个指向内存中程序起始位置的指针。passthrough的作用就是读取引导程序并且将其改为引导至GBA卡带中的位置。那么为什么要引导至这个位置呢?很明显因为我们所编写的代码就保存在这里。[/ALIGN]
[B]Passthrough[/B][B]历史[/B][/ALIGN]
第一款Passthrough设备是由DarkFader (Rafael Vuijk)开发出来的。他通过FPGA(Field Programmable Gate Array现场可编程门阵列)将passthrough写入一块CPLD(Complex Programmable Logic Device 复杂可编程逻辑器件)芯片中。依靠着这一点又出现了很多独立开发passthrough的人(包括Kraln, Firefly, and Natrium42),但DarkFader可是完完全全自主设计的。[/ALIGN]
在接下来的几个月,各种passthrough层出不穷。Lynx 和 Natrium42都是当初自制软件团体的主要成员,后来他们在网上开始贩卖制作的好的passthrough设备,并取名为“Passme”。许多尚处于萌芽阶段的DS程序员都是购买的他们的passthrough设备(也包括我自己)。如果你有意购买相关的设备,可以来Lynx的网店看看,地址是DSPassme.com。[/ALIGN][B]图示2.1. 早期PassMe图片(上)和FPGA(下)[/B]


[B]未来就在眼下,眼下,眼下。[/B][/ALIGN]
PassMe是首款可以在任天堂NDS上运行自制软件的设备。由于这个原因,开始出现一些盗版软件。为了防盗版,任天堂公司进行了改进,新型号的任天堂DS已经不再能够运行老的PassMe了。后来他们又开发出了PassMe 2。新型号可以绕过新的加密保护,但是需要更多的硬件资源(所以在后来的产品中都加入了SRAM)以及更加复杂的程序。随后不久,一款名为“NoPass”的设备出现在人们的眼前。[/ALIGN]
[B]关于 NoPass [/B][/ALIGN]
由成功dump出任天堂DS主机BIOS的Martin Korth破解了正规游戏卡带的加密保护,并且反编译了反盗版信息,使得NoPass可以伪装成真正的任天堂DS游戏卡带,而这正是由于它包含了与正规卡带相同的加密代码。从不需要绕开版权保护这一点上来看,NoPass已经不属于passthrough的范畴了。[/ALIGN]
[B]关于 Slot-1卡槽的烧录设备[/B][/ALIGN]
最近,通过研究Martin Korth取得的成果,自制软件开发者们发明了一种可以直接利用DS卡槽运行自制软件的新方法。由此设计开发的新产品包含了小型化的passthrough设备(以NoPass方式运行)以绕过加密保护,并且内建flash内存或者搭载了microSD卡槽。这种设备被称为Slot-1烧录设备。而想老型号的需要利用GBA卡槽进行操作的则一般被称作Slot-2烧录设备。[/ALIGN]
[B]如何得到Passthrough [/B][/ALIGN]
现在在市面上有很多种Passthrough以及NoPass。如果你想买一部以研究的话,强烈推荐购买由自制软件爱好者开发的而不是厂家推出的用于盗版DS软件的产品(比如SuperCard系列的SuperPass、SuperKey,NeoFlash的MK4-Mini或者MagicKey,G6Flash的PassKey,M3Adapter的Passkey或Passcard,或是由非常可恶的并且没什么技术含量的Datel推出的Max Meida Launcher)。所有这些厂商都是利用盗版任天堂DS游戏软件来获利,喜爱任天堂游戏的玩家最好不要为这些厂商花上任何一分钱。购买passthrough最佳地点是DSPassme.com。[/ALIGN]
[B]应该购买何种Passthrough ?[/B][/ALIGN]
强烈推荐购买NoPass或者采用Slot-1卡槽设计的自制软件设备,因为他们可以在任何任天堂DS主机上顺利地运行。而像一些老型号的passthrough则不能保证在任何型号的NDS主机上运行,PassMe 2则需要额外的硬件资源,而且他们都要在原卡槽空间商多出一截,外观上也不太好看。[/ALIGN]
[/ALIGN]
采用Slot-1槽的设备相比Slot-2槽的设备有许多有点,比如它要比Slot-2槽的设备更省电。这意味着你的DS将获得更加持久的电力。许多Slot-1槽的设备还包括了NoPass的功能,所以你不再像以前那样需要买一个passthrough然后再买一个Slot-2槽的设备了。[/ALIGN]
[B]如何选择老版Passthrough? [/B][/ALIGN]
如果你坚持要买老型号的passthrough的话,那么就需要考虑很多问题了。首先要考虑的是你购买的NDS是什么型号的,你可以买到两种不同型号的passthrough。第一种是与DarkFader开发的初代产品极为相似的“PassMe”,它可以在大部分第一代NDS主机(非DSL或DSi)上运行。[/ALIGN]
[/ALIGN]
然而,如果你的NDS是新型号(无论是厚版的还是DSL都有可能),你可能就需要“PassMe 2”了,采用这种设备的原因是后来的NDS主机采用了新的固件。[/ALIGN]
这种采用新固件的主机不允许引导程序变更至GBA卡槽。然而还是有办法实现这一点的。PassMe 2可以使主机引导至某些特定的存于卡带内存中的指令,方法是由软件中断引导至GBA卡带的SRAM上,而在GBA卡带的SRAM上保存着一些必要的代码来跳转到GBA卡带所保存的程序中。每一块PassMe 2都要为一些特定的游戏进行更新,并且要为不同的DS游戏重新定义GBA卡带的引导程序地址。[/ALIGN]
你可以用以下的办法来查看你所购买的主机固件。利用主机内置的pictochat软件,然后进行一系列操作便可以查看,基于硬件固件信息再决定到底需不需要购买PassMe 2。 表格2.1“弹出卡带,显示固件版本” 表格2.1“弹出卡带,显示固件版本”[/ALIGN]
[B]步骤 2.1. 检查固件版本[/B][/ALIGN]
1.插入一张NDS游戏卡带。[/ALIGN]
2.打开NDS。(如果你设置了自动启动,那么请在系统启动时按住Start键和Select。)[/ALIGN]
3.点击屏幕菜单中的Poctochat启动Pictochat。[/ALIGN]
4.进入任何一间***。[/ALIGN]
5.拔出插在主机上的游戏卡带。[/ALIGN]
6.Pictochat会死机,或者两个屏幕都改变了颜色。[/ALIGN]
[B]表格 2.1. 弹出卡带,显示固件版本[/B][/ALIGN]
[B]观察结果[/B][/ALIGN]
[B]固件版本[/B][/ALIGN]
Pictochat 死机[/ALIGN]
版本 1[/ALIGN]
两个屏幕变成浅蓝色[/ALIGN]
版本 2[/ALIGN]
两个屏幕变成暗绿色[/ALIGN]
版本 3
神游iQue 或者 Flashme版[/ALIGN]
两个屏幕变成橘黄色[/ALIGN]
版本 4[/ALIGN]
两个屏幕变成洋红色[/ALIGN]
版本 5[/ALIGN]
两个屏幕变成深蓝色[/ALIGN]
版本 6[/ALIGN]

I总之如果你真的无法确认自己的NDS类型的话强烈建议你购买NoPass这种可以在任何时候在任何一台NDS主机上运行的产品。如果你有一个朋友想玩你编写的游戏的话,那么一个NoPass就可以做到这一点。[/ALIGN]
[/ALIGN]
[B]PassMe 2 [/B][B]购买提示[/B][/ALIGN]
如果要买PassMe 2 的话,一定要买那种最新更新,允许你运行你自己的游戏的版本,比如要能够运行《银河战士:猎人》DEMO或者是超级马里奥DS版。每一块PassMe 2都要经过更新来适应不同的游戏,而且最好不要买一些太过偏门的游戏。而且你还需要一块带有SRAM的GBA卡带。大部分的GBA烧录卡都带有SRAM,但是一定要检查你的卡带能够兼容PassMe 2。有一些新的GBA烧录卡可能只能以flash内存的方式保存游戏记录,这样的话以后肯定是要麻烦一些的。[/ALIGN]
[B]如何使用 Passthrough [/B][/ALIGN]
使用 Passthrough 设备是非常简单的。只要将一款游戏卡带插在passthrough上然后再将其插入DS卡槽即可,任何游戏均可。图示2.2“插入DS卡槽的PassMe” 中可以看到其样态。[/ALIGN][B]图示2.2“插入DS卡槽的PassMe”[/B]

如何使用 Passthrough  
最好用的一项功能就是利用passthrough可以为你的DS里的固件安装一个小叫做“FlashMe”的小补丁。这个固件补丁可以运行未认证的wifi连接,也可以让DS以NDS模式运行GBA卡带里的内容。这也就意味着一旦你安装了“FlashMe”,你就不再需要PassMe了。如要用来运行自制软件的话,那这种方式无异是最方便快捷的了,真正的做到了一劳永逸,你根本不用再插拔passthrough。如果你愿意的话,甚至都不再需要烧录卡,通过无线多重启动(WMB)都可以发送自己的代码。很多人可能因此会担心以后会有一些恶意代码破坏主机的固件,或者是时间久了会把机器弄得很乱。其实大可不必担心,FlashMe本身具有恢复程序,你可以随时恢复你的DS系统。处于这个原因,安装FlashMe还是很有必要的。有关FlashMe的信息可以在这里查询到http://home.comcast.net/~olimar/flashme/.

退伍者

本命年换兔子头保平安

精华
3
帖子
11433
威望
14 点
积分
12723 点
种子
9 点
注册时间
2004-2-3
最后登录
2023-12-12
 楼主| 发表于 2010-3-22 22:06  ·  北京 | 显示全部楼层
[B]章节3.[/B] [B]如何将程序写入任天堂DS中?[/B][/ALIGN]
[B]内容目录[/B][/ALIGN]
方法 [/ALIGN]
我应该买什么样的烧录设备? [/ALIGN]
DLDI是什么? [/ALIGN]
Slot-1设备优劣评判标准 [/ALIGN]
R4DS烧录设备 [/ALIGN]
M3 Real烧录设备 [/ALIGN]
Cyclo DS Evolution烧录设备 [/ALIGN]
如何购买烧录设备? [/ALIGN]
采用Slot-2槽的设备挑选 [/ALIGN]
运行多个软件[U][/U][/ALIGN]
[B]方法[/B][/ALIGN]
这里有几种办法来让你的NDS运行自己的代码。第一种就是使用GBA烧录卡。这种烧录卡一般价格都比较高,而且易用性上稍微差一点,内存容量上也不是很充足。这种办法能够很好的兼容老版本的NDS,而且底部也不会多出一块,但是NDSL的话则会多出一块了。第二种方法就是使用可擦写内存设备来实现。这种设备可以选择同时利用Slot-1槽和Slot-2槽或者只Slot-2槽。比较有名的如M3 Adapter、G6Flash、NeoFlash、SuperCard还有GBAMoviePlayer,一般都能很好的兼容DS和GBA的软件。Slot-1的设备有M3Simply、R4DS、M3Real、DS-X、NinjaDS以及CycloDSEvolution,只支持DS软件。这其中有四分之一的产品是由盗版厂商所开发的,应尽量避免挑选这样的产品。如果你想选择Slot-2槽的设备的话,推荐购买GBAMoviePlayer(GBAMP)。而对于Slot-1槽的产品的话,剩下的这些似乎都或多或少的与盗版沾了边儿。[/ALIGN]
[B]我应该买什么样的烧录设备?[/B][/ALIGN]
很遗憾现在暂时没有专门用于运行自制软件的烧录设备,也就是说我们不得不购买那些专门用于盗版游戏的产品了。但是其中一定要挑选包含DLDI功能的产品。
[B]DLDI[/B][B]是什么?[/B][/ALIGN]
DLDI全称为dynamically linked device interface,亦即动态连接设备接口。为了能够适应自制软件文件系统比如libfat,你就需要购买支持DLDI标准的产品了。带有DLDI的产品可以在系统下动态地读取自制软件。也就是说它允许用户随时使用开发者开发出来的自制软件。如果你买的设备不支持DLDI功能,那么很遗憾可能将有大部分的自制软件以及游戏无法运行。虽然很多盗版厂商可能会高呼什么“我们的产品就是专门用来玩游戏的”之类的话。我们真的希望这样的厂商能够倒闭。你可以在维基百科上查看如何DLDI的相关信息http://dldi.drunkencoders.com/index.php?title=Main_Page. [/ALIGN]
[B]Slot-1[/B][B]设备优劣评判标准[/B][/ALIGN]
现在市场上也有许多Slot-1槽的设备。由于种类实在太多,所以选择一款适合自己的产品是非常难的。在本手册的中,我只谈我熟悉的一些性能比较不错的产品,并提供对照。[/ALIGN]
[B]R4DS[/B][B]烧录设备[/B][/ALIGN]
T这款产品也非常优秀。恐怕是现在卖得比较不错的了。这款产品使用了microSD卡带来进行存储,并且支持DLDI。在最新的固件中,可以自动得更新软件以及DLDI驱动。也就是说你不必再用电脑为你的自制软件打补丁了。然而需要注意的是,R4DS不能使用microSDHC存储卡。有些用户对弯曲的microSD卡槽有所抱怨,开发商也注意到了这一点,在新版本中已经将这个问题解决了。总而言之R4DS是一款比较优秀的产品,能够很好的支持DS自制软件,但是唯一的一点不足就是它无法运行GBA游戏。[/ALIGN]
[B]M3 Real[/B][B]烧录设备[/B][/ALIGN]
这款产品提供了不同的套装来满足你的需要。分别有Slot-1槽和Slot-2槽两个版本。Slot-2槽版本中还细分为3种不同的套装。第一种就是最便宜的,只有一个震动包。第二种稍贵,包含两个震动包以及额外的RAM,而且也可以运行GBA上的自制软件。出于某种原因,M3并未公布烧录卡带的容量大小。经由好心的Natrium42提示,以及一系列的测试之后,我发现其容量大概有8MB左右,但是没有SRAM。第三种最贵,包含了GBA上的完整兼容功能,提供了震动包、SRAM以及32MB RAM。[/ALIGN]
M3 Real也能够很好的支持DLDI。但是我不确定它能不能自动利用更新驱动来兼容各种自制软件。如果不能的话,则要事先在电脑上为自制软件打好补丁。[/ALIGN]
这款产品支持MicroSD和MicroSDHC。所以如果你想要更大容量的话,可能挑选这款产品将是你最好的选择。[/ALIGN]
[B]Cyclo DS Evolution[/B][B]烧录设备[/B][/ALIGN]
这款产品是最近才登陆市场的,但是它却吸引了不少自制软件爱好者的目光。Cyclo DS Evolution外观以及功能菜单上最为出彩。菜单皮肤也是可以自行更换的。很多人可能会在想菜单皮肤什么的不用在意,但是事实上如果你在一遍又一遍的读取自己的软件的时候,恶心的彩弹设计真的会让人很恼火。如果你无法使用触摸屏来滚动屏幕菜单而只能不停的按上和下才能实现的话,你也会很恼火。[/ALIGN]
除去华丽的外观,Cyclo DS Evolution也能很好的支持DLDI。同样提供了DLDI的自动补丁功能。[/ALIGN]
Cyclo DS Evolution中我最喜欢的一个功能就是能够自动记忆我最后运行的程序并且能够自动运行那个程序。在启动时按住L和R就可以自动启动最后一次运行过的程序。这样避免了一些无意义的重新选择、进入的操作,这在无穷无尽的debug阶段是非常非常便利的。[/ALIGN]
Cyclo DS Evolution也能够以NoPass的方式启动GBA烧录卡,GBAMoviePlayer或者是其他Slot-2槽设备。[/ALIGN]
大家可能注意到了,这些介绍当中篇幅最长的就是这款Cyclo DS Evolution了,因为这是这里介绍的三种设备当中我最为喜欢的一款。这款产品的性价比也很高。虽然它不支持GBA游戏,但是在DS软件方面的可塑性却足以为其扳回一分。如果想要购买一款Slot-1产品的话,我还是推荐Cyclo DS Evolution。[/ALIGN]
[B]如何购买Slot-1烧录设备? [/B][/ALIGN]
市面上有很多地方可以买到这些产品,其中electrobee是比较不错的一个网站。这个网站是由自制软件团体的成员开办,价格也十分平易近人。地址是: electrobee.com. [/ALIGN]
[/ALIGN]
[/ALIGN]
[B]采用Slot-2槽的设备挑选 [/B][/ALIGN]
如果你决定进行GBA软件开发的话,可能就需要Slot-2槽的产品了。NoPass或许是一个不错的选择。它允许你运行Slot-2槽设备中保存的DS软件以及GBA软件。如果只是想运行DS软件的话,则不必专门挑选Slot-2槽产品。[/ALIGN]
[/ALIGN]
GBAMoviePlayer是一款能够使用CF存储卡作为媒介的很棒的产品,CF存储卡性价比非常不错。如果你只有microSD卡的话,那么只能挑选SuperCard或者M3Adapter了。GBAMP在任天堂DS的底部也会有凸起,如[U]图示3.1. GBAMP与GBA烧录卡的比较 [/U]所示。[/ALIGN]
[B]图示 3.1. GBAMP(左)与GBA烧录卡(右)的比较[/B][/ALIGN]

由于采用GBA烧录卡,所以在读取程序速度方面会稍稍慢一些。而且每一款GBA烧录卡都对应着专门的驱动。也就是说对于linux系统或者是苹果系统的话可能就不会有专门的驱动来用了。事实上选择GBA烧录卡最大的好处就是底部不会凸起。如图示3.1. GBAMP(左)与GBA烧录卡的比较 所示。 [/ALIGN]
[B]运行多个软件[/B][/ALIGN]
如果你决定使用GBA烧录卡的话,你可以使用一款叫做“Darkain's MultiNDS Loader”的软件来下载程序到你的烧录卡当中。因此在你想运行不同的程序的时候就不必来回下载了。[/ALIGN]
如果你选择的是GBAMP的话,我强烈推荐使用“DragonMinded's DSOrganize”这款软件。它支持同时启动多个程序,文档编辑器、地址簿、日历等等,是一款功能强大的DS软件。下载地址如下:http://www.dragonminded.com/?loc=ndsdev/DSOrganize. 然而,你不能使用GBAMP运行NDS程序。要想运行NDS程序的话则要刷写一些特殊的固件。介绍和固件类型如下可以查到:http://chishm.drunkencoders.com/NDSMP/index.html. [/ALIGN]
同样,对于Slot-1设备,对于其各自的菜单系统(虽然很多人称之为操作系统),你不必进行任何的设定就可运行多个软件。[/ALIGN]

退伍者

本命年换兔子头保平安

精华
3
帖子
11433
威望
14 点
积分
12723 点
种子
9 点
注册时间
2004-2-3
最后登录
2023-12-12
 楼主| 发表于 2010-3-22 22:07  ·  北京 | 显示全部楼层
[B]章节 4. 如何编程?[/B][/ALIGN]
[B]内容目录[/B][/ALIGN]
[U]关于devkitPro开发部件[/U] [/ALIGN]
Libnds的精彩世界 [/ALIGN]
安装devkitARM开发包 [/ALIGN]
从源代码安装libnds [/ALIGN]
下一步[U][/U][/ALIGN]
[B]关于devkitPro开发部件[/B][/ALIGN]
就像Microsoft或者Adobe一样,devkitPro是一个商标品牌。devkitPro为自制软件开发者提供了一些系列工具包。其中包含了GBA、GP32、PSP、GC以及NDS主机。当然其中最广为人知的便是devkitARM。[/ALIGN]
devkitARM是devkitPro中一项特殊的功能包。其基于GCC编译器,所以它可以在任何一台计算机下编译ARM代码。devkitARM包含了所有你需要用来开发一款DS、GBA以及GP32软件的功能,运算时则需要ARM处理器来协助。然而在进行开发的时候我们还需要一些别的软件来进行辅助。[/ALIGN]
[B]Libnds[/B][B]的精彩世界[/B][/ALIGN]
libnds,即任天堂DS的图书馆(library)之意,起初其叫做NDSLIB。NDSLIB是一个由joat (Michael Noland)以及dovoto (Jason Rogers)所制作的袖珍版的资料库。在几个月之后,开发者变为 WinterMute (Dave Murphy),而其名称也随之变更为libnds。[/ALIGN]
NDSLIB最初时提供了一系列NDS主机的内存地址定义。这对于自制软件开发来说是非常有用处的,你可以用BG_BMP_RAM来表示背景图片的内存单元而不必用可读性非常差的0x06000000。之后这个资料库慢慢地开始加入指令集以及单元还有一些其他的典型架构来简化程序员的开发工作,同时解放了一些硬件上的负担。[/ALIGN]
时至今日,libnds已经成为96%的自制软件开发团队所必备的工具之一了。[/ALIGN]
[B]安装devkitARM开发包[/B][/ALIGN]
安装devkitARM开发包是非常简单的。在其官方网站上已经有明确的指导教程。点击 http://www.devkitpro.org/setup.shtml 可以找到安装教程。虽然安装的时候有很多选项,但是其实一直下一部便可以。该开发包支持Windows、苹果系统以及linux。 [/ALIGN]
[B]从源代码安装libnds [/B][/ALIGN]
libnds源代码的安装并没有像devkitPro源代码安装那样提供了文档帮助,但是相对来说还是比较简单的。在安装devkitARM时可以自动安装libnds。然而如果你想要具体查看libnds的源代码的话,那么就必须单独安装了。[/ALIGN]
[B]步骤 4.1. 从源代码安装libnds[/B][/ALIGN]

先从SourceForge.net下载最新的源代码包。
解压至 $DEVKITPRO/libnds。
[B]3.       [/B]                [email protected]:~$[B]mkdir[/B][/ALIGN]
4.        [B]                $DEVKITPRO/libnds[/B][/ALIGN]
[B]5.       [/B]                [email protected]:~$[B]mv[/B][/ALIGN]
6.        [B]                libnds-src-*.tar $DEVKITPRO/libnds/[/B][/ALIGN]
[B]7.       [/B]                [email protected]:~$[B]cd[/B][/ALIGN]
8.        [B]                $DEVKITPRO/libnds[/B][/ALIGN]
[B]9.       [/B]                [email protected]:~$[B]tar[/B][/ALIGN]
10.     [B]                -xvjf libnds-src-*.tar.bz2 $DEVKITPRO/libnds[/B][/ALIGN]
              [/ALIGN]

变更目前的目录为 $DEVKITPRO/libnds 然后输入 [B]make[/B].
[B]12.    [/B]                [email protected]:~$[B]cd[/B][/ALIGN]
13.     [B]                $DEVKITPRO/libnds[/B][/ALIGN]
14.                     [email protected]:~$[B]make[/B][/ALIGN]
              [/ALIGN]

如果安装顺利,libnds会在几秒后自动编译,之后你便可以进行NDS软件的开发工作了。
[B]下一步[/B][/ALIGN]现在你的计算机中已经安装了devkitARM 和 libnds,对于编程的软件部分已经完全准备完毕了。但是对于NDS中的特殊硬件还是需要了解一些有关知识。在下一章节,我们将着重介绍在屏幕中显示位图的方法。

退伍者

本命年换兔子头保平安

精华
3
帖子
11433
威望
14 点
积分
12723 点
种子
9 点
注册时间
2004-2-3
最后登录
2023-12-12
 楼主| 发表于 2010-3-22 22:07  ·  北京 | 显示全部楼层
[B]章节 5. 如何显示背景图片?[/B][/ALIGN]
[B]内容目录[/B][/ALIGN]
一些关于背景的资料 [/ALIGN]
2D图像引擎 [/ALIGN]
第五模式 [/ALIGN]
精细的仿射背景 [/ALIGN]
以本手册来进行编程 [/ALIGN]
硬件初始化 [/ALIGN]
配置VRAM Bank [/ALIGN]
仿射背景的建立 [/ALIGN]
定点数入门 [/ALIGN]
TDMA基础知识 [/ALIGN]
Makefile使用介绍 [/ALIGN]
[U]速成教程[/U] [/ALIGN]
放入星空 [/ALIGN]
编译[U][/U][/ALIGN]
[B]一些关于背景的资料[/B][/ALIGN]
在最开始,人们开始使用光栅来显示电子图像。实际上在此之后,光栅显示图像的方法就没有使用得那么广泛了。然而,几乎全部编程出来显示的图像都是以类似光栅显示的方法来显示的。在NDS的LCD屏幕上的每一个物体显示并非采用光栅显示法,但是在NDS屏幕上控制电子来成像的方式却与光栅显示的方法类似。[/ALIGN]
那么到底什么是光栅显示?简单来说,就是像电视机显示图像那样。从屏幕后方的显像管射出一束电子束,这束电子以确定的路线(这就是光栅扫描)打到屏幕上。以第一人称来观察电视时,这束电子是从左到右,每次扫一行,而且永远不会从右到左。在电子束到达右端边界处消失然后转而进行下一行的扫描。当电子束到达屏幕的最下方时,将再次回到屏幕的左上角周而复始地进行着扫描。[/ALIGN]
这里有两件事要着重介绍一下。第一,电子束从右侧回到左侧(不绘图时)的时间周期称作水平消隐周期,或者简写英文为hblank。第二,电子束从最低处回到上方(同样不进行绘图)的时间周期成为垂直消隐周期,或者简写英文为vblank。熟悉vblank对于我们进行DS软件编程来说是十分重要的,因为我们要在这个时间段内让DS画出图形。如果不进行特殊规定的话,图像会偏离我们预期设定的位置并且造成我们并不想要的效果。[/ALIGN]
[B]2D[/B][B]图像引擎[/B][/ALIGN]
由于任天堂NDS有两个屏幕,所以就需要分别编写两套图像引擎。第一套引擎也可以称为主引擎,第二套引擎称为副引擎。这两套引擎在使用的过程中可以相互替换。事实上在默认状态下,libnds就可以自动使主引擎应用于上屏,副引擎应用于下屏。[/ALIGN]
每一个图像引擎都可以支持4层背景、128个像素。由各自的控制寄存器、背景、像素、图形数据以及调色板来独立工作。如果要使用一个引擎的话,必须首先启动它。然后将这个引擎设置成某个工作状态模式,接下来将内存划分给引擎。最后读取图像数据和调色板所在的内存区域,DS主机便会按照我们设置的那样进行填充图像了。[/ALIGN]
[B]第五模式[/B][/ALIGN]
每一个图像引擎都有7种不同的模式来配合不同的特效。在本手册中,我们会讲解比较易用的一个图形模式,第五模式。如同我所说的那样,这个模式易用性很好,而且还可以实现一些很棒的特效。在这一章节,我们将一起学习如何显示16位色、1位α[I]仿射[/I] 背景。第五模式包含四种不同的具有各自特色性能的背景。 表格5.1 “第五模式 相关信息” 列出了第五模式的易用特性。[/ALIGN]
[B]表格5.1 第五模式 相关信息[/B][/ALIGN]
[B]背景[/B][/ALIGN]
[B]用途[/B][/ALIGN]
0[/ALIGN]
块状显示模式,有3D特效的2D显示[/ALIGN]
1[/ALIGN]
块状显示模式
2D显示[/ALIGN]
2[/ALIGN]
扩展旋转背景图[/ALIGN]
3[/ALIGN]
扩展旋转背景图[/ALIGN]

[B]精细的仿射背景[/B][/ALIGN]
仿射背景也可以当做是表面旋转背景或者exrot背景,它可以根据仿射变形矩阵来进行变化。由于本手册不着重讲解关于数学方面的知识,所以这里简单讲讲如何建立一个基本的仿射背景,至于如何实现旋转、缩放、透明等效果这里就不作说明了。本手册中的例子由始至终只讲述如何完成一种不改变背景的简单的变形。利用仿射变形矩阵的话,你可以自己试着尝试添加旋转、缩放以及透明特效。如果你想进一步了解关于特效的信息,我强烈建议你到这里来看看Cearn的关于仿射变换的指导教程。地址是:http://www.coranac.com/tonc/text/affine.htm. [/ALIGN]
[B]以本手册来进行编程[/B][/ALIGN]
在这里我们将开始讲述如何编程,当然在这之前大家最好已经准备好各种需要利用到的资源。如果还没有开始着手的话,在这里可以下载到与本手册配套提供的一些源代码等信息。地址是http://patater.com/manual。解压缩你下载到的源代码,将会得到一个名为code的文件夹。这里提供给各位一个范例模板,你大可以以此来进行NDS软件的编程工作。模板就保存于code文件夹内,名为chapter_0-starting_system。这个code文件夹内也包含了每一个章节的工程文件夹。如果你在阅读某个章节遇到困难的时候可以到这里来找找相关信息,或者是想要利用已经完成的工程文件来进行自己的编程也都是可以的。跟随本手册,将chapter_0-starting_system文件夹拷贝至任意目录下,用任意文档编辑器打开source/main.cpp,让我们马上开始工作吧![/ALIGN]
[B]硬件初始化[/B][/ALIGN]
为了让硬件能够更好地为我所用,我们首先必须要进行硬件初始化。也就是说要让硬件开启2D图形核心。这是我们需要用到libnds的其中一处(在接下来的内容中我们将不断地利用libnds)。libnds将这一步骤简化到只需要做两件事便可以完成这项工作。将以下代码粘贴至你新建的main.cpp C++程序文件中。[/ALIGN]
#include [/ALIGN]
[/ALIGN]
int main() {[/ALIGN]
   /*  Turn on the 2D graphics core. */[/ALIGN]
   powerOn(POWER_ALL_2D);[/ALIGN]
[/ALIGN]
   return 0;[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]配置VRAM Bank [/B][/ALIGN]
接下来我们要进行一下简单的设置工作,我们得告诉图形引擎要从哪里得到需要显示的图像数据。两个图像引擎共享一个VRAM显存,也就是说并没有两个同样的VRAM 显存bank,一个为上屏工作一个为下屏工作,仅仅只有一个共用的VRAM bank。过一会儿在读取图像的时候将利用到这些内存地址。首相让我们来创建一个名为 initVideo的函数。[/ALIGN]
void initVideo() {[/ALIGN]
   /*[/ALIGN]
    *  定义VRAM在上屏和下屏显示背景图片。[/ALIGN]
    * [/ALIGN]
    *  vramSetMainBanks 函数带有四个定义,[/ALIGN]
    *  每一个分别对应一个主VRAM bank。[/ALIGN]
    *  我们可以用它来快速分配每一个VRAM bank控制寄存器的值。    *[/ALIGN]
    *  我们为上屏背景内存将bank划分 A 和 B 两部分 。[/ALIGN]
    *  总共有256KB大小,对于16位图像来说完全够用了。[/ALIGN]
    *[/ALIGN]
    *  定义bank C 为下屏背景内存。[/ALIGN]
    *[/ALIGN]
    *  定义bank D为LCD。[/ALIGN]
    *  这么做是因为有时我们可能需要一个临时空间。[/ALIGN]
    */[/ALIGN]
   vramSetMainBanks(VRAM_A_MAIN_BG_0x06000000,[/ALIGN]
              VRAM_B_MAIN_BG_0x06020000,[/ALIGN]
              VRAM_C_SUB_BG_0x06200000,[/ALIGN]
              VRAM_D_LCD);[/ALIGN]
[/ALIGN]
   /*  设置上屏的视频模式 */[/ALIGN]
   videoSetMode(MODE_5_2D | // 设置图形模式为第五模式[/ALIGN]
            DISPLAY_BG2_ACTIVE | // 开启BG2[/ALIGN]
            DISPLAY_BG3_ACTIVE); // 开启BG3[/ALIGN]
[/ALIGN]
   /*  设置下屏的视频模式*/[/ALIGN]
   videoSetModeSub(MODE_5_2D | //设置图形模式为第五模式[/ALIGN]
              DISPLAY_BG3_ACTIVE); // 开启BG3[/ALIGN]
}[/ALIGN]
        [/ALIGN]
DS当中的VRAM bank总共被划分为9个区域。详细请查看 表格5.2“VRAM Bank信息”。 16位的背景图片最多可以占用每一内存单元各128KB。因此每一幅背景图都需要整个VRAM bank来声明。然而并非全部的VRAM bank都可以为我们所利用。在附录A当中,详细备注了其中的细节信息。[/ALIGN]
[B]表格5.2 VRAM bank 信息[/B][/ALIGN]
[B]VRAM Bank[/B][/ALIGN]
[B]控制寄存器地址[/B][/ALIGN]
[B]控制寄存器[/B][/ALIGN]
[B]VRAM Bank [/B][B]容量[/B][/ALIGN]
VRAM_A[/ALIGN]
0x04000240[/ALIGN]
VRAM_A_CR[/ALIGN]
128KB[/ALIGN]
VRAM_B[/ALIGN]
0x04000241[/ALIGN]
VRAM_B_CR[/ALIGN]
128KB[/ALIGN]
VRAM_C[/ALIGN]
0x04000242[/ALIGN]
VRAM_C_CR[/ALIGN]
128KB[/ALIGN]
VRAM_D[/ALIGN]
0x04000243[/ALIGN]
VRAM_D_CR[/ALIGN]
128KB[/ALIGN]
VRAM_E[/ALIGN]
0x04000244[/ALIGN]
VRAM_E_CR[/ALIGN]
64KB[/ALIGN]
VRAM_F[/ALIGN]
0x04000245[/ALIGN]
VRAM_F_CR[/ALIGN]
16KB[/ALIGN]
VRAM_G[/ALIGN]
0x04000246[/ALIGN]
VRAM_G_CR[/ALIGN]
16KB[/ALIGN]
VRAM_H[/ALIGN]
0x04000248[/ALIGN]
VRAM_H_CR[/ALIGN]
32KB[/ALIGN]
VRAM_I[/ALIGN]
0x04000249[/ALIGN]
VRAM_I_CR[/ALIGN]
16KB[/ALIGN]

[B]仿射背景的建立 [/B][/ALIGN]
在这里我们还是要利用libnds所提供的API接口,利用放射变形矩阵实现一个特定的仿射背景。libnds提供了四种不同的方法。[/ALIGN]
我们所需要做的就是添加三个背景进去。在上屏中首先添加一幅带有标题的图片,在下图加入星空的背景图,然后再将一幅飞船的图片放在星空背景图的上面。利用SUB_BG3(同样也可以使用SUB_BG2)来作为标题图片,背景2和背景3要在上屏显示星球和星空。为了能够实现星球在星空背景之上,我们要将星球的优先级设置为高于星空的优先级。相对较低优先级的图片会在较高优先级的图片下方。每一个图像引擎都只包含4种优先级数字设置(从0到3,依次降低)。[/ALIGN]
现在要使用API接口提供给我们的背景控制寄存器以及仿射变形矩阵了。这里我们建立一个名为initBackgrounds的函数,用它来设定仿射背景图。注释中将解释每一条语句的作用。[/ALIGN]
void initBackgrounds() {[/ALIGN]
   /*  设置仿射背景3为16位色、放置于上屏*/[/ALIGN]
   REG_BG3CNT = BG_BMP16_256x256 |[/ALIGN]
            BG_BMP_BASE(0) | //内存起始地址[/ALIGN]
            BG_PRIORITY(3); //低优先级[/ALIGN]
[/ALIGN]
   /* 为上屏背景3设置放射变化矩阵 */[/ALIGN]
   REG_BG3PA = 1 [/ALIGN]
   REG_BG3PB = 0;[/ALIGN]
   REG_BG3PC = 0;[/ALIGN]
   REG_BG3PD = 1 [/ALIGN]
[/ALIGN]
   /*  设置上屏中背景3的起始位置(屏幕左上角) */[/ALIGN]
   REG_BG3X = 0;[/ALIGN]
   REG_BG3Y = 0;[/ALIGN]
[/ALIGN]
   /*  设置仿射背景2为16位色,放置于上屏*/[/ALIGN]
   REG_BG2CNT = BG_BMP16_128x128 |[/ALIGN]
            BG_BMP_BASE(8) | // 内存起始地址[/ALIGN]
            BG_PRIORITY(2);  // 高优先级[/ALIGN]
[/ALIGN]
   /*  为上屏背景2设置放射变化矩阵*/[/ALIGN]
   REG_BG2PA = 1 [/ALIGN]
   REG_BG2PB = 0;[/ALIGN]
   REG_BG2PC = 0;[/ALIGN]
   REG_BG2PD = 1 [/ALIGN]
[/ALIGN]
   /*  设置上屏中的背景2的位置 */[/ALIGN]
   REG_BG2X = -(SCREEN_WIDTH / 2 - 32) [/ALIGN]
   REG_BG2Y = -32 [/ALIGN]
[/ALIGN]
   /*  设置仿射背景3为16位色,放置于下屏*/[/ALIGN]
   REG_BG3CNT_SUB = BG_BMP16_256x256 |[/ALIGN]
              BG_BMP_BASE(0) | // 内存起始地址[/ALIGN]
              BG_PRIORITY(3); // 低优先级[/ALIGN]
[/ALIGN]
   /*  为下屏背景3设置放射变化矩阵*/[/ALIGN]
   REG_BG3PA_SUB = 1 [/ALIGN]
   REG_BG3PB_SUB = 0;[/ALIGN]
   REG_BG3PC_SUB = 0;[/ALIGN]
   REG_BG3PD_SUB = 1 [/ALIGN]
[/ALIGN]
   /*[/ALIGN]
    *  设置下屏中背景3的起始位置(屏幕左上角)[/ALIGN]
    */[/ALIGN]
   REG_BG3X_SUB = 0;[/ALIGN]
   REG_BG3Y_SUB = 0;[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]定点数入门[/B][/ALIGN]
肯定有读者会问这些“”都是什么东西,这就是所谓的定点数。任天堂DS中将数字定义为不同的定点数。定点数是以一种特殊的方式来定义非整数的数值。举个例子,如果我们要使用一个整数变量来表示所拥有的人民币,那么就只能代表整数部分的钱数。然而当遇到角、分的时候,又该怎么做呢?一般都是要将带有小数点的单位缩小以整数放置,比如我有1元2角1分,那么就必须要换成121分。[/ALIGN]
在定点数中的带有小数点的数值表示时都是像1.31、1.724、8.8这样。读数的顺序为从由至左。首先我们找到小数部分,然后找到整数部分。如果左边还有数字的话,如果还有一位的话,通常是用来表示整数或者是负数。[/ALIGN]
[/ALIGN]
[B]DMA[/B][B]基础知识 [/B][/ALIGN]
DMA允许CPU独立地读或者写内存中的内容。任天堂DS主机内自带了一个特别的专用DMA硬件来快速地有效地读写内存。然而在内存填充操作当中DMA并非是最佳选择,但是在每写进一个数时都要读一次这个数的时候则非常有用了。libnds提供了任天堂DS主机中DMA控制器的几项功能。[/ALIGN]
如果能够使用DMA的话,还是尽量选择使用吧。使用DMA总比使用一个for循环来读数要快上很多很多。在使用DMA往主内存当中拷贝数据的时候不要忘记首先清除主内存中的内容。在相应内存单元临时互相拷贝的内容的时候,DMA并不使用缓存。另外一个值得考虑的问题是在DMA过程中,CPU不能够访问内存。这也许会在程序运行过程中导致一些不可控制的bug出现。出于这个原因考虑,使用swifastcopy函数可能更安全一些,但是速度上稍慢。如果你真的遇到了什么无法解决的bug的话,最安全的方式明显就是利用memcopy和memset函数。[/ALIGN]
libnds中dmaCopyHalfWords函数的声明如下:[/ALIGN]
static inline void [B]dmaCopyHalfWords[/B]([/ALIGN]
[I]uint8[/I][I] channelconst void * sourcevoid * destuint32 size[/I]);[/ALIGN]
[/ALIGN]

[/ALIGN]
[I]uint8 channel[/I] [I]const void * source[/I] [I]void * dest[/I]  [/ALIGN]
[I]uint8 channelconst void * sourcevoid * destuint32 size[/I] [I]const void * source[/I] [I]void * dest[/I] [I]uint32 size[/I] ;[/ALIGN]

在我们的程序当中,将利用dmaCopyHalfWords函数来将图像读入内存当中。使用dmaCopyHalfWords函数而不是使用dmaCopy函数的原因是在拷贝时更加明确直顺。但是我们也可以使用相同的通道(通道3)来负责普通的dmaCopy函数应用,也可以具体指定在DMA传输时使用的通道。那么,现在就让我们开始编写显示背景的代码吧。由于我们已经决定好了要显示的方式,接下来就是找到合适的图片放进去就可以了。如果不首先从背景入手的话,将来想要往已经填入大量内容之后的寄存器入加入背景将会变得十分麻烦。[/ALIGN]
/*选择一个低优先级的DMA通道来拷贝我们的背景图片*/[/ALIGN]
static const int DMA_CHANNEL = 3;[/ALIGN]
[/ALIGN]
void displayStarField() {[/ALIGN]
   dmaCopyHalfWords(DMA_CHANNEL,[/ALIGN]
              starFieldBitmap, /* 我们要呈现的星空背景 */[/ALIGN]
              (uint16 *)BG_BMP_RAM(0), /* 背景3的内存地址*/[/ALIGN]
              starFieldBitmapLen); /* 星空背景图像的大小(bytes) */[/ALIGN]
}[/ALIGN]
[/ALIGN]
void displayPlanet() {[/ALIGN]
   dmaCopyHalfWords(DMA_CHANNEL,[/ALIGN]
              planetBitmap, /*我们要呈现的星球背景*/[/ALIGN]
              (uint16 *)BG_BMP_RAM(8), /* 背景2的地址 */[/ALIGN]
              planetBitmapLen); /* 星球背景图像的大小(bytes)*/[/ALIGN]
}[/ALIGN]
[/ALIGN]
void displaySplash() {[/ALIGN]
   dmaCopyHalfWords(DMA_CHANNEL,[/ALIGN]
              splashBitmap, /* 标题图片 */[/ALIGN]
              (uint16 *)BG_BMP_RAM_SUB(0), /* 背景3的地址*/[/ALIGN]
              splashBitmapLen); /* 标题图像的大小(bytes) */[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]Makefile[/B][B]使用介绍 [/B][/ALIGN]
默认的makefile范例可以把你的图像文件转换成可链接进程序的工程文件。但是千万不要将图像数据做成头文件。[/ALIGN]
要显示的图像必须是无损图像格式,比如gif、tif、bmp或者pnp等,只有这些文件才能够被正确识别。我个人推荐使用png格式。如果想要转换图像格式的话,可以使用grit这个程序。makefile会启动grit来转换保存于你的工程目录下gfx文件夹内的图片,使之成为NDS可以识别的图片格式。[/ALIGN]
我们所提供的makefil与libnds默认的makefile相匹配,可以很好地应用于各种工程。它可以自动找到你放置于在gfx文件夹内的图片(makefile文件也在同目录下)。找到图片之后,会使用bin2o来启动grit根据grit的规则文件将你的图片转变为.o的文件,这样便可以链接进程序当中。grit还将为你的数据生成一个头文件(.h格式)。具体的过程为:如果你有一张名为orangeShuttle.png的图片,那么头文件也会采用相同的名称,只不过后缀变为.h即orangeShuttle.h。在头文件当中,会自动关联相关的.o文件,具体的名称是叫做orangeShuttleTiles 还是orangeShuttlePal 又或者 orangeShuttleBitmap是根据grit生成文件时所读入的图片格式而定。同时这里面也包含了代表图片长度的 orangeShuttleTilesLen 或者 orangeShuttlePalLen 又或者orangeShuttleBitmapLen。[/ALIGN]
在我们的这个工程当中,就是直接将想要生成的图片以及grit规则文件放入gfx目录下,使makefile自动启动grit来转换格式。[/ALIGN]
[/ALIGN]
[/ALIGN]
[B]速成教程 [/B][/ALIGN]
在这些非常棒的多平台开发工具当中,grit是绝对有必要好好研究一下的。现在它已经成为了NDS以及GBA软件开发中所不可或缺的图片转换方式。[/ALIGN]
为了使用grit,就要创建grit规则文件。这个文件是以.grit后缀结尾,它包含的信息会告知grit如何转换我们想要呈现的图像。想要了解更多的话可以键入grit并运行然后查看内置的帮助文档。在本手册接下来的部分当中我也会在所提供的grit规则文件中加入注释。希望通过这些,大家能够很快的上手并掌握grit。[/ALIGN]
想要了解更多关于grit的信息,可以访问这个工程页面来阅读grit手册:http://www.coranac.com/projects/grit/ 以及http://www.coranac.com/man/grit/html/grit.htm 你也可以在这两个网站下载到最新的grit。[/ALIGN]
[B]放入星空[/B][/ALIGN]
那么现在就让我们把所有的函数都放入主程序当中吧。[/ALIGN]
[/ALIGN]
#include [/ALIGN]
#include "starField.h"[/ALIGN]
#include "planet.h"[/ALIGN]
#include "splash.h"[/ALIGN]
[/ALIGN]
/*我们所建立的其他函数放在这里。*/[/ALIGN]
[/ALIGN]
int main() {[/ALIGN]
   /*  启动2D图形核心*/[/ALIGN]
   powerOn(POWER_ALL_2D);[/ALIGN]
[/ALIGN]
   /*  设定VRAM和背景控制寄存器*/[/ALIGN]
   lcdMainOnBottom(); //设定下屏为主屏幕[/ALIGN]
   initVideo(); [/ALIGN]
   initBackgrounds(); [/ALIGN]
[/ALIGN]
   /*背景的显示*/[/ALIGN]
   displayStarField(); [/ALIGN]
   displayPlanet();[/ALIGN]
   displaySplash();[/ALIGN]
[/ALIGN]
   return 0;[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]编译[/B][/ALIGN]
再次按照提供的范例检查你的代码是否正确。确定你的图形文件和grit规则文档放入了工程文件夹内的gfx目录下。进入命令行模式,然后进入到包含makefile文件的你的工程文件夹内。输入make,顺利的话你将会看到如同图示5.4, “程序正常运行时的样子”中所表现的效果。至于如何拷贝至你的NDS则要看你选择哪种方式了。[/ALIGN]
[B]图示5.4, 程序正常运行时的样子[/B][/ALIGN]

退伍者

本命年换兔子头保平安

精华
3
帖子
11433
威望
14 点
积分
12723 点
种子
9 点
注册时间
2004-2-3
最后登录
2023-12-12
 楼主| 发表于 2010-3-22 22:09  ·  北京 | 显示全部楼层
[B]章节 6. 精灵图是什么?如何使用?[/B][/ALIGN]
[B]内容目录[/B][/ALIGN]
何为精灵图像? [/COLOR] [/ALIGN]
OAM介绍[/COLOR] [/ALIGN]
精灵图像显示的硬件之信息 [/COLOR] [/ALIGN]
精灵图在内存中的存储方式[/COLOR] [/ALIGN]
精灵图像的属性[/COLOR] [/ALIGN]
更新OAM[/COLOR] [/ALIGN]
OAM初始化[/COLOR] [/ALIGN]
旋转精灵图 [/COLOR] [/ALIGN]
显示隐藏的精灵图[/COLOR] [/ALIGN]
移动精灵图[/COLOR] [/ALIGN]
设定精灵图的优先级[/COLOR] [/ALIGN]
精灵图的使用[/COLOR] [/ALIGN]
为精灵图像设置VRAM显存 [/COLOR] [/ALIGN]
精灵图像的寻址方式 [/COLOR] [/ALIGN]
精灵图像的读入 [/COLOR] [/ALIGN]
断言是什么?[/COLOR] [/ALIGN]
精灵图的显示[/COLOR] [/ALIGN]
编译[/COLOR][U][/U][/ALIGN]
[B]何为精灵图像?[/B][/ALIGN]
精灵图像并不是什么多么神奇的东西。它是由程序生成的一系列2D图像的动画或者单幅图片。任天堂DS主机有专门的精灵图像处理硬件。这对于2D图像的显示非常有帮助。很多游戏主机没有2D核心,所有的精灵图以及其他2D图像都必须通过3D的方式绘制很多2D方块图来实现。[/ALIGN]
[B]OAM[/B][B]介绍[/B][/ALIGN]
精灵图是由OAM来进行管理的。管理精灵图是一项十分巨大的工程,大部分的工作都无须我们来进行。想想看可能觉得它很神奇但实际并非如此。OAM全称为对象属性存储器(Object Attribute Memory),是内存当中我们用于控制精灵图变化的内存空间。OAM配合SpriteEntry与SpriteRotation来管理精灵图的属性。[/ALIGN]
[B]精灵图像显示的硬件之信息[/B][/ALIGN]
对于任天堂DS主机,我们有总共128个精灵图可以使用。其中只有32个精灵图可以进行仿射变化(旋转、缩放、歪斜等等)。我们在每一个引擎中使用1024个不同地址的块状图来组成精灵图。可以使用块状图来呈现16色或者一组256色的精灵图。使用256色的块状图组成的精灵图是仅使用16色的精灵图的体积的2倍。另一个使用16色精灵图的优势就是可以使用16种不同的色调。当使用256色的精灵图时,每一个精灵图(包括块状图)都必须使用相同的色调。而当使用16色的精灵图时,可以采用不同的色调来进行配图,即使无论是256色还是16色都要使用到相同的块状图数据。游戏当中经常使用这一技巧来表现外形一样但是颜色不同的敌人,虽然块状图一样,但是色调并不尽相同。[/ALIGN]
[/ALIGN]
[B]精灵图在内存中的存储方式[/B][/ALIGN]
精灵图的分辨率为***像素,这就是之前提到的块状图。当要在屏幕中显示的时候,主机首先会把这些块状图像拼图一样无缝地拼在一起。在给精灵图分块的时候可以有两种方式,分别是1D和2D。在2D方式下,精灵图的内存是将每一块块状图最后拼成一个大的图像的形式来存储的。而在1D方式下,精灵图是以线性方式,在[U]图表6.1中可以看到内存排列方式。[/U][/ALIGN]
转换过程和背景图的转换过程极为相似。我们做了几个grit的规则文件来规定grit的转换方式,最后将生成一个.o以及一个头文件。grit规则文件和图像文件也在gfx的文件夹下。[/ALIGN]
[B]图表6.1 下面的文档显示了块状图显示方式的内存数据情况。 [/B][/ALIGN]
const u16 data[] = {[/ALIGN]
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,[/ALIGN]
0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F,[/ALIGN]
0x2020, 0x2020, 0x2020, 0x2020, 0x2020, 0x2020, 0x2020, 0x2020,[/ALIGN]
0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F,[/ALIGN]
0x4040, 0x4040, 0x4040, 0x4040, 0x4040, 0x4040, 0x4040, 0x4040,[/ALIGN]
0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F,[/ALIGN]
0x6060, 0x6060, 0x6060, 0x6060, 0x6060, 0x6060, 0x6060, 0x6060,[/ALIGN]
0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F,[/ALIGN]
0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080,[/ALIGN]
0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F,[/ALIGN]
0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0,[/ALIGN]
0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF,[/ALIGN]
0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0,[/ALIGN]
0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF,[/ALIGN]
0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0,[/ALIGN]
0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF};[/ALIGN]
const u16 data[] = {[/ALIGN]
0x0000, 0x0000, 0x0000, 0x0000, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F,[/ALIGN]
0x2020, 0x2020, 0x2020, 0x2020, 0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F,[/ALIGN]
0x4040, 0x4040, 0x4040, 0x4040, 0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F,[/ALIGN]
0x6060, 0x6060, 0x6060, 0x6060, 0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F,[/ALIGN]
0x0000, 0x0000, 0x0000, 0x0000, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F,[/ALIGN]
0x2020, 0x2020, 0x2020, 0x2020, 0x2F2F, 0x2F2F, 0x2F2F, 0x2F2F,[/ALIGN]
0x4040, 0x4040, 0x4040, 0x4040, 0x4F4F, 0x4F4F, 0x4F4F, 0x4F4F,[/ALIGN]
0x6060, 0x6060, 0x6060, 0x6060, 0x6F6F, 0x6F6F, 0x6F6F, 0x6F6F,[/ALIGN]
0x8080, 0x8080, 0x8080, 0x8080, 0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F,[/ALIGN]
0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0, 0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF,[/ALIGN]
0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF,[/ALIGN]
0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0, 0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF,[/ALIGN]
0x8080, 0x8080, 0x8080, 0x8080, 0x8F8F, 0x8F8F, 0x8F8F, 0x8F8F,[/ALIGN]
0xA0A0, 0xA0A0, 0xA0A0, 0xA0A0, 0xAFAF, 0xAFAF, 0xAFAF, 0xAFAF,[/ALIGN]
0xC0C0, 0xC0C0, 0xC0C0, 0xC0C0, 0xCFCF, 0xCFCF, 0xCFCF, 0xCFCF,[/ALIGN]
0xE0E0, 0xE0E0, 0xE0E0, 0xE0E0, 0xEFEF, 0xEFEF, 0xEFEF, 0xEFEF};[/ALIGN]
           [/ALIGN]
[B]精灵图像的属性[/B][/ALIGN]
精灵图有三种不同的属性设置。根据设置的不同,像素图可以进行旋转、翻转、马赛克风格以及各种有趣的效果。每一种属性都适用于很多种形式,但是需要设置一系列的位操作才能够实现我们想要实现的效果。因此也就不受libnds提供给我们的SpriteEntry单元的限制了。在多数情况下,我们要用到这个单元来实现我们想要的精灵图,但是由于现在有了这样几个特殊的设置,也就需要一些改动了。[/ALIGN]
接下来我们会讲到如何使用位操作来更新、初始化、旋转,libnds提供了的一些功能比如可以实现精灵图移动什么的。那么就让我们在这样的情况下来写几个函数试试看吧。[/ALIGN]
在文档中提供了以个头文件,目录为code/chapter_6-sprites/include/sprites.h。其中包含了精灵图使用时涉及到的一些函数声明。下面我们将在一个新的工程文件sprites.cpp中使用着每一个函数来实现精灵图。首先要新建一个源代码文件,名称修改为sprites.cpp,然后放进你的源文件目录下。[/ALIGN]
[B]更新OAM [/B][/ALIGN]
不用直接在OAM上进行修改,我们要复制OAM然后使用updateOAM来拷贝内存中每一帧的内容。这么做的原因是OAM本身除了vblank期间以外都是只读的。在拷贝OAM的备份进入到原本的OAM之前,需要经过vblank。[/ALIGN]
更新OAM非常直接。只要把我们的OAM备份拷贝到真正的OAM即可。OAM备份有可能会发生卡死在缓存中无法操作的情况,这时我们需要首先刷新本地内存(需要DMA操作)来确保DMA识别到的是正确的数据。在这之后如果还是卡死的话,就要告诉OAM立即进行查找OAM表然后创建一个关于每一份精灵图的新信息。[/ALIGN]
void updateOAM(OAMTable * oam) {[/ALIGN]
   DC_FlushAll();[/ALIGN]
   dmaCopyHalfWords(SPRITE_DMA_CHANNEL,[/ALIGN]
              oam->oamBuffer,[/ALIGN]
              OAM,[/ALIGN]
              SPRITE_COUNT * sizeof(SpriteEntry));[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]OAM [/B][B]初始化[/B][/ALIGN]
在进行OAM初始化时首先要做的就是清除OAM当中所有的精灵图信息。之后,要调用之前写的updateOAM函数。[/ALIGN]
void iniOAMTable(OAMTable * oam) {[/ALIGN]
   /*对DS当中全部128个精灵图进行删除,清除已有属性。防止显示时出现遗留下来的数据,并提供一个干净的工作内存区域。*/[/ALIGN]
   for (int i = 0; i [/ALIGN]
      oam->oamBuffer.attribute[0] = ATTR0_DISABLED;[/ALIGN]
      oam->oamBuffer.attribute[1] = 0;[/ALIGN]
      oam->oamBuffer.attribute[2] = 0;[/ALIGN]
   }[/ALIGN]
   for (int i = 0; i [/ALIGN]
      /*如果你认真观察一下,可以看到这就是之前用过的仿射变换矩阵。如同在背景图中使用时的那样,将其定义为确切的矩阵。*/[/ALIGN]
      oam->matrixBuffer.hdx = 1 [/ALIGN]
      oam->matrixBuffer.hdy = 0;[/ALIGN]
      oam->matrixBuffer.vdx = 0;[/ALIGN]
      oam->matrixBuffer.vdy = 1 [/ALIGN]
   }[/ALIGN]
   updateOAM(oam);[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]旋转精灵图[/B][/ALIGN]
现在来试着进行精灵图的旋转操作吧。这相比我们之前做过的东西会有一点难,但是不要怕,还是很有趣的。首先不用针对作出每一个精灵图旋转时的位置,这一点是非常好的。但是要调试出真正想要实现的效果的话,要做出一系列必要的调试工作。[/ALIGN]
libnds中的sin和cos函数是利用查找表来实现的,总共可以实现32768度。人们通常使用360度来描述度数或者弧度。在这里我们要使用的角度也是这32768中的其中一部分,所以不得不将我们习惯的360度制转换成32768度制。 [/ALIGN]
需要根据之前用到的仿射变化矩阵来制作一个变换方式。精灵图的仿射变化矩阵和背景图仿射变化矩阵有些许的不同。如果你有线性代数的基础的话,我建议你读一读下面的硬件信息:[U]http://www.coranac.com/tonc/text/affine.htm[/COLOR][/U]. [/ALIGN]
void rotateSprite(SpriteRotation * spriteRotation, int angle) {[/ALIGN]
   s16 s = sinLerp(angle) >> 4;[/ALIGN]
   s16 c = cosLerp(angle) >> 4;[/ALIGN]
   [/ALIGN]
   spriteRotation->hdx = c;[/ALIGN]
   spriteRotation->hdy = s;[/ALIGN]
   spriteRotation->vdx = -s;[/ALIGN]
   spriteRotation->vdy = c;[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]显示隐藏的精灵图 [/B][/ALIGN]
直到现在,我们还没有使用过这个libnds中神奇的SpriteEntry单元。在很多案例中我们都可以利用它来替我们实现位操作。在显示一张隐藏的精灵图时,由于NDS主机的特异性,我们还是需要自己来进行位操作:如果是仿射的隐藏精灵图,那么它的位数(位9的精灵图、属性0)是普通精灵图(位8的精灵图、属性0)位数的2倍。一步一步跟着下面的操作来走,我们将建立一个函数来实现显示各种隐藏精灵图。[/ALIGN]
void setSpriteVisibility(SpriteEntry * spriteEntry, bool hidden, bool affine,[/ALIGN]
                 bool doubleBound) {[/ALIGN]
   if (hidden) {[/ALIGN]
      /*[/ALIGN]
      *使精灵图精灵图不可视。[/ALIGN]
      *仿***灵图无法隐藏。我们必须将其转换为非仿***灵图。并且要设置位8清除位9。由于这个位是多余的,所以最好节省这一位以提高操作速度。[/ALIGN]
      */[/ALIGN]
      spriteEntry->isRotateScale = false; //关闭位9[/ALIGN]
      spriteEntry->isHidden = true; // 开启位8[/ALIGN]
   } else {[/ALIGN]
      /*使精灵图可见*/[/ALIGN]
      if (affine) {[/ALIGN]
        /*再一次的,要记住仿***灵图不能被隐藏,这是为了保证能够显示出完整的仿***灵图。我们还需要允许精灵图属性中的2倍边界标识符可读。如果不这么做,精灵图隐藏函数就不能很好的达成效果以及无法完整地回复2倍大小的精灵图。[/ALIGN]
         *由于要显示仿***灵图,所以这里要开启位9。[/ALIGN]
         */[/ALIGN]
        spriteEntry->isRotateScale = true;[/ALIGN]
[/ALIGN]
        /*双倍边界标识符只有在精灵图是仿***灵图的时候才有效。在其他用途中,它将作为精灵图可见标识符来使用。这里我们需要一个双倍边界大小的精灵图,所以要开启位8。*/[/ALIGN]
        spriteEntry->isSizeDouble = doubleBound;[/ALIGN]
      } else {[/ALIGN]
        /*这时位9(仿射标识符)已经准备好,所以我们不需要进行清除。然而位8(精灵图可见标识符)需要清除。*/[/ALIGN]
        spriteEntry->isHidden = false;[/ALIGN]
      }[/ALIGN]
   }[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]移动精灵图[/B][/ALIGN]
接下来要进行的工作才是最有意思的部分。在硬件中移动精灵图不用担心速度、缓冲什么的,一切都很简单。要移动精灵图,我们只需要改变SpriteEntry的X和Y。这里我们不需要过多地考虑基本的资源安排。libnds的SpriteEntry单元已经提供了编译器给我们,并且给出了图像在进行数据操作时最好的编译方法;编译器已经替我们完成了位操作。如此简单,以至于我们甚至不需要来写任何的函数。只需要记住以下几点;如果一定想要自己编写一个函数来进行操作的话,那么我推荐各位最好使用内联函数。[/ALIGN]
/*移动精灵图的语句*/[/ALIGN]
spriteEntry->x = 0;[/ALIGN]
spriteEntry->y = 0;[/ALIGN]
        [/ALIGN]
[B]设置精灵图的优先级[/B][/ALIGN]
当处理多层精灵图的时候这项工作就是我们必须要做的了。下面我们将讨论一下如何设置精灵图的优先级。[/ALIGN]
比如一幅作为背景图的精灵图,需要优先级来决定是覆盖在其他精灵图上面还是在下面。与背景图优先级相同的精灵图会显示在其上方。低优先级数字的精灵图会在其他精灵图的上方(数字越低优先级越高)。每个图像引擎都有四个优先级可供选择,与背景图的优先级类似。[/ALIGN]
要设置精灵图优先级,我们只需要适当地设置SpriteEntry中的4个优先级数字即可,具体为多少取决于我们想要的效果:OBJPRIORITY_0, OBJPRIORITY_1, OBJPRIORITY_2, or OBJPRIORITY_3. 下面的代码显示出一个具体设置优先级时的语句。[/ALIGN]
spriteEntry->priority = OBJPRIORITY_3;[/ALIGN]
        [/ALIGN]
[B]精灵图的使用[/B][/ALIGN]
既然我们的sprites.cpp文件已经完成了,那么就让我们开始学习下精灵图在内存中的存储、读取之类的吧。把sprites.cpp文件放入源文件目录,打开main.cpp文件。[/ALIGN]
[B]为精灵图设置VRAM显存[/B][/ALIGN]
我们需要在VRAM中开辟一片空间来存放我们的精灵图数据。因为我们要在主图像引擎中使用这些精灵图,所以我们就可以使用VRAM bank E来存放精灵图。VRAM bank E 要比其他VRAM bank都小一些,只有64KB存储空间。然而,用来存储1024个独立的16色块状图来说是完全足够的。[/ALIGN]
在initVideo中,我们需要划分VRAM bank E来用于精灵图的操作。然后我们需要告知主引擎激活我们需要的块状图。在这里我们要使用1D的块状精灵图。修改后的initVideo函数在下面给出。[/ALIGN]
void initVideo() {[/ALIGN]
   /*[/ALIGN]
    *  定义VRAM在上屏和下屏显示背景图片。[/ALIGN]
    * [/ALIGN]
    *  vramSetMainBanks 函数带有四个定义,[/ALIGN]
    *  每一个分别对应一个主VRAM bank。[/ALIGN]
    *  我们可以用它来快速分配每一个VRAM bank控制寄存器的值。    *[/ALIGN]
    *  我们为上屏背景内存将bank划分 A 和 B 两部分 。[/ALIGN]
    *  总共有256KB大小,对于16位图像来说完全够用了。[/ALIGN]
    *[/ALIGN]
    *  定义bank C 为下屏背景内存。[/ALIGN]
    *[/ALIGN]
    *  定义bank D为LCD。[/ALIGN]
    *  这么做是因为有时我们可能需要一个临时空间。[/ALIGN]
    *[/ALIGN]
    *  划分bank E 作为主屏上的精灵图内存,也就是对象内存。    */[/ALIGN]
   vramSetMainBanks(VRAM_A_MAIN_BG_0x06000000,[/ALIGN]
              VRAM_B_MAIN_BG_0x06020000,[/ALIGN]
              VRAM_C_SUB_BG_0x06200000,[/ALIGN]
              VRAM_D_LCD);[/ALIGN]
[/ALIGN]
   vramSetBankE(VRAM_E_MAIN_SPRITE);[/ALIGN]
[/ALIGN]
   /*  设置上屏的视频模式  */[/ALIGN]
   videoSetMode(MODE_5_2D | // 设置图形模式为第五模式[/ALIGN]
            DISPLAY_BG2_ACTIVE | // 开启BG2[/ALIGN]
            DISPLAY_BG3_ACTIVE | // 开启BG3[/ALIGN]
            DISPLAY_SPR_ACTIVE | // Enable sprites for display[/ALIGN]
            DISPLAY_SPR_1D     // Enable 1D tiled sprites[/ALIGN]
            );[/ALIGN]
[/ALIGN]
   /*  设置下屏的视频模式 */[/ALIGN]
   videoSetModeSub(MODE_5_2D | // 设置图形模式为第五模式[/ALIGN]
              DISPLAY_BG3_ACTIVE); // 开启BG3[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]精灵图像的寻址方式 [/B][/ALIGN]
如同GBA编程当中一样,我们将使用同样的内存排列(范围)。块状图VRAM地址必须以32byte来进行排列。如果你觉得这个内存空间太小也是没有办法的,因为当使用256色块状图的时候你不能使用全部1024个块状图地址。你可以在这里查看一下如何使用其他排列方式。http://nocash.emubase.de/gbatek.htm#dsvideoobjs[/COLOR]. 之后需要设置REG_DISPCNT (通过 videoSetMode) 一个在 libnds/include/nds/arm9/video.h 中与DISPLAY_SPR_1D_SIZE_XXX (默认的GBA编程方式中所使用的值DISPLAY_SPR_1D_SIZE_32)相似的值. [/ALIGN]
计算出地址,然后把块状图传输进去,这里我们只需要搞懂两件事:在内存排列中,我们想要分配数据的块状图编号。使用Martin Korth的GBATEK中的"TileVramAddress = TileNumber * BoundaryValue"以及libnds的SPRITE_GFX定义我们可以计算出任何块状图的地址。[/ALIGN]
static const int BOUNDARY_VALUE = 32; /*这是默认的范围值[/ALIGN]
                          * (可以在 REG_DISPCNT中设置) */[/ALIGN]
static const int OFFSET_MULTIPLIER = BOUNDARY_VALUE /[/ALIGN]
                         sizeof(SPRITE_GFX[0]);[/ALIGN]
uint16 * tileVramAddress = &SPRITE_GFX[shuttle->gfxIndex *[/ALIGN]
                             OFFSET_MULTIPLIER];[/ALIGN]
        [/ALIGN]
W通常我们都会一次拷贝多个块状图进VRAM。幸运的是当使用grit转换至精灵图的时候,它会自动告知我们块状数据的大小。既然知道了大小,那么我们就可以使用dmaCopyHalfwords(使用byte长度来进行拷贝)往VRAM中拷贝数据。我们也可以从数据大小上计算出一张图中使用了多少块状图,然后分成若干份进行传输。在我们的例子中,使用了16色的块状图,一个块状图(***像素)将占用32bytes。[/ALIGN]
[B]精灵图像的读入[/B][/ALIGN]
现在来看看精灵图是怎么运作的吧。首先我们要读入橘黄色飞船的图像以及月球的图像。建立一个新的名为initSprites的函数。将其放在initBackgrounds函数的后面。确定其中包含了orangeShuttle.h以及moon.h。它们包含了grit生成的我们的精灵图的信息。[/ALIGN]
我也新建了一个叫做“SpriteInfo”的结构类型。这个结构中包含了SpriteEntry中不是很明确声明的精灵图的信息。待一会儿我们将使用它来帮助我们管理精灵图的信息。[/ALIGN]
[B]步骤 6.1 创造一张精灵图[/B][/ALIGN]

首先我们要为精灵图填充SpriteInfo信息。每一张精灵图都有其独自的SpriteInfo结构。我们要做的第一件事是声明一个OAM ID给精灵图。这个数字将帮助我们跟踪精灵图是跟哪一个OAM相关联。同时我们也要用它来计算其他的东西。
声明宽度、高度、以及精灵图的角度。
选择精灵图关联的OAM入口。
设置属性0。
设置属性1。
设置属性2。
向VRAM拷贝块状图数据。
向VRAM拷贝调色板数据。
再根据下面的代码来写initSprites函数。[/ALIGN]
void initSprites(OAMTable * oam, SpriteInfo *spriteInfo) {[/ALIGN]
   /*  定义一些精灵图设置中的具体内容。使用这些函数来计算一个有效的块状图或调色板内存的索引。[/ALIGN]
    *  OFFSET_MULTIPLIER基于下面的来自GBATEK中的方程式进行计算。(http://nocash.emubase.de/gbatek.htm#dsvideoobjs):[/ALIGN]
    *    TileVramAddress = TileNumber * BoundaryValue[/ALIGN]
    *  由于 SPRITE_GFX 是16进制数, the compiler will increment the address[/ALIGN]
    *  编译器将会为SPRITE_GFX增加一个每2进1的序列地址。(编译器自动计算)[/ALIGN]
    */[/ALIGN]
   static const int BYTES_PER_16_COLOR_TILE = 32;[/ALIGN]
   static const int COLORS_PER_PALETTE = 16;[/ALIGN]
   static const int BOUNDARY_VALUE = 32; /* 默认的范围值[/ALIGN]
                             * (可在REG_DISPCNT中设定) */[/ALIGN]
   static const int OFFSET_MULTIPLIER = BOUNDARY_VALUE /[/ALIGN]
                            sizeof(SPRITE_GFX[0]);[/ALIGN]
[/ALIGN]
   /* 跟踪可用块状图*/[/ALIGN]
   int nextAvailableTileIdx = 0;[/ALIGN]
[/ALIGN]
   /* 创造一个飞船精灵图 */[/ALIGN]
   static const int SHUTTLE_OAM_ID = 0;[/ALIGN]
   assert(SHUTTLE_OAM_ID [/ALIGN]
   SpriteInfo * shuttleInfo = &spriteInfo[SHUTTLE_OAM_ID];[/ALIGN]
   SpriteEntry * shuttle = &oam->oamBuffer[SHUTTLE_OAM_ID];[/ALIGN]
[/ALIGN]
   /* 初始化飞船信息 */[/ALIGN]
   shuttleInfo->oamId = SHUTTLE_OAM_ID;[/ALIGN]
   shuttleInfo->width = 64;[/ALIGN]
   shuttleInfo->height = 64;[/ALIGN]
   shuttleInfo->angle = 462;[/ALIGN]
   shuttleInfo->entry = shuttle;[/ALIGN]
[/ALIGN]
   /*[/ALIGN]
    *  设定属性0。[/ALIGN]
    *[/ALIGN]
    *  OBJCOLOR_16将制造出一个16色的精灵图。在这里我们需要一个仿***灵图(通过isRotateScale),因为之后可能会将飞船旋转。[/ALIGN]
    */[/ALIGN]
   shuttle->y = SCREEN_HEIGHT / 2 - shuttleInfo->height;[/ALIGN]
   shuttle->isRotateScale = true;[/ALIGN]
   /*这段语句检查是否有一个可用的矩阵来进行精灵图的仿射矩阵变换。当然不一定非要矩阵的ID与仿射ID相同,但是如果能够确保其相同,那将非常有帮助。*/[/ALIGN]
   assert(!shuttle->isRotateScale || (shuttleInfo->oamId [/ALIGN]
   shuttle->isSizeDouble = false;[/ALIGN]
   shuttle->objMode = OBJMODE_NORMAL;[/ALIGN]
   shuttle->isMosaic = false;[/ALIGN]
   shuttle->colMode = OBJCOLOR_16;[/ALIGN]
   shuttle->objShape = OBJSHAPE_SQUARE;[/ALIGN]
[/ALIGN]
   /*[/ALIGN]
    *  设定属性1。rotationIndex中提到了放射变换矩阵的位置。用一个宏计算来为其设置地址。OBJSIZE_64, 在我们的案例中由于要使用一张方形精灵图,所以要设定为64x64大小。[/ALIGN]
    */[/ALIGN]
   shuttle->x = SCREEN_WIDTH / 2 - shuttleInfo->width * 2 +[/ALIGN]
              shuttleInfo->width / 2;[/ALIGN]
   shuttle->rotationIndexx = shuttleInfo->oamId;[/ALIGN]
   shuttle->objSize = OBJSIZE_64;[/ALIGN]
[/ALIGN]
   /* [/ALIGN]
    *  设定属性2。[/ALIGN]
设定精灵图要使用的块状图,放置图层的优先级,使用的调色板,以及是否显示。[/ALIGN]
    */[/ALIGN]
   shuttle->gfxIndex = nextAvailableTileIdx;[/ALIGN]
   nextAvailableTileIdx += orangeShuttleTilesLen / BYTES_PER_16_COLOR_TILE;[/ALIGN]
   shuttle->priority = OBJPRIORITY_0;[/ALIGN]
   shuttle->palette = shuttleInfo->oamId;[/ALIGN]
[/ALIGN]
   /*旋转精灵图*/[/ALIGN]
   rotateSprite(&oam->matrixBuffer[shuttleInfo->oamId],[/ALIGN]
            shuttleInfo->angle);[/ALIGN]
[/ALIGN]
   /*************************************************************************/[/ALIGN]
[/ALIGN]
   /* 创造一个月球的精灵图 */[/ALIGN]
   static const int MOON_OAM_ID = 1;[/ALIGN]
   assert(MOON_OAM_ID [/ALIGN]
   SpriteInfo * moonInfo = &spriteInfo[MOON_OAM_ID];[/ALIGN]
   SpriteEntry * moon = &oam->oamBuffer[MOON_OAM_ID];[/ALIGN]
[/ALIGN]
   /* 初始化月球信息 */[/ALIGN]
   moonInfo->oamId = MOON_OAM_ID;[/ALIGN]
   moonInfo->width = 32;[/ALIGN]
   moonInfo->height = 32;[/ALIGN]
   moonInfo->angle = 462;[/ALIGN]
   moonInfo->entry = moon;[/ALIGN]
[/ALIGN]
   /*[/ALIGN]
    *  设定属性0。[/ALIGN]
制造出一个16色的精灵图。这里不用仿***灵图。[/ALIGN]
    */[/ALIGN]
   moon->y = SCREEN_WIDTH / 2 + moonInfo->height / 2;[/ALIGN]
   moon->isRotateScale = false;[/ALIGN]
   /*这段语句检查是否有一个可用的矩阵来进行精灵图的仿射矩阵变换。当然不一定非要矩阵的ID与仿射ID相同,但是如果能够确保其相同,那将非常有帮助。*/[/ALIGN]
   assert(!moon->isRotateScale || (moonInfo->oamId [/ALIGN]
   moon->isHidden = false;[/ALIGN]
   moon->objMode = OBJMODE_NORMAL;[/ALIGN]
   moon->isMosaic = false;[/ALIGN]
   moon->colMode = OBJCOLOR_16;[/ALIGN]
   moon->objShape = OBJSHAPE_SQUARE;[/ALIGN]
[/ALIGN]
   /*[/ALIGN]
    * Configure attribute 1.[/ALIGN]
    *[/ALIGN]
    *因为我们要用到一个方形精灵图,将制造出一张32x32尺寸的精灵图。要用一张非仿***灵图,所以属性1不需要旋转信息。另外,它能直接垂直或者水平翻转精灵图。[/ALIGN]
    */[/ALIGN]
   moon->x = SCREEN_WIDTH / 2 + moonInfo->width + moonInfo->width / 2;[/ALIGN]
   moon->hFlip = false;[/ALIGN]
   moon->vFlip = false;[/ALIGN]
   moon->objSize = OBJSIZE_32;[/ALIGN]
[/ALIGN]
   /* [/ALIGN]
    *  设定属性2。[/ALIGN]
设定精灵图要使用的块状图,放置图层的优先级,使用的调色板,以及是否显示。[/ALIGN]
    */[/ALIGN]
   moon->gfxIndex = nextAvailableTileIdx;[/ALIGN]
   nextAvailableTileIdx += moonTilesLen / BYTES_PER_16_COLOR_TILE;[/ALIGN]
   moon->priority = OBJPRIORITY_2;[/ALIGN]
   moon->palette = moonInfo->oamId;[/ALIGN]
[/ALIGN]
   /*************************************************************************/[/ALIGN]
[/ALIGN]
   /* 拷贝精灵图调色板*/[/ALIGN]
   dmaCopyHalfWords(SPRITE_DMA_CHANNEL,[/ALIGN]
              orangeShuttlePal,[/ALIGN]
              &SPRITE_PALETTE[shuttleInfo->oamId *[/ALIGN]
                         COLORS_PER_PALETTE],[/ALIGN]
              orangeShuttlePalLen);[/ALIGN]
   dmaCopyHalfWords(SPRITE_DMA_CHANNEL,[/ALIGN]
              moonPal,[/ALIGN]
              &SPRITE_PALETTE[moonInfo->oamId * COLORS_PER_PALETTE],[/ALIGN]
              moonPalLen);[/ALIGN]
[/ALIGN]
   /* 向精灵图图像内存中拷贝精灵图 */[/ALIGN]
   dmaCopyHalfWords(SPRITE_DMA_CHANNEL,[/ALIGN]
              orangeShuttleTiles,[/ALIGN]
              &SPRITE_GFX[shuttle->gfxIndex * OFFSET_MULTIPLIER],[/ALIGN]
              orangeShuttleTilesLen);[/ALIGN]
   dmaCopyHalfWords(SPRITE_DMA_CHANNEL,[/ALIGN]
              moonTiles,[/ALIGN]
              &SPRITE_GFX[moon->gfxIndex * OFFSET_MULTIPLIER],[/ALIGN]
              moonTilesLen);[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]断言是什么?[/B][/ALIGN]
在上面的语句中,有一些东西可能看起来像是一个函数调用另外一个函数,这个功能叫做断言。他们实际上并不是函数调用,更像是宏扩展。要使用断言的话,就必须要包含其头文件。[/ALIGN]
#include [/ALIGN]
        [/ALIGN]
断言宏允许程序自行假设代码将如何运行。你只需要在程序中放置一些fact来使用它。当假设失败,程序将自动退出。在运行的时候,断言将会进行评估。[/ALIGN]
当一些语句可能有问题的时候,断言将自动停止它,从而帮助你避免某些bug。是一个可以一直使用的好帮手。[/ALIGN]
当你要发布一个软件的时候,预处理程序会将代码中的断言自动移除。使用GNU编译器就可以做到这一点,你只需要设置一下NDEBUG。[/ALIGN]
[B]精灵图的显示 [/B][/ALIGN]
在主函数中,我们需要初始化一下OAM中的拷贝信息,创造一个架构来暂时保存精灵图数据,进行initSprites函数的调用,然后使用OAM拷贝来更新OAM。 [/ALIGN]
int main() {[/ALIGN]
   /*  启动2D图形核心 */[/ALIGN]
   powerOn(POWER_ALL_2D);[/ALIGN]
[/ALIGN]
   /*设定VRAM和背景控制寄存器。设定下屏为主屏幕为。分配VRAM bank。接下来定义背景控制寄存器。[/ALIGN]
    */[/ALIGN]
   lcdMainOnBottom();[/ALIGN]
   initVideo();[/ALIGN]
   initBackgrounds();[/ALIGN]
[/ALIGN]
   /* 建立一部分精灵图 */[/ALIGN]
   SpriteInfo spriteInfo[SPRITE_COUNT];[/ALIGN]
   OAMTable *oam = new OAMTable();[/ALIGN]
   iniOAMTable(oam);[/ALIGN]
   initSprites(oam, spriteInfo);[/ALIGN]
[/ALIGN]
   /* 显示背景 */[/ALIGN]
   displayStarField();[/ALIGN]
   displayPlanet();[/ALIGN]
   displaySplash();[/ALIGN]
[/ALIGN]
   /*更新OAM。在VBlank期间,使用OAM拷贝。(其他期间它是锁定的)[/ALIGN]
    */[/ALIGN]
   swiWaitForVBlank();[/ALIGN]
   updateOAM(oam);[/ALIGN]
[/ALIGN]
   return 0;[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]编译[/B][/ALIGN]
在编译之前,最好看一下main.cpp文件是否包含进了全部的文件。由于包含的文件过多,所以有时候很容易丢下一些东西。#include [/ALIGN]
#include [/ALIGN]
#include "sprites.h"[/ALIGN]
[/ALIGN]
/* 背景*/[/ALIGN]
#include "starField.h"[/ALIGN]
#include "planet.h"[/ALIGN]
#include "splash.h"[/ALIGN]
/* 精灵图*/[/ALIGN]
#include "orangeShuttle.h"[/ALIGN]
#include "moon.h"[/ALIGN]
        [/ALIGN]
如果一切准备无误,那么你就可以进行编译、输出,之后就可以看到如[U]图示 6.2,“输出背景图和精灵图”[/U] [/ALIGN]
[B]图示 6.2,“输出背景图和精灵图”[/B][/ALIGN]

退伍者

本命年换兔子头保平安

精华
3
帖子
11433
威望
14 点
积分
12723 点
种子
9 点
注册时间
2004-2-3
最后登录
2023-12-12
 楼主| 发表于 2010-3-22 22:09  ·  北京 | 显示全部楼层
[B]章节7. 太空飞船射击游戏类型的基本开发过程[/B][/ALIGN]
[B]内容目录[/B][/ALIGN]
面向对象编程语言的重要性 [/ALIGN]
飞船的设定 [/ALIGN]
制作飞船 [/ALIGN]
构造函数 [/ALIGN]
加速度 [/ALIGN]
飞船的移动 [/ALIGN]
飞船方向改变的方法 [/ALIGN]
飞船的旋转 [/ALIGN]
飞船位置的获取 [/ALIGN]
飞船角度的获取 [/ALIGN]
将飞船添加进我们的程序 [/ALIGN]
创建主游戏循环函数 [/ALIGN]
编译[U][/U][/ALIGN]
[B]面向对象编程语言的重要性[/B][/ALIGN]
面向对象编程(OOP)是在现代系统中制造出好的游戏的基础。虽然不用到OOP也可以做出好东西,但是它仍然是改进代码可重用性可读性,模块化以及抽象化的十分重要的工具。它简化了程序员的工作。而且由于采用了模块化,工程中与同伴进行合作将变得十分简单明快。[/ALIGN]
[B]飞船的设定[/B][/ALIGN]
我们要做的第一件事就是制造出一台飞船。下面要介绍到的飞船的“级”中将包含所有的参数,以一种便于理解和易用使用的飞船功能。考虑一下飞船应该具有什么功能吧。它应该可以射击、加速、高速运行(惯性滑行),以及其他的一些你想要加入的性能。你觉得一艘船应该包含什么参数呢?应该有速度提升、冲刺、规模、最高速度、高速运行、位置、还有防护盾?在整理这些参数之后,下一步就是要写出函我们需要放入飞船“级”的功能和参数了。像 表格7.1“飞船参数和功能”, 你也可以自己列一个表格,或者画出一些草图什么的。另外,最好要先将构思付诸于纸上之后再进行编程。[/ALIGN]
[B]表格7.1“飞船参数和函数”[/B][/ALIGN]
[B]参数[/B][/ALIGN]
[B]功能[/B][/ALIGN]
飞船高度[/ALIGN]
accelerate[/ALIGN]
飞船宽度[/ALIGN]
moveShip[/ALIGN]
位置[/ALIGN]
turnClockwise[/ALIGN]
飞行速度[/ALIGN]
turnCounterClockwise[/ALIGN]
角度[/ALIGN]
getPosition[/ALIGN]
旋转速度[/ALIGN]
reverseTurn[/ALIGN]
冲刺[/ALIGN]
getAngle[/ALIGN]
最高速度[/ALIGN]
[/ALIGN]
规模[/ALIGN]
[/ALIGN]

[B]制作飞船[/B][/ALIGN]
这里有一个比较不错的架构文件提供给大家,以便大家直接将自己的“级”写入。这个架构文件可以直接添加进你的ship.cpp文件。同时也包含头文件ship.h。如果想自己写出新的“级”的话,那么就需要自己重新写一个架构文件了。它很易于使用所以不用过分的担心设计“级”的时候要琢磨很长时间语义学的问题。[B]构造函数[/B][/ALIGN]
我们提供了一个见到的构造函数以及一个比较个人化的初始化方法。这里面有一大堆工作要做。事实上,编译器可以自动的生成构造函数,拷贝构造函数,如果你不确信该如何使用的话,也会自动为你执行和提供方法。来随意更改一下初始化当中的默认数值,试试看这对飞船参数有什么影响吧。[/ALIGN]
[B]加速[/B][/ALIGN]
加速可能是飞船所需要的必备功能。我们只需要按照确定的数值提高飞行速度就可以实现加速功能、冲刺、朝向。这里利用到了一些简单的几何学的东西。由于我们的飞行速度是以二维向量来表示的(X Y 元素),所以我们不得不向每一个方向来覆盖向量。我们将用冲刺变量乘以sin(角度)来获得X变量,然后用-cos(角度)来获得y变量。接下来,在计算出x和y的增量之后,将其加到目前的速度上,一定要确保不要超过飞船的最大速度。void Ship::accelerate() {[/ALIGN]
   float incX = thrust * sin(angle);[/ALIGN]
   float incY = -(thrust * cos(angle));[/ALIGN]
[/ALIGN]
   //下面的限制最高速度的方法可能并不是十分准确。斜线速度要比直线速度快一些。[/ALIGN]
[/ALIGN]
   velocity.x += incX;[/ALIGN]
   //确保X方向速度不要过快。[/ALIGN]
   if (velocity.x > maxSpeed) {[/ALIGN]
      velocity.x = maxSpeed;[/ALIGN]
   }[/ALIGN]
   if (velocity.x [/ALIGN]
      velocity.x = -maxSpeed;[/ALIGN]
   }[/ALIGN]
[/ALIGN]
   velocity.y += incY;[/ALIGN]
   //确保Y方向速度不要过快。[/ALIGN]
   if (velocity.y > maxSpeed) {[/ALIGN]
      velocity.y = maxSpeed;[/ALIGN]
   }[/ALIGN]
   if (velocity.y [/ALIGN]
      velocity.y = -maxSpeed;[/ALIGN]
   }[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]飞船的移动[/B][/ALIGN]
要感谢NDS硬件系统,这一部分非常简单。我们所要做的就是利用飞行速度来改变位置即可。硬件部分已经帮忙考虑了出屏和边框的问题。[/ALIGN]
void Ship::moveShip() {[/ALIGN]
   //move the ship[/ALIGN]
   position.x += velocity.x;[/ALIGN]
   position.y += velocity.y;[/ALIGN]
[/ALIGN]
   //由于不用考虑出屏的问题,所以这里不用管。[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]飞船方向改变的方法 [/B][/ALIGN]
这部分花了我不少时间来考虑,即使是单线性的也不简单,但是这非常有用。我们可以通过旋转飞船,不用来个180度大调转,只要在目前速度方向的相反方向进行指向即可。以我们目前速度方向为0度,然后直接赋予180度级即可。void Ship::reverseTurn() {[/ALIGN]
   angle = (2 * PI) - atan2(velocity.x, velocity.y);[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]飞船的旋转[/B][/ALIGN]
旋转飞船也十分简单。我们只需要提升或者改变飞船旋转目标方向的速度即可。void Ship::turnClockwise() {[/ALIGN]
    angle += turnSpeed;[/ALIGN]
}[/ALIGN]
void Ship::turnCounterClockwise() {[/ALIGN]
    angle -= turnSpeed;[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]飞船位置的获取[/B][/ALIGN]
返回到飞船的位置。[/ALIGN]
MathVector2D Ship::getPosition() {[/ALIGN]
    return position;[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]飞船角度的获取 [/B][/ALIGN]
这部分有一些技巧性在里面。首先我们要知道NDS一周有32768度。然而并非实际上就是32768度,但是如果这么考虑的话,问题将会变得简单一些。然而采用32768度制是由于libnds内建的sin和cos查找表是这么规定的。如果只有360个入口的话将会对表的使用造成一种空间浪费。libnds将角度更精确的设定为32768个入口。更多的入口明显代表着精确度更高。为了使NDS知道如何旋转我们的精灵图我们不得不将弧度值转变为32768度制,转换起来还是比较简单的。[/ALIGN]
第一步就是要先转换成360度制,像初中时学过的那样,乘以180/π即可。180就是一个圆的度数的一半。所以在32768度制中我们可以乘以一个弧度值DEGREES_IN_A_CIRCLE/(2 * PI)。然后,返回值取为整数(硬件不支持浮点数,所以我们必须使用定点数方式来将其表示为整数)。[/ALIGN]
然后,写出一个函数来返回得到的角度值。[/ALIGN]
int Ship::radToDeg(float rad) {[/ALIGN]
    return (int)(rad * (DEGREES_IN_A_CIRCLE/(2 * PI)));[/ALIGN]
}[/ALIGN]
int Ship::getAngleDeg() {[/ALIGN]
    return radToDeg(angle);[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]将飞船添加进我们的程序[/B][/ALIGN]
现在我们要在主函数中建立我们的飞船数据了。这其实非常简单,我们只需要建立一个飞船物体然后定义一个SpriteEntry给它。[/ALIGN]
如果可能的话,优化一下新的“级”,这样就可以知道我们所写的哪一部分是有效的。让我们试着使飞船以10倍的速度来飞行吧。[/ALIGN]
int main() {[/ALIGN]
   /*  开启2D图像核心 */[/ALIGN]
   powerOn(POWER_ALL_2D);[/ALIGN]
[/ALIGN]
   /*设定VRAM以及背景控制寄存器。设置下屏为主屏幕,分配VRAM bank。接下来,设置背景控制寄存器。[/ALIGN]
    */[/ALIGN]
   lcdMainOnBottom();[/ALIGN]
   initVideo();[/ALIGN]
   initBackgrounds();[/ALIGN]
[/ALIGN]
   /* 建立一部分精灵图*/[/ALIGN]
   SpriteInfo spriteInfo[SPRITE_COUNT];[/ALIGN]
   OAMTable *oam = new OAMTable();[/ALIGN]
   iniOAMTable(oam);[/ALIGN]
   initSprites(oam, spriteInfo);[/ALIGN]
[/ALIGN]
   /* 显示背景 */[/ALIGN]
   displayStarField();[/ALIGN]
   displayPlanet();[/ALIGN]
   displaySplash();[/ALIGN]
[/ALIGN]
   /* 制作飞船*/[/ALIGN]
   static const int SHUTTLE_OAM_ID = 0;[/ALIGN]
   SpriteEntry * shipEntry = &oam->oamBuffer[SHUTTLE_OAM_ID];[/ALIGN]
   SpriteRotation * shipRotation = &oam->matrixBuffer[SHUTTLE_OAM_ID];[/ALIGN]
   Ship * ship = new Ship(&spriteInfo[SHUTTLE_OAM_ID]);[/ALIGN]
[/ALIGN]
   /*给飞船加入速度,让其运动起来。*/[/ALIGN]
   for (int i = 0; i [/ALIGN]
      ship->accelerate();[/ALIGN]
   }[/ALIGN]
[/ALIGN]
   /*更新OAM*/[/ALIGN]
   swiWaitForVBlank();[/ALIGN]
   updateOAM(oam);[/ALIGN]
[/ALIGN]
   return 0;[/ALIGN]
}[/ALIGN]
      [/ALIGN]
[B]创建主游戏循环函数[/B][/ALIGN]
之前的代码可能看着挺无趣的,因为我们不止一次地重复更新OAM。现在我们要开始创建一个游戏循环。其中有一部分将会放到下一章节进行讲解。[/ALIGN]
游戏循环至少需要三个主要元素。第一是任何游戏循环都要能够接收外接输入指令。这个会在下一章节进行讲解。第二是更新游戏状态。根据输入指令的不同,用掉的时间,游戏状态会发生变化。最后就是反应了。在本例中,我们更新了OAM来让它知道现在游戏进行了什么变化,然后再根据需要对变化做出反应。[/ALIGN]
既然我们已经知道了游戏循环都包括哪些东西,那么现在就让我们来试着用它来跑程序吧。首先我们要更新游戏状态。因为目前我们没法进行输入采集。我们让飞船以目前的速度移动,然后飞船的位置就会发生变化。更新飞船精灵图的属性信息,也就是位置发生的变化。最后调用一个vblank的函数来确保程序不要超过60FPS(NDS的图形速度),再更新至OAM,告诉它我们已经改变了精灵图的状态,它应该开始做出反应了。[/ALIGN]
   for (;;) {[/ALIGN]
      /* 更新游戏状态 */[/ALIGN]
      ship->moveShip();[/ALIGN]
[/ALIGN]
      /* 更新精灵图属性 */[/ALIGN]
      MathVector2D position = ship->getPosition();[/ALIGN]
      shipEntry->x = (int)position.x;[/ALIGN]
      shipEntry->y = (int)position.y;[/ALIGN]
      rotateSprite(shipRotation, ship->getAngleDeg());[/ALIGN]
[/ALIGN]
      /*更新OAM  */[/ALIGN]
      swiWaitForVBlank();[/ALIGN]
      updateOAM(oam);[/ALIGN]
   }[/ALIGN]
[/ALIGN]
   return 0;[/ALIGN]
}[/ALIGN]
        [/ALIGN]
OAM在这里发挥了至关重要的作用,得利于NDS硬件的优势,我们已经实现了在自己的程序中旋转和移动飞船。其实说实在的我们所做的不过是把一些寄存器当中的某些位以一定的规律进行了改动而已,然而我们的飞船已经活了过来。很不可思议吧。[/ALIGN]
[B]编译[/B][/ALIGN]
确保在编译之前关联了所有需要的文件。[/ALIGN]
#include [/ALIGN]
#include [/ALIGN]
#include "sprites.h"[/ALIGN]
#include "ship.h"[/ALIGN]
[/ALIGN]
/* 背景*/[/ALIGN]
#include "starField.h"[/ALIGN]
#include "planet.h"[/ALIGN]
#include "splash.h"[/ALIGN]
/* 精灵图*/[/ALIGN]
#include "orangeShuttle.h"[/ALIGN]
#include "moon.h"[/ALIGN]
        [/ALIGN]
由于采用了新的“级”,所以这里面所有的东西都应该重新编译。在下一章节我们将讲到如何利用输入系统来影响飞船。[/ALIGN]

退伍者

本命年换兔子头保平安

精华
3
帖子
11433
威望
14 点
积分
12723 点
种子
9 点
注册时间
2004-2-3
最后登录
2023-12-12
 楼主| 发表于 2010-3-22 22:09  ·  北京 | 显示全部楼层
[B]章节 8. 任天堂DS输入系统[/B][/ALIGN]
[B]内容目录[/B][/ALIGN]
概述 [/ALIGN]
按键输入 [/ALIGN]
触摸! [/ALIGN]
编写一个输入更新函数 [/ALIGN]
编写一个输入控制函数 [/ALIGN]
再次创建主游戏循环函数 [/ALIGN]
编译[U][/U][/ALIGN]
[B]概述[/B][/ALIGN]
NDS有很多种用户输入系统,包括按键,触摸屏以及麦克风。大多数的游戏主机只有按键以及模拟摇杆输入。然而NDS没有模拟摇杆,但它却有一个可以创造各种奇迹的神奇的触摸屏。当然,本手册中仅概述触摸屏以及按键。如果你想要了解更多关于麦克风的信息的话,我推荐你来这里看看,虽然资料可能稍微有些古旧,但是还是很不错的:http://www.double.co.nz/nintendo_ds/nds_develop9.html. [/ALIGN]
[B]按键输入[/B][/ALIGN]
libnds提供给了我们一个很不错的按键输入的调用功能。不用与门来判断当前按下的按键,只要调用一下scanKeys函数,然后检查一下三个输入函数中的一个,keysDown(), keysHeld即可。要想知道哪一个按键被刚刚按下,使用keysDown函数。想知道哪个按键被长按了,使用keysHeld。想知道哪个按键被释放了,使用keysUp。是不是相当简单呢?libnds提供了一些按键码的定义,表格8.1“libnds按键定义” 中可以查看到。[/ALIGN]
[B]表格8.1“libnds按键定义”[/B][/ALIGN]
[B]按键定义[/B][/ALIGN]
[B]掩码位[/B][/ALIGN]
[B]关联输入[/B][/ALIGN]
KEY_A[/ALIGN]
1 [/ALIGN]
A 键[/ALIGN]
KEY_B[/ALIGN]
1 [/ALIGN]
B 键[/ALIGN]
KEY_SELECT[/ALIGN]
1 [/ALIGN]
Select 键[/ALIGN]
KEY_START[/ALIGN]
1 [/ALIGN]
Start 键[/ALIGN]
KEY_RIGHT[/ALIGN]
1 [/ALIGN]
右 D-pad[/ALIGN]
KEY_LEFT[/ALIGN]
1 [/ALIGN]
左 D-pad[/ALIGN]
KEY_UP[/ALIGN]
1 [/ALIGN]
上 D-pad[/ALIGN]
KEY_DOWN[/ALIGN]
1 [/ALIGN]
下 D-pad[/ALIGN]
KEY_R[/ALIGN]
1 [/ALIGN]
R 键[/ALIGN]
KEY_L[/ALIGN]
1 [/ALIGN]
L 键[/ALIGN]
KEY_X[/ALIGN]
1 [/ALIGN]
X 键[/ALIGN]
KEY_Y[/ALIGN]
1 [/ALIGN]
Y 键[/ALIGN]
KEY_TOUCH[/ALIGN]
1 [/ALIGN]
触摸屏输入(无匹配)[/ALIGN]
KEY_LID[/ALIGN]
1 [/ALIGN]
合盖 (用于休眠)[/ALIGN]

[B]触摸![/B][/ALIGN]
宇多田光想玩触摸。你不得不开始编程了,开始吧![/ALIGN]
触摸屏是NDS成功的一大关键所在。libnds接口提供了很方便的触摸屏设置功能。我们可以马上开始为宇多田光编写自己的程序了(笑)。如果你想获取触摸的位置,只需要调用touchRead函数。这个函数声明的变量的架构包含了触摸时X和Y轴信息。使用的时候便是如此简单。[/ALIGN]
/* 读取触摸屏信息时,我们可以这么做 */[/ALIGN]
touchPosition touch;[/ALIGN]
touchRead(&touch);[/ALIGN]
touch->px; // X像素的位置[/ALIGN]
touch->py; // Y像素的位置[/ALIGN]
        [/ALIGN]
[B]IPC[/B][B]是什么?[/B][/ALIGN]
你可能会看到有些代码在使用一个叫做IPC的东西来关联触摸屏,使用IPC架构是相对来说比较不赞成的。未来也最好不要以它为基础做设计。我们没有使用IPC方法,以后估计也不会这么做。[/ALIGN]
[B]编写一个输入更新函数[/B][/ALIGN]
既然我们已经知道了一些关于NDS输入的原理。那么就开始试着在main.cpp中加入一个新的函数来采集输入吧。我们姑且称之为updateInput。待一会儿我们将在游戏循环中首先使用这个函数。[/ALIGN]
void updateInput(touchPosition * touch) {[/ALIGN]
   // 以当前值更新按键寄存器[/ALIGN]
   scanKeys();[/ALIGN]
[/ALIGN]
   // 更新触摸屏的值[/ALIGN]
   *touch = touchRead();[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]编写一个输入控制函数[/B][/ALIGN]
这个函数是我们在进行游戏循环的状态更新时所用到的一部分。它可以根据外部输入来对应地改变游戏状态。我们可以把游戏状态作为一个函数参数付给它。让我们来调用handleInput函数吧。[/ALIGN]
首先,当按住十字键的上的时候,我们希望飞船开始加速。这样的话,就需要NDS检测上键被按下的函数D-padheld(也包括下键的初始化),然后开始加速。上键被认为是长按。采集输入并不会影响按键寄存器。其他的键也照着这个思路去理解。读一读下面的代码,看看你能不能独自理解吧。[/ALIGN]
void handleInput(Ship * ship, MathVector2D * moonPos,[/ALIGN]
            SpriteInfo * moonInfo, touchPosition * touch) {[/ALIGN]
[/ALIGN]
   /* 按住十字键的上或者下 */[/ALIGN]
   if (keysHeld() & KEY_UP) {[/ALIGN]
      //accelerate ship[/ALIGN]
      ship->accelerate();[/ALIGN]
   } else if (keysHeld() & KEY_DOWN) {[/ALIGN]
      //reverse ship direction[/ALIGN]
      ship->reverseTurn();[/ALIGN]
   }[/ALIGN]
   [/ALIGN]
   /* 按住十字键的左或右 */[/ALIGN]
   if (keysHeld() & KEY_LEFT) {[/ALIGN]
      //rotate counter clockwise[/ALIGN]
      ship->turnCounterClockwise();[/ALIGN]
   } else if (keysHeld() & KEY_RIGHT) {[/ALIGN]
      //rotate clockwise[/ALIGN]
      ship->turnClockwise();[/ALIGN]
   }[/ALIGN]
[/ALIGN]
   /*触摸笔触碰触摸屏。这一般都会应用在抓取或者移动物品时。对于本例没有必要完全理解它是如何获取触摸屏位置信息的,如果觉得有趣的话还是可以自己看看。[/ALIGN]
   */[/ALIGN]
   static MathVector2D moonGrip;[/ALIGN]
   if (keysDown() & KEY_TOUCH) {[/ALIGN]
      /* Record the grip */[/ALIGN]
      moonGrip.x = touch->px;[/ALIGN]
      moonGrip.y = touch->py;[/ALIGN]
   } else if (keysHeld() & KEY_TOUCH) {[/ALIGN]
      int newX = moonPos->x + touch->px - moonGrip.x;[/ALIGN]
      int newY = moonPos->y + touch->py - moonGrip.y;[/ALIGN]
[/ALIGN]
      /* 防止拖出屏幕*/[/ALIGN]
      if (newX [/ALIGN]
        moonPos->x = 0;[/ALIGN]
      } else if (newX > (SCREEN_WIDTH - moonInfo->width)) {[/ALIGN]
        moonPos->x = SCREEN_WIDTH - moonInfo->width;[/ALIGN]
      } else {[/ALIGN]
        moonPos->x = newX;[/ALIGN]
      }[/ALIGN]
      if (newY [/ALIGN]
        moonPos->y = 0;[/ALIGN]
      } else if (newY > (SCREEN_HEIGHT - moonInfo->height)) {[/ALIGN]
        moonPos->y = SCREEN_HEIGHT - moonInfo->height;[/ALIGN]
      } else {[/ALIGN]
        moonPos->y = newY;[/ALIGN]
      }[/ALIGN]
[/ALIGN]
      /* 再次记录当前的操作 */[/ALIGN]
      moonGrip.x = touch->px;[/ALIGN]
      moonGrip.y = touch->py;[/ALIGN]
   }[/ALIGN]
}[/ALIGN]
        [/ALIGN]
大家可能注意到了,想利用输入控制飞船是非常简单的,我们的按键直接作用于飞船不同的属性当中。这很有趣,但是更有趣的东西还在后面。[/ALIGN]
对于月球这个物体来说,我们还没有为其写过“级”,如果在其中加入月球部分的代码的话,可能会显得有些凌乱。如果想加入月球的话,我们可以将两个参数发送给handleInput函数来表现月球的信息。想要让代码的更为清晰明了的话,可以重新写一个游戏状态结构来包含所有的游戏状态以及操作。[/ALIGN]
[B]再次创建主游戏循环函数[/B][/ALIGN]
让我们重新回到主函数中。由于添加了采集输入以及对输入做出反应的功能,所以现在我们需要来做一些调整以适应游戏循环。第一件事就是我们要做到按键寄存器以及触摸屏的反应能够在游戏循环中实时得到更新。建立一个叫做updateInput然后用它来实现这件工作。接下来,利用刚刚建立的这个updateInput函数得到的信息,再去更新游戏状态。其他的东西和之前的一样。int main() {[/ALIGN]
   /*  启动2D图像引擎 */[/ALIGN]
   powerOn(POWER_ALL_2D);[/ALIGN]
[/ALIGN]
   /*设定VRAM以及背景控制寄存器。设置下屏为主屏幕,分配VRAM bank。接下来,设置背景控制寄存器。[/ALIGN]
    */[/ALIGN]
   lcdMainOnBottom();[/ALIGN]
   initVideo();[/ALIGN]
   initBackgrounds();[/ALIGN]
[/ALIGN]
   /* 建立一部分精灵图 */[/ALIGN]
   SpriteInfo spriteInfo[SPRITE_COUNT];[/ALIGN]
   OAMTable *oam = new OAMTable();[/ALIGN]
   iniOAMTable(oam);[/ALIGN]
   initSprites(oam, spriteInfo);[/ALIGN]
[/ALIGN]
   /* 显示背景 */[/ALIGN]
   displayStarField();[/ALIGN]
   displayPlanet();[/ALIGN]
   displaySplash();[/ALIGN]
[/ALIGN]
   /*************************************************************************/[/ALIGN]
[/ALIGN]
   /* 监视触摸屏操作*/[/ALIGN]
   touchPosition touch;[/ALIGN]
[/ALIGN]
   /* 绘制出飞船 */[/ALIGN]
   static const int SHUTTLE_AFFINE_ID = 0;[/ALIGN]
   SpriteEntry * shipEntry = &oam->oamBuffer[SHUTTLE_AFFINE_ID];[/ALIGN]
   SpriteRotation * shipRotation = &oam->matrixBuffer[SHUTTLE_AFFINE_ID];[/ALIGN]
   Ship * ship = new Ship(&spriteInfo[SHUTTLE_AFFINE_ID]);[/ALIGN]
[/ALIGN]
   /* 绘制出月球 */[/ALIGN]
   static const int MOON_AFFINE_ID = 1;[/ALIGN]
   SpriteEntry * moonEntry = &oam->oamBuffer[MOON_AFFINE_ID];[/ALIGN]
   SpriteInfo * moonInfo = &spriteInfo[MOON_AFFINE_ID];[/ALIGN]
   MathVector2D * moonPos = new MathVector2D();[/ALIGN]
   moonPos->x = moonEntry->x;[/ALIGN]
   moonPos->y = moonEntry->y;[/ALIGN]
[/ALIGN]
   for (;;) {[/ALIGN]
      /* 更新游戏状态 */[/ALIGN]
      updateInput(&touch);[/ALIGN]
      handleInput(ship, moonPos, moonInfo, &touch);[/ALIGN]
      ship->moveShip();[/ALIGN]
[/ALIGN]
      /* 更新飞船精灵图属性 */[/ALIGN]
      MathVector2D position = ship->getPosition();[/ALIGN]
      shipEntry->x = (int)position.x;[/ALIGN]
      shipEntry->y = (int)position.y;[/ALIGN]
      rotateSprite(shipRotation, ship->getAngleDeg());[/ALIGN]
      /* 更新月球精灵土属性 */[/ALIGN]
      moonEntry->x = (int)moonPos->x;[/ALIGN]
      moonEntry->y = (int)moonPos->y;[/ALIGN]
[/ALIGN]
      /*更新OAM */[/ALIGN]
      swiWaitForVBlank();[/ALIGN]
      updateOAM(oam);[/ALIGN]
   }[/ALIGN]
[/ALIGN]
   return 0;[/ALIGN]
}[/ALIGN]
        [/ALIGN]
[B]编译[/B][/ALIGN]
再一次的,一定要确定所有的文件都包含进去之后再编译。[/ALIGN]
#include [/ALIGN]
#include [/ALIGN]
#include "sprites.h"[/ALIGN]
#include "ship.h"[/ALIGN]
[/ALIGN]
/* Backgrounds */[/ALIGN]
#include "starField.h"[/ALIGN]
#include "planet.h"[/ALIGN]
#include "splash.h"[/ALIGN]
/* Sprites */[/ALIGN]
#include "orangeShuttle.h"[/ALIGN]
#include "moon.h"[/ALIGN]
        [/ALIGN]
让现在我们就可以通过十字键来控制飞船了。有意思吧!现在游戏应该像 图示 8.1,“橘黄色飞船开始移动” 那样了。现在,我们只差做一些外星人来打了...[/ALIGN]
[B]图示 8.1,“橘黄色飞船开始移动”[/B][/ALIGN]

退伍者

本命年换兔子头保平安

精华
3
帖子
11433
威望
14 点
积分
12723 点
种子
9 点
注册时间
2004-2-3
最后登录
2023-12-12
 楼主| 发表于 2010-3-22 22:10  ·  北京 | 显示全部楼层
[B]后记[/B][/ALIGN]
我希望大家能够在本文中有所收获。我也希望它能够指导你了解一些NDS编程的基本知识。希望各位能够独自创造出很棒的作品。[/ALIGN]
我的地址如下,可以尽请来信 [email protected]
如果各位需要帮助或者有建议和意见的话,都请尽管给我写信。[/ALIGN]
[B]鸣谢[/B][/ALIGN]
感谢这几年来指导过我关于NDS编程方面知识的各位。如果遗漏了哪位,还请海涵。特别感谢:[/ALIGN]

John Haugeland
Martin Korth
Dave Murphy
Liran Nuna
Thorsten Lemke
Tobias Weyand
Shaun Taylor
Bryant Poffenberger
Hollis Jacobsen
Jeff Katz
James Zawacki
Michael Noland
Jasper Vijn
Jason Rogers
Christopher Double
Matt Luckett
Mathias Onisseit
[B]关于作者[/B][/ALIGN]
Jaeden Amero目前是美国德克萨斯的美国国家仪器公司中的多平台驱动开发人员。2008年12月毕业于杨百翰大学。闲暇时热衷于NDS、GBA游戏编程、FPGA编程、独轮车以及手风琴演奏。[/ALIGN]
全文完
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|A9VG电玩部落 川公网安备 51019002005286号

GMT+8, 2024-5-15 21:38 , Processed in 0.204256 second(s), 10 queries , Redis On.

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

返回顶部