LibGDX教程——重建Flappy Bird——(3) 打包资源

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/artzok/article/details/50550426

修改Android启动图标和名称

首先,我们需要替换Android项目的默认启动图标。FlappyBird-android项目中存在一个名为res的特殊目录。在该目录中包含了Android项目专用的资源文件。
展开res目录可以看到四个以drawable为前缀的文件夹:
drawable-ldpi(低分辨率显示屏)
drawable-mdpi(中分辨率显示屏)
drawable-hdpi(高分辨率显示屏)
drawable-xdpi(极高分辨率显示屏)
这些文件被用于Android应用支持不同尺寸显示屏的设备并且Android根据显示屏的屏幕密度决定使用内容。为了简单起见,我们忽略设备分辨率的区别,这里我们创建一个公用的drawable文件夹。这样,无论在什么分辨率的设备上Android都会使用该文件夹内的资源。
下面截图是本例Android项目的默认启动图标ic_launcher.png,也就是我们当前使用的Android应用启动图标:

现在删除上述四个文件和所有名为ic_launcher.png图标文件。
下面图标是我们将使用的启动图标:


将上述图标文件命名为ic_launcher.png并拷贝到drawable文件夹内。因为我们并没有改变启动图标的命名,因此项目将无需再更改什么便能正常启动。相反,如果你重命名了启动图标的名称,则不要忘记修改AndroidManifest.xml文件对他的引用。AndroidManifest.xml文件对启动图标的引用行如下所示:

<application  
    android:icon="@drawable/ic_launcher"  
    .../> 
接下来我们打开android项目的res/value/string.xml文件,将内容修改如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Flappy Bird</string>
</resources>
创建纹理集
下面截图显示了本项目使用的所有图片资源文件:


Libgdx有一个内置的TexturePacker工具类,该类可以方便的创建和更新纹理集内容。由于TexturePacker类并不是LibGDX的核心内容,因此在使用之前必须先完成一下准备工作:
  1. 将libgdx-1.2.0.zip压缩文件中的extensions/tools/gdx-tools.jar文件解压出来。
  2. 将gdx-tools.jar文件剪切到FlappyBird-desktop/libs子文件夹中。接下来必须将扩展添加到Build Path中。
  3. 在Eclipse中,右键单击FlappyBird-desktop项目,然后导航到Build Path|Configure Build Path|Libraries。
  4. 然后单击Add JARs按钮打开JAR selection窗口。
  5. 在窗口列表中,找到FlappyBird-desktop项目的libs子文件夹。
  6. 最后,选中新添加的gdx-tools.jar扩展文件,然后点击OK按钮确认选择。
接下来根据以下步骤打包纹理集资源。
  1. FlappyBird-desktop项目创建assets-raw文件夹。然后添加一个assets-raw/images子文件夹。将所有图片资源复制到该文件夹下。
  2. 接下来,打开FlappyBird-desktop项目的启动类Main.java,然后添加下面两行代码:
    import com.badlogic.gdx.tools.texturepacker.TexturePacker;  
    import com.badlogic.gdx.tools.texturepacker.TexturePacker.Settings; 
  3. 然后再Main类中添加如下代码:
    package com.art.zok.flappybird;
    
    import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
    import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
    import com.badlogic.gdx.tools.texturepacker.TexturePacker;
    import com.badlogic.gdx.tools.texturepacker.TexturePacker.Settings;
    
    
    public class Main {
    	private static boolean rebuildAtlas = true;
    	private static boolean drawDebugOutline = true;
    	
    	public static void main(String[] args) {
    		if(rebuildAtlas) {
    			Settings settings = new Settings();
    			settings.maxWidth = 1024;
    			settings.maxHeight = 1024;
    			settings.duplicatePadding = false;
    			settings.debug = drawDebugOutline;
    			TexturePacker.process(settings, "assets-raw/images", 
    					"../FlappyBird-android/assets/images",
    					"flappy-bird.pack");
    		}
    		LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
    		cfg.title = "FlappyBird";
    		cfg.width = 480;
    		cfg.height = 800;
    		cfg.resizable = false;
    		new LwjglApplication(new FlappyBirdMain(), cfg);
    	}
    }
