libGDX 2D游戏开发之 - 资源管理
原文地址:https://github.com/libgdx/libgdx/wiki/Managing-your-assets.
译者:重庆好爸爸 [email protected]
谢绝转载
译者总结 (需要完善)
相关的类
- AssetManager
- AssetLoader
术语对照
- Dispose - 处置,释放
- Asset - 资源
- Loader - 加载器
- TTF - TrueType File 美国苹果公司和微软公司共同开发的一种电脑轮廓字体(曲線描邊字)类型标准
- nitty-gritty parts - 美国俚语,一些必不可少的细节部分
管理你的资源
为什么要使用AssetManager
AssetManager帮助你加载和管理你的资源。这是一个推荐的加载资源的方法,因为这个方法有如下诸多优点:
- 异步完成大多数资源的加载。所以在资源加载完成前,你可以通过渲染进程去显示一个loading界面。==(译者注:“异步”是指资源的加载可以放在非渲染线程的另外一个线程中。)==
- 资源是索引计数。如果资源A和资源B都依赖于另外一个资源C,那么在资源A和资源B都被释放之前,资源C是不会释放的。这也说明如果多次加载了同一个资源,实际上这个资源在内存中是只有1个实例的。
- 你所有的资源可以放在同一个地方。
- 允许透明(transparently)的实现一些功能,比如Caches(见下面的FileHandleResolver)
继续把。
创建一个AssetManager
创建AssetManager很简单
AssetManager manager = new AssetManager();
上面的代码会创建一个标准的AssetManager。代码执行后,libgdx拥有的所有loaders(加载器)都也都创建了。让我们一起看看这个加载机制是如何工作的。
注意: 不要把AssetManager和其他的资源(比如Texture等)设置为静态变量,除非你有信心正确的管理他们。例如,下面的代码将会导致问题:
public static AssetManager assets = new AssetManager();
这条语句在Andriod上可能会引发问题。Andriod中的静态变量的生命周期和你的APP生命周期可能是不一样的。(译者注:关于静态变量这一段不是很理解,你们自行找资料看吧:- 因此,你的前一个APP实例的AssetManager实例可能被下一个APP实例拿去应用,然后那些资源其实都已经不可用了。这样会导致黑屏/丢失纹理或者错误资源的典型问题。
在Android中,你的Activity的多个实例甚至可能被同时被激活,所以即使你能正确的处理生命周期,你也不一定100%安全。(见Stackflow描述的场景)
加载资源
AssetManager需要知道如何加载特定类型的资源。这个功能功过 AssetLoader 来实现。AssetLoader有2个变种:SynchronousAssetLoader(同步资源加载器) 和 AsynchronousAssetLoader(异步资源加载器)。SynchronousAssetLoader在渲染线程中加载所有的资源;AsynchronousAssetLoader在除渲染线程外的另外线程中加载部分资源,比如: Pixmap需要一个Texture,然后在渲染线程中加载OpenGL的依赖部分,但后面需要的资源就可以在其他线程中加载了。(译者注:原文为 The following resources can be loaded out of the box with the AssetManager as constructed above.)
下面列举了各种资源的加载器: (译者注:下面这些加载器都是从同步/异步加载器继承下来的)
- Pixmaps: PixmapLoader
- Textures : TextureLoader
- BitmapFonts : BitmapFontLoader
- FreeTypeFonts : FreeTypeFontLoader
- TextureAtlases : TextureAtlasLoader
- Music instances : MusicLoader
- Sound instances : SoundLoader
- Skins : SkinLoader
- Particle Effects : ParticleEffectLoader
- I18NBundles : I18NBundleLoader
- FreeTypeFontGenerator : FreeTypeFontGeneratorLoader
加载特定的的资源也很简单,见如下代码
manager.load("data/mytexture.png", Texture.class);
manager.load("data/myfont.fnt", BitmapFont.class);
manager.load("data/mymusic.ogg", Music.class);
上面的.load()把将要加载的资源放入队列。资源将按照.load()方法的调用顺序被依次加载。有的加载器允许你在调用.load()方法的时候加入参数。比如说,我们想在加载texture的时候指定一个非默认的filter设置和mipmapping设置:
TextureParameter param = new TextureParameter();
param.minFilter = TextureFilter.Linear;
param.genMipMaps = true;
manager.load("data/mytexture.png", Texture.class, param);
你可以抽空研究一下上面提到各种loader可用的参数。
使用AssetHandler加载TTF(TrueType File)
通过AssetHandler加载TTF需要一点额外的调整, 因为在加载TTF之前,我们需要设置加载FFT需要的加载器类型:
FileHandleResolver resolver = new InternalFileHandleResolver();
manager.setLoader(FreeTypeFontGenerator.class, new FreeTypeFontGeneratorLoader(resolver));
manager.setLoader(BitmapFont.class, ".ttf", new FreetypeFontLoader(resolver));
接下来,我们想要创建一个FreeTypeFontLoaderParameter, 这个FreeTypeFontLoaderParameter定义了 1)我们实际的字体文件 2)我们的字体字号(size)。如果你有时间,你还可以研究下其他的参数,可以放进来。
比如说我们想创建2种不同的字体: 小一点的sans-serif字体,用作写作的字体;大一点的serif字体,用作标题和其他有趣的东西。我决定分别使用Arial和Georgia这两种字体。 以下是我可以使用AssetManager加载它们的方法:
// First, let's define the params and then load our smaller font
FreeTypeFontLoaderParameter mySmallFont = new FreeTypeFontLoaderParameter();
mySmallFont.fontFileName = "arial.ttf";
mySmallFont.fontParameters.size = 10;
manager.load("arial.ttf", BitmapFont.class, mySmallFont);
// Next, let's define the params and then load our bigger font
FreeTypeFontLoaderParameter myBigFont = new FreeTypeFontLoaderParameter();
myBigFont.fontFileName = "georgia.ttf";
myBigFont.fontParameters.size = 20;
manager.load("georgia.ttf", BitmapFont.class, myBigFont);
整齐! 现在我们有两种不同的字体,mySmallFont和myBigFont,可以用来显示不同的文本。
还没有完,现在字体已经".load()",但是我们还需要设置他们:
BitmapFont mySmallFont = manager.get("arial.ttf", BitmapFont.class);
BitmapFont myBigFont = manager.get("georgia.ttf", BitmapFont.class);
截至目前,我们只是把资源放入队列。AssetManager实际上还没有加载任何资源。为了触发加载动作,我们需要连续调用AssetManager.update()方法,比如把它放在我们的ApplicationListener.render() 方法中:
public MyAppListener implements ApplicationListener {
public void render() {
if(manager.update()) {
// we are done loading, let's move to another screen!
}
// display loading information
float progress = manager.getProgress()
... left to the reader ...
}
}
只要AssetManager.update()方法还在返回false,你就知道资源加载未完成。要轮询具体的加载状态,您可以使用AssetManager.getProgress()方法,它返回一个介于0和1之间的数字,表示到目前为止加载的资源的百分比。AssetManager中还有其他方法为您提供类似的信息,如AssetManager.getLoadedAssets()或AssetManager.getQueuedAssets()。 你必须调用AssetManager.update()来保存加载状态。
如果要block并确保所有资产都已加载,您可以调用:
manager.finishLoading();
这个会block,直到所有队列中的资源完成实际的加载。Kinda defeats the purpose of asynchronous loading, but sometimes one might need it (e.g., loading the assets needed to display the loading screen itself).
获取资源
获取资源也很简单
Texture tex = manager.get("data/mytexture.png", Texture.class);
BitmapFont font = manager.get("data/myfont.fnt", BitmapFont.class);
当让我们假定这些资源已经被成功加载了。如果我们想轮询一个特定的资源是否已经成功加载了,我们用下面的方法:
if(manager.isLoaded("data/mytexture.png")) {
// texture is available, let's fetch it and do something interesting
Texture tex = manager.get("data/mytexture.png", Texture.class);
}
处置(释放)资源
也很简单,这里你可以看到AssetManager的威力:
manager.unload("data/myfont.fnt");
如果这个myfont.fnt是索引到你之前手工加载的一个Texture,那么这个texture不会被销毁!在这种情况下,它是索引计数将是2次:1次是来自bitmap字体的计数,另外1次是来自它自身。只要这个计数不为0,那么这个texture就不会被销毁。
不同通过手工销毁AssetManager管理的资源,而是要用AssetManager.upload()去销毁。如果你想一次性销毁所有的资源,那么调用manager.clear();或者manager.dispose(); 这两种方法都会销毁当前所有已经加载的资源,以及销毁队列中还未来得及完成加载的资源。AssetManager.dispose()方法会销毁AssetManager本身。所有在调用AssetManager.dispose()后,就要不能再使用这个manager了。
目前基本所有的东西都说完了,但是让我们来看看另外一些必不可少的细节部分。
问题: 我没有提供String,但是AssetManager从来加载的String资源呢?
每一个加载器都有一个指向FileHandleResolver的索引。这是一个简单的接口,
Every loader has a reference to a FileHandleResolver. That's a simple interface looking like this:
public interface FileHandleResolver {
public FileHandle resolve(String file);
}
默认情况下,每个加载器会使用一个InternalFileHandleResolver。这会返回一个FileHandle指向一个内部文件(就像Gdx.files.internal("data/mytexture.png"))。你可以写你自己的resolver!研究一下assets/loaders/resolvers包去看看更多的FileHandleResolver的具体实现。一个例子是Caching系统,你可以检查是否有在外部存储中有一个新版本已经下载,如果它不可用则回退到内部存储。可能性是无限多的。
你可以通过AssetManager的第二个构造函数的形式来设置将要使用的FileHandleResolver。
AssetManager manager = new AssetManager(new ExternalFileHandleResolver());
这将确保上面列出的所有默认加载程序将使用该加载程序。
编写你自己的加载器
我无法预期您要加载哪些其他类型的资源,所以在某些时候您可能想要编写自己的加载程序。有两个接口叫做SynchronousAssetLoader和AsynchronousAssetLoader可以实现。如果您的资源可以快速加载,请使用SynchronousAssetLoader;如果您希望展现加载屏幕,则使用AsynchronousAssetLoader。我建议您的加载器在要基于上面列出的其中一个装载机的代码上。MusicLoader是一个简单的SynchronousAssetLoader;PixmapLoader是一个AsynchronousAssetLoader。BitmapFontLoader是AsynchronousAssetLoader的一个很好的例子,它在资源可以加载之前需要加载一些依赖项(在这种情况下,指存储字形Textrue)。 你可以做很多事情。
当你写完自己的加载器后,需要告诉AssetManager:
manager.setLoader(MyAssetClass.class, new MyAssetLoader(new InternalFileHandleResolver()));
manager.load("data/myasset.mas", MyAssetClass.class);
从Loading屏幕返回
在Andriod里,你的APP可以paused和resumed. 在这种情况下,需要重新加载像Texture这样的托管OpenGL资源,这可能需要花费一些时间。如果要在resume时显示加载屏幕,则可以在创建AssetManager后执行以下操作。
Texture.setAssetManager(manager);
在ApplicationListener.resume()方法中,您可以切换到加载屏幕,并再次调用AssetManager.update(),直到所有内容恢复正常。
如果没有像最后一个代码片段中所示的那样设置AssetManager,那么通常的托管纹理机制就会出现,所以你不用担心任何事情。
这就是大家期待已久的AssetManager文章。 good luck!