现在的位置: 首页 > 综合 > 正文

第十三章 读取游戏资源文件

2012年02月22日 ⁄ 综合 ⁄ 共 4915字 ⁄ 字号 评论关闭

“从零开始系列又回来了”……

本来,老邢已经决定结束这一系列文章的写作了,却意外收到了毓彦的邀请,希望能够把这些文章结集出版,虽然出版很麻烦,但有机会将自己的文字付梓却是一件令人兴奋的事,所以老邢决定尝试一下。如毓彦所说,从零系列目前的内容有点少,而老邢也正好可以把以前的笔记整理一下,补充一些本来想写却没有写的东西。计划新增的内容有文件和网络、整合GAE、解析xml、Box2D物理引擎、HGE粒子系统、Lua脚本、Scoreloop排行榜、Admob广告和如何发布应用到Android Market。大部分内容都有笔记,但真正整理起来却很费时间。因为还有新引擎的开发工作,也不能分散太多精力,希望能尽早完成吧。

下面就让我们进入新的章节。

在游戏中,文件操作基本就是访问资源(图片,声音等)与存取记录,我们将分两部分讲解。首先说资源,通常,游戏比较小的时候,我们可以直接将资源文件放置在res中相应的目录下,通过Resources类来读取。如果是图片之外的文件,比如音频、地图数据,我们可以在res目录下创建一个raw目录,就可以通过Resource ID来访问了。但如果是较大的游戏,资源很多,还会分成很多目录来管理,这时候依靠Android生成的资源ID就有些力不从心了。这种情况下,我们可以使用assets目录,直接将目录结构保存在assets中并通过文件名来访问。而对于更大型的游戏,拥有几十M甚至上百M的资源文件,全都跟主程序打包在一起(放在assets目录下)让大家从应用商店下载似乎有点bt了,所以这种大型游戏一般选择单独发行数据包或者在应用启动时从网络下载。

使用res目录中资源的方法,我想大家阅读了前面的章节之后肯定已经掌握了,下面就让我们分别来学习如何访问assets和sdcard中的资源文件。

assets目录的操作需要通过特殊的API,这是由assets的特性决定的。assets目录是被打包在apk文件中的,所以他并不是一个真正的文件系统,而是一个压缩包。下面请看一个例子,我们将图片player1.png放在assets/graphics目录下,将音频sample.mid放在assets/audio目录下,在程序中显示图片并播放音频。

首先让我们看一下显示图片的代码:

Bitmap bmp = null;