上述代码提供了一个创建和更新纹理集的功能,每当桌面应用运行时都会更新纹理集内容。rebuildAtlas变量控制是否在启动应用时更新纹理集。使用TexturePacker类创建纹理集非常简单,该类包含一个process()静态方法,该方法需要一个可选的Settings参数和三个必要参数。第一个必要参数是资源的文件夹路径。第二个是输出纹理集的目标路径,第三个参数是输出纹理集的描述文件名。关于Settings的配置参数可以查看官方文档。
运行FlappyBird-desktop项目,然后查看FlappyBird-android项目下assets/images/flappy-bird.png文件:

我们发现所有资源都被整齐的排列在一起了。还有,我们可以发现FlappyBird-desktop项目的assets文件夹下也生成了images/flappy-bird.png和images/flappy-bird.pack文件,但是我们并没有将该路径作为纹理集的目标输出路径呀?这里需要记住,这其实是Eclipse提供的功能,Eclipse可以为一个目标文件夹维护一个拷贝文件夹,即当目标文件夹的内容发生改变则拷贝文件也会发生相应的改变。这里,Android项目的assets文件夹就是目标文件夹,而Desktop项目的assets文件夹就是拷贝文件夹,因此我们以后只需要将应用所需的资源存储在Andorid项目的assets文件夹即可。
组织资源
现在我们创建自己的Assets类组织和管理我们资源文件。首先,在Constants类中添加一个String常量。
package com.art.zok.flappybird.util;

public class Constants {

	// 视口宽度为20米
	public static final float VIEWPORT_WIDTH =  50f;
	// 视口高度为20米
	public static final float VIEWPORT_HEIGHT = 50f;

	// 纹理集描述文件路径
	public static final String TEXTURE_ATLAS_OBJECTS = "images/flappy-bird.pack";
}
接下来创建Assets类并添加下面代码:
package com.art.zok.flappybird.game;

import com.art.zok.flappybird.util.Constants;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.assets.AssetDescriptor;
import com.badlogic.gdx.assets.AssetErrorListener;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;

	
public class Assets implements Disposable, AssetErrorListener {
	public static final String TAG = Assets.class.getName();
	
	public static final Assets instance = new Assets();
	
	private AssetManager assetManager;
	
	// 单态类:阻止在其他类中实例化
	private Assets () {}
	
	public void init (AssetManager assetManager) {
		this.assetManager = assetManager;
		
		// 设定资源管理器的错误处理对象句柄
		assetManager.setErrorListener(this);
		
		// 载入纹理集
		assetManager.load(Constants.TEXTURE_ATLAS_OBJECTS, TextureAtlas.class);
		// 开始载入资源直到完成
		assetManager.finishLoading();
		
                // 打印资源信息
		Gdx.app.debug(TAG, "# of assets loaded: " + assetManager.getAssetNames().size);
		for(String a : assetManager.getAssetNames()) {
			Gdx.app.debug(TAG, "asset: " + a);
		}
	}
	
	@Override
	@SuppressWarnings("rawtypes") 
	public void error(AssetDescriptor asset, Throwable throwable) {
		Gdx.app.debug(TAG, "Couldn't load asset '" + asset.fileName + "'", (Exception)throwable);
	}
	
	@Override
	public void dispose () {
		assetManager.dispose();
	}
}
首先该类(Assets)被设计为单态类(singleton)。简单的说,单态类可以确保只有一个实例对象,这样做是有意义的,因为我们并不需要创建多个指向同一份资源的实例对象。单态类是通过定义一个私有的构造函数来阻止在其他类中创建实例。实际上Assets类的唯一实例被保存在instance成员变量中,我们使用public static final关键字进行修饰保证该变量的只读性和访问Assets类的唯一途径。这样设计使得Assets类允许我们在任何一处代码中无需传递任何引用参数便可访问该类内容。
init()方法应该在游戏一开始时被调用。他将初始化资源管理器加载所有资源文件。使用资源管理器载入资源文件只需简单的调用load()方法。load()方法的第一个参数要求传入资源文件的全路径,第二个参数要求指定要创建类的class。接下来调用finishLoading()方法开始执行载入过程,该方法是一个阻塞方法,他将等待所有资源加载完成之后才能继续执行后续代码。接下来,我们在控制台中打印出资源的数量和名称。
Assets类还实现了Disposable和AssetErrorListener接口。我们知道,当资源不再使用时必须及时释放他,我们通过实现dispose()方法将释放资源的任务交给资源管理器。无论任何时候资源管理器发生错误将会调用error()方法。但是,在资源管理调用我们实现的接口方法之前,我们首先需要通过setErrorListener()方法先告知资源管理器需要使用的AssetErrorListener接口实例。这里我们只是使用error()方法打印了错误消息,你可以添加额外的代码处理错误,以此避免应用崩溃。
接下来,我们可以从已经载入的纹理集中检索子图像(纹理)了。一般情况下,我们可以通过findRegion()方法完成这项工作,该方法需要一个资源名称作为参数。findRegion()方法返回一个AtlasRegion对象。
 接下来我们将在Assets内实现几个较小的内部类。这些类允许我们通过逻辑单元结构化(分组)纹理集中的子图像资源,并且还能长时间存储(缓存)查找结果的引用。
package com.art.zok.flappybird.game;

import com.art.zok.flappybird.util.Constants;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.assets.AssetDescriptor;
import com.badlogic.gdx.assets.AssetErrorListener;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;

	
public class Assets implements Disposable, AssetErrorListener {
	public static final String TAG = Assets.class.getName();
	
	public static final Assets instance = new Assets();
	
	TextureAtlas atlas;
	private AssetManager assetManager;
	
	public AssetFonts fonts;
	public AssetBird bird;
	public AssetPipe pipe;
	public AssetLand land;
	public AssetUI assetUI;
	public AssetNumber number;
	public AssetSounds sounds;
	public AssetDecoration decoration;
	
	// 单态类:阻止在其他类中实例化
	private Assets () {}
	
	public void init (AssetManager assetManager) {
		this.assetManager = assetManager;
		
		// 设定资源管理器的错误处理对象句柄
		assetManager.setErrorListener(this);
		
		// 载入纹理集
		assetManager.load(Constants.TEXTURE_ATLAS_OBJECTS, TextureAtlas.class);
		
		// 载入声音文件
		assetManager.load("sounds/sfx_die.ogg", Sound.class);
		assetManager.load("sounds/sfx_hit.ogg", Sound.class);
		assetManager.load("sounds/sfx_point.ogg", Sound.class);
		assetManager.load("sounds/sfx_swooshing.ogg", Sound.class);
		assetManager.load("sounds/sfx_wing.ogg", Sound.class);
		
		assetManager.finishLoading();
		// 打印资源信息
		Gdx.app.debug(TAG, "# of assets loaded: " + assetManager.getAssetNames().size);
		for(String a : assetManager.getAssetNames()) {
			Gdx.app.debug(TAG, "asset: " + a);
		}
		atlas = assetManager.get(Constants.TEXTURE_ATLAS_OBJECTS, TextureAtlas.class);
		fonts = new AssetFonts();
		bird = new AssetBird(atlas);
		pipe = new AssetPipe(atlas);
		land = new AssetLand(atlas);
		number = new AssetNumber(atlas);
		assetUI = new AssetUI(atlas);
		sounds = new AssetSounds(assetManager);
		decoration = new AssetDecoration(atlas);
	}
	