public void start(SurfaceHolder surfaceHolder){

try {

bmp = BitmapFactory.decodeStream(Main.getInstance().getAssets().open("graphics/player1.png"));

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

首先,我们在SceneStartMenu中声明一个Bitmap对象bmp。然后在start函数中初始化bmp。这里的代码虽然只有一行(除了try/catch结构),但涉及到的知识却不少。

首先,我们用到了BitmapFactory,读者对它肯定不陌生了,我们一直都用它来获取bmp对象。但这次,我们没有使用decodeResource,而是另外一个函数decodeStream。Stream是一种处理字节数据的机制,比如网络、文件、键盘输入等,都是字节类型的数据。Stream提供了对数据的一种封装,使用户更方便的对数据进行读写。在例子中,我们要从文件中取得图像数据,没有Resource ID,所以要使用Stream。

下面的问题是如何获得Stream。前面说过,使用assets目录下的文件,需要特殊的API,这就是AssetManager,要访问assets文件就必须通过它。AssetManager中有两个最重要的方法会常常被用到,分别是open和openFd。前者获取一个InputStream,就是输入流,我们可以通过这个输入流获取文件内容(与InputStream相对应的是OutputStream输出流,通过它可以修改文件内容)。后者获得一个对于文件的描述,我们在下一步播放音乐的时候会用到。

新的问题又来了,我们如何取得AssetManager呢?首先,可以通过Main的静态方法获取Activity的实例,然后调用getAsset方法获取AssetManager实例。完成了以上步骤我们就可以使用decodeStream构建Bitmap实例了。最后在update中显示这个位图。

@Override

public void update(Canvas c) {

// TODO Auto-generated method stub

c.drawARGB(255, 0, 0, 0);

c.drawText("SceneStartMenu", GameView.width/2, GameView.height/2, paint);

if(bmp != null) {

c.drawBitmap(bmp, 0, 0, paint);

}

}

让我们运行程序看一下效果:

clip_image002

完成了从assets目录载入图片文件的工作,下面让我们学习如何从sdcard载入资源。

首先我们将图片文件存入sdcard中graphics目录下。使用adb创建目录的方法大家没有忘记吧,如果没有将adb的路径加入到系统路径中,就需要手工进入sdk目录下的platform-tools目录,执行adb命令。

clip_image004

创建了目录之后我们就可以通过命令或DDMS将文件push到模拟器的sdcard上了。

clip_image006

经过了前面的学习,我们知道可以通过Stream创建Bitmap实例,sdcard就是通常的文件系统,所以java.io包中的FileInputStream类就可以完成这项工作,而且非常简单。请看代码:

bmp = BitmapFactory.decodeStream(new FileInputStream("/sdcard/graphics/player1.png"));

需要注意的是文件的路径一定要正确,访问sdcard中的文件一定要以”/sdcard/”作为根目录。当然,BitmapFactory中还有更简单的方法decodeFile,读者可以自己尝试一下。

下面让我们完成本章的第二个内容,播放一个保存在assets目录下的音频文件。

大家一定还记得上一章中我们讲到的播放音乐和音效的方法吧。就拿音乐为例,我们播放了一首保存在sdcard上的midi音频,这一次,我们把这段音频copy到assets目录中。还记得上一章中我们获取音频文件的方法么?

mp.setDataSource(path);

没错,我们使用了文件的路径。那么我们能不能也使用assets中的文件的路径来作为setDataSource的参数呢?似乎常有人问这样的问题。但老邢可以肯定的说,不能。大家一定要记住,assets是一个压缩包,不是通常的文件系统,所以我们还必须要借助AssetManager。根据显示图像的经验,读者也许很快会想到创建一个InputStream给MediaPlayer,实际上在很多系统上我们都是这样做的。但是很奇怪,Android偏偏没有这种功能,真是让人非常不解。所以我们在前面埋下了一个伏笔──AssetManager中另一个重要的函数openFd。

当我们查看MediaPlayer的说明文档时(查看说明文档的方法大家还记得吧),会发现setDataSource有一种形式是以FileDescriptor文件描述符作为参数的,这正是我们所需要的。如同显示图片的方法,我们使用openFd,可以取得一个对音频文件的描述符。

AssetFileDescriptor afd = Main.getInstance().getAssets().openFd("audio/sample.mid");

但是细心的读者会发现这个文件描述符并不是FileDescriptor,多了个Asset前缀。并且,它也不是FileDescriptor的子类,所以不能直接用作setDataSource的参数。但这不是问题,通过AssetFileDescripter的getFileDescriptor就可以获得我们需要的FileDescriptor。

下面就让我们修改一下Util中的playBGM,用FileDescriptor代替path作为参数:

public static void playBGM(FileDescriptor fd, long offset, long length, boolean looping) {

if(mp == null) {

mp = new MediaPlayer();

}

mp.reset();

try {

mp.setDataSource(fd, offset, length);

mp.prepare();

} catch (IllegalArgumentException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (IllegalStateException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

mp.setLooping(looping);

mp.start();

}

这样就可以使用playBGM播放assets目录下的文件了。

Util.playBGM(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), false);

当然读者还可以使用其他方法,比如使用路径/assets/path/file.ext作为路径参数,但是在函数内部检查路径的值,如果是/assets开头的,则使用AssetManager获取文件描述。修改后的playBGM如下

public static void playBGM(String path, boolean looping) {

if(mp == null) {

mp = new MediaPlayer();

}

mp.reset();

try {

if(path.indexOf("/assets/") == 0) {

AssetFileDescriptor afd = Main.getInstance().getAssets().openFd(path.substring(8));

mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());

} else {

mp.setDataSource(path);

}

mp.prepare();

} catch (IllegalArgumentException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (IllegalStateException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

mp.setLooping(looping);

mp.start();

}

这样我们就可以通过

Util.playBGM("/assets/audio/sample.mid", false);

播放音频文件了。但是请读者一定记住,在playBGM内部依然使用了AssetManager。

播放sdcard中音频文件的方法在上一章就已经讲过了,大家没有忘记吧。

关于资源读取的方法我们就介绍到这里,本章我们学习了从assets目录和sdcard载入资源的方法,从网络载入资源的方法我们会在后面的章节中介绍。

抱歉!评论已关闭.