	@Override
	@SuppressWarnings("rawtypes") 
	public void error(AssetDescriptor asset, Throwable throwable) {
		Gdx.app.debug(TAG, "Couldn't load asset '" + asset.fileName + "'", (Exception)throwable);
	}
	
	@Override
	public void dispose () {
		assetManager.dispose();
	}
	
	public class AssetBird {
		public final Array<AtlasRegion> bird0;
		public final Array<AtlasRegion> bird1;
		public final Array<AtlasRegion> bird2;
		
		public AssetBird (TextureAtlas atlas) {
			bird0 = atlas.findRegions("bird0");
			bird1 = atlas.findRegions("bird1");
			bird2 = atlas.findRegions("bird2");
		}
	}
	
	public class AssetPipe {
		public final AtlasRegion pipeUpGreen;
		public final AtlasRegion pipeUpBrown;
		public final AtlasRegion pipeDownGreen;		
		public final AtlasRegion pipeDownBrown;
		
		public AssetPipe (TextureAtlas atlas) {
			pipeUpGreen = atlas.findRegion("pipe_up");
			pipeDownGreen = atlas.findRegion("pipe_down");
			pipeUpBrown = atlas.findRegion("pipe2_up");
			pipeDownBrown = atlas.findRegion("pipe2_down");
		}
	}
	
	public class AssetLand {
		public final AtlasRegion land;
		public AssetLand (TextureAtlas atlas) {
			land = atlas.findRegion("land");
		}
	}
	
	public class AssetNumber {
		public final Array<AtlasRegion> numbers_score;
		public final Array<AtlasRegion> numbers_context;
		public final Array<AtlasRegion> numbers_font;
		public AssetNumber(TextureAtlas atlas) {
			numbers_score = atlas.findRegions("number_score");
			numbers_context = atlas.findRegions("number_context");
			numbers_font = atlas.findRegions("font");
		}
	}
	
	public class AssetUI {
		public final AtlasRegion textGameOver;
		public final AtlasRegion scorePanel;
		public final AtlasRegion buttonMenu;
		public final AtlasRegion buttonOk;
		public final AtlasRegion buttonPause;
		public final AtlasRegion buttonPlay;
		public final AtlasRegion buttonRate;
		public final AtlasRegion buttonResume;
		public final AtlasRegion buttonScore;
		public final AtlasRegion buttonShare;
		public final AtlasRegion tutorial;
		public final AtlasRegion textReady;
		public final AtlasRegion textTitle;
		public final AtlasRegion copyRight;
		public final Array<AtlasRegion> medals;
		
		public AssetUI(TextureAtlas atlas) {
			textGameOver = atlas.findRegion("text_game_over");
			scorePanel = atlas.findRegion("score_panel");
			buttonMenu = atlas.findRegion("button_menu");
			buttonOk = atlas.findRegion("button_ok");
			buttonPause = atlas.findRegion("button_pause");
			buttonPlay = atlas.findRegion("button_play");
			buttonRate = atlas.findRegion("button_rate");
			buttonResume = atlas.findRegion("button_resume");
			buttonScore = atlas.findRegion("button_score");
			buttonShare = atlas.findRegion("button_share");
			tutorial = atlas.findRegion("tutorial");
			medals = atlas.findRegions("medals");
			textReady = atlas.findRegion("text_ready");
			textTitle = atlas.findRegion("title");
			copyRight = atlas.findRegion("brand_copyright");
		}
	}
	
	public class AssetSounds { 
		public final Sound die;
		public final Sound hit;
		public final Sound point;
		public final Sound swooshing;
		public final Sound wing;
		
		public AssetSounds(AssetManager am) {
			die = am.get("sounds/sfx_die.ogg", Sound.class);
			hit = am.get("sounds/sfx_hit.ogg", Sound.class);
			point = am.get("sounds/sfx_point.ogg", Sound.class);
			swooshing = am.get("sounds/sfx_swooshing.ogg", Sound.class);
			wing = am.get("sounds/sfx_wing.ogg", Sound.class);
		}
	}
	
	public class AssetDecoration {
		public final Array<AtlasRegion> bg;
		public final AtlasRegion white;
		public AssetDecoration (TextureAtlas atlas) {
			bg = new Array<AtlasRegion>();
			bg.add(atlas.findRegion("bg_day"));
			bg.add(atlas.findRegion("bg_night"));
			white = atlas.findRegion("white");
		}
	}	
	
	public class AssetFonts {
		
		public final BitmapFont defaultSmall;
		public final BitmapFont defaultNormal;
		public final BitmapFont defaultBig;
		
		public AssetFonts() {
			// 使用libgdx的15px位图字体文件创建三个字体
			defaultSmall = new BitmapFont(
					Gdx.files.internal("images/arial-15.fnt"), true);
			defaultNormal = new BitmapFont(
					Gdx.files.internal("images/arial-15.fnt"), true);
			defaultBig = new BitmapFont(
					Gdx.files.internal("images/arial-15.fnt"), true);
			// 设定字体尺寸
			defaultSmall.setScale(0.75f);
			defaultNormal.setScale(1.0f);
			defaultBig.setScale(2.0f);
			// 设定字体过滤模式为线性平滑
			defaultSmall.getRegion().getTexture().setFilter(
					TextureFilter.Linear, TextureFilter.Linear);
			defaultNormal.getRegion().getTexture().setFilter(
					TextureFilter.Linear, TextureFilter.Linear);
			defaultBig.getRegion().getTexture().setFilter(
					TextureFilter.Linear, TextureFilter.Linear);
		}
	}	
}
这里我们将声音资源和字体资源也加入了进来,最终Android的assets文件夹目录如下:
其中images存储着我们前面打包的纹理集资源flappy-bird.pack和flappy-bird以及字体资源arial-15.fnt和arial-15.png。
因为添加的代码比较多,所以我们将详细的解释上述代码:
首先,我们创建了AssetBird、AssetPipe、AssetLand、AssetNumber、AssetUI、AssetSounds、AssetDecoration、AssetFonts八个内部类,我们使用这八个类组织了所有资源对象。前三个AssetBird、AssetPipe和AssetLand分别为三个游戏对象准备资源。AssetSounds准备了所有声音资源。AssetFonts资源是为了让我们可以在GUI上显示信息,其实可以没有,关于字体的创建可以另外查找资料。剩下的几个内部类分别是为了UI界面和背景等等创建。
面介绍上面涉及的几个方法:
  1. AssetManager.get():该方法需要两个参数,第一个是需要获得的资源的全路径名,第二个是资源类型的class对象。
  2. TextureAtlas.findRegions():该方法返回一个文理区域序列,只需要一个字符串参数即可,该参数必须是一系列具有相同前缀的文件名。例如我们的资源中包含了三个图片bird0_0.png,bird0_1.png和bird0_2.png,那么这里bird0就是该函数的参数,之后该方法将返回一个Array<AtlasRegion>实例,该列表包含了三个纹理域。
  3. TextureAtlas.findRegion():该方需要一个不带文件扩展名的资源名称作为参数,结果将返回一个AtlasRegion对象。
  4. Texture.setFilter():该方法设置纹理分别在放大和缩小的情况下的过滤模式,TextureFilter.Linear表示平滑过滤,即消除锯齿。
最后为了方便我们为Assets创建了下面几个成员变量:
public AssetFonts fonts;
public AssetBird bird;
public AssetPipe pipe;
public AssetLand land;
public AssetUI assetUI;
public AssetNumber number;
public AssetSounds sounds;
public AssetDecoration decoration;

然后再init()方法中实例化每个成员变量,之后在任何地方我们就可以直接通过Assets.instance.fonts.defaultSmall访问字体,其他资源也可以通过相同的方法访问。


展开阅读全文

没有更多推荐了,返回首页