对于每个平台,必须写一个启动类。该类实例化一个特定后台的Application实现和实现了应用逻辑的ApplicationListener。该启动类依赖于平台,让我们看看怎样为每个后台实例化和配置一个启动类。
该篇假定你已经完成了Project Setup中的说明并将生成的核心,桌面,Android 和 HTML5工程导入到了Eclipse。
打开my-gdx-game中的类 Main.java,显示如下:
package com.me.mygdxgame; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; public class Main { public static void main(String[] args) { LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration(); cfg.title = "my-gdx-game"; cfg.useGL20 = false; cfg.width = 480; cfg.height = 320; new LwjglApplication(new MyGdxGame(), cfg); } }
首先实例化一个 LwjglApplicationConfiguration 。该类允许设置各位配置,如初始化屏幕分辨率,是否使用OpenGL ES 1.x 或2.0等。请参考该类的Javadocs获取更多信息。
一旦设置好了配置对象,一个LwjglApplication 就被实例化了。MyGdxGame() 类是实现了游戏逻辑的 ApplicationListener 。
由此,就创建了一个窗口并且 ApplicationListener 如 The Life-Cycle 篇描述的那样被调用。
Android 应用不使用 main() 方法作为入口点,取而代之的是一个 Activity。打开my-gdx-game-android项目中的类 MainActivity.java :
package com.me.mygdxgame; import android.os.Bundle; import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; public class MainActivity extends AndroidApplication { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration(); cfg.useGL20 = false; initialize(new MyGdxGame(), cfg); } }
主入口方法是 Activity 的 onCreate() 方法. 注意, MainActivity 继承自 AndroidApplication,而AndroidApplication继承自Activity。像桌面应用中的启动类一样,创建一个配置实例 (AndroidApplicationConfiguration)。配置好后,调用 AndroidApplication.initialize()方法,传入ApplicationListener(MyGdxGame) 和配置。请参考 AndroidApplicationConfiguration Javadocs 以获取更多关于可配置项的信息。
Android 应用可以拥有多个activities。 Libgdx 游戏通常应该只由单个的 activity 组成。在libgdx内部实现游戏的不同屏幕,而不是单独的activities。这样做的原因是创建一个新的 Activity同时意味着创建一个新的OpenGL上下文,这很耗时且意味着所有的图形资源都要被重新加载。
除了 AndroidApplicationConfiguration,Android 应用程序也可通过 AndroidManifest.xml文件进行配置,该文件位于 Android 工程根目录下。该文件大致如下所示:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.me.mygdxgame" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="5" android:targetSdkVersion="15" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".MainActivity" android:label="@string/app_name" android:screenOrientation="landscape" android:configChanges="keyboard|keyboardHidden|orientation"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
如果你的应用是打算运行在Android1.5及更高版本,那么关键的一点是要把 targetSdkVersion的值设置为 >= 6。如果没有设置这个属性,高版本的Android会以传统模式运行应用程序。在不好的情况下,绘制区域的分辨率会比实际的分辨率低很多。
除了 targetSdkVersion,activity 元素中的 screenOrientation 和 configChanges 也应该设置。
screenOrientation 属性为应用指定一个固定的方向。一旦省略,应用程序就可以同时在横屏和竖屏情况下运行。
configChanges 属性很 关键 且上面应该始终有值。省略该属性意味着每次当物理键盘滑出/滑入或者设备方向改变时应用程序都会重启。 如果省略了screenOrientation ,libgdx应用会收到ApplicationListener.resize()的调用以指示屏幕方向的改变。接着API客户端就可能据此重新布局。
如果应用需要能够在设备外存储器中写入(如:SD-card),需要访问网络,使用振动器,想避免屏幕进入睡眠状态,或者想记录音频,那么需要在AndroidManifest.xml 文件中加入以下权限:
<uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
用户一般都会质疑带有很多权限的应用,因此我们只选择这些。
为了让唤醒锁定正常工作,相应地需要设置 AndroidApplicationConfiguration.useWakeLock 为 true。
如果一个应用不需要访问加速计或指南针,建议通过设置 AndroidApplicationConfiguration 的 useAccelerometer 和useCompass 域为 false 以取消它们。请参考 Android Developer's Guide 查看怎样设置其他属性,如设置应用程序的图标。
Libgdx 有一个简单的功能可以为Android创建动态壁纸。动态壁纸的启动类为 AndroidLiveWallpaperService,这里是一个例子:
package com.mypackage; // imports snipped for brevity public class LiveWallpaper extends AndroidLiveWallpaperService { @Override public ApplicationListener createListener () { return new MyApplicationListener(); } @Override public AndroidApplicationConfiguration createConfig () { return new AndroidApplicationConfiguration(); } @Override public void offsetChange (ApplicationListener listener, float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset) { Gdx.app.log("LiveWallpaper", "offset changed: " + xOffset + ", " + yOffset); } }
当动态壁纸展示在选择器或在主屏中显示时,createListener() 和 createConfig() 方法会被调用。
offsetChange() 方法在用户滑动主屏时调用,指出与屏幕与中心位置有多大偏移。该方法在渲染线程进而调用,因此不需要同步任何东西。
除了启动类,还必须创建一个XML文件以描述壁纸。我们将它命名为 livewallpaper.xml。在Android工程 res/ 文件夹下创建一个文件夹叫 xml/ 并把该文件放在那(res/xml/livewallpaper.xml)。下面是该文件的内容:
<?xml version="1.0" encoding="UTF-8"?> <wallpaper xmlns:android="http://schemas.android.com/apk/res/android" android:thumbnail="@drawable/ic_launcher" android:description="@string/description" android:settingsActivity="com.mypackage.LivewallpaperSettings"/>
这里定义了你的LWP在选择器中显示的缩略图,描述和一个Activity,这个Activity会在用户点击LWP选择器的 "Settings" 的时候显示。这里应该是一个标准的Activity,包含一些组件用来更改设置,如:背景色等类似的设置。你可以在SharedPreferences中保存这些设置并稍后在LWP 的 ApplicationListener 里通过Gdx.app.getPreferences()加载它们。
最后,需要在AndroidManifest.xml 文件中添加一些东西。这里是一个LWP以及一个简单设置的:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mypackage" android:versionCode="1" android:versionName="1.0" android:installLocation="preferExternal"> <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="14"/> <uses-feature android:name="android.software.live_wallpaper" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".LivewallpaperSettings" android:label="Livewallpaper Settings"/> <service android:name=".LiveWallpaper" android:label="@string/app_name" android:icon="@drawable/icon" android:permission="android.permission.BIND_WALLPAPER"> <intent-filter> <action android:name="android.service.wallpaper.WallpaperService" /> </intent-filter> <meta-data android:name="android.service.wallpaper" android:resource="@xml/livewallpaper" /> </service> </application> </manifest>
该表单定义了:
注意,动态壁纸功能仅从Android 2.1 (SDK level 7)版本开始支持。
LWPs 的触摸输入有一定的局限。通常只记录点击/删除。如果你需要全触摸,可以查看设置 AndroidApplicationConfiguration#getTouchEventsForLiveWallpaper 标志为 true 以接收全部多点触摸事件。
从Android 4.2起, 用户可能设置 Daydreams,它会在设备闲置或锁定时显示。daydreams 类似于屏保可以显示相册等。Libgdx 使你能够很容易地编写daydreams:
Daydream 的启动类称作 AndroidDaydream。下面是一个例子:
package com.badlogic.gdx.tests.android; import android.annotation.TargetApi; import android.util.Log; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; import com.badlogic.gdx.backends.android.AndroidDaydream; import com.badlogic.gdx.tests.MeshShaderTest; @TargetApi(17) public class Daydream extends AndroidDaydream { @Override public void onAttachedToWindow() { super.onAttachedToWindow(); setInteractive(false); AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration(); cfg.useGL20 = true; ApplicationListener app = new MeshShaderTest(); initialize(app, cfg); } }
简单地继承 AndroidDaydream,重写 onAttachedToWindow,设置 configuration 和 ApplicationListener 然后初始化 daydream。
除了daydream本身,还可以设置一个activity以便用户配置他的dayream。这只需要一个普通activity,或是一个libgdx AndroidApplication。一个空的activity如下所示:
package com.badlogic.gdx.tests.android; import android.app.Activity; public class DaydreamSettings extends Activity { }
这个配置activity必须被用作Dayream的元数据。在res/xml下创建一个xml文件,并像如下所示指定activity:
<dream xmlns:android="http://schemas.android.com/apk/res/android" android:settingsActivity="com.badlogic.gdx.tests.android/.DaydreamSettings" />
最后,像以前一样把该配置activity添加到AndroidManifest.xml中,再为daydream添加一段服务描述:
<service android:name=".Daydream" android:label="@string/app_name" android:icon="@drawable/icon" android:exported="true"> <intent-filter> <action android:name="android.service.dreams.DreamService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <meta-data android:name="android.service.dream" android:resource="@xml/daydream" /> </service>
iOS 后台依赖使用 Xamarin 的 MonoDevelop IDE作开发,并使用一个Monotouch许可作部署。Monotouch 应用的入口点是AppDelegate,位于工程的Main.cs文件中。下面是一个例子:
using System; using System.Collections.Generic; using System.Linq; using MonoTouch.Foundation; using MonoTouch.UIKit; using com.badlogic.gdx.backends.ios; using com.me.mygdxgame; namespace com.me.mygdxgame { public class Application { [Register ("AppDelegate")] public partial class AppDelegate : IOSApplication { public AppDelegate(): base(new MyGdxGame(), getConfig()) { } internal static IOSApplicationConfiguration getConfig() { IOSApplicationConfiguration config = new IOSApplicationConfiguration(); config.orientationLandscape = true; config.orientationPortrait = false; config.useAccelerometer = true; config.useMonotouchOpenTK = true; config.useObjectAL = true; return config; } } static void Main (string[] args) { UIApplication.Main (args, null, "AppDelegate"); } } }
Info.plist 文件包含以下应用配置信息:屏幕方向,最低OS版本,可选, 屏幕截图等。下面是一个通过MonoDevelop编辑过的XML文件:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDisplayName</key> <string>my-gdx-game</string> <key>MinimumOSVersion</key> <string>3.2</string> <key>UIDeviceFamily</key> <array> <integer>2</integer> <integer>1</integer> </array> <key>UIStatusBarHidden</key> <true/> <key>UISupportedInterfaceOrientations</key> <array> <string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortraitUpsideDown</string> </array> <key>UISupportedInterfaceOrientations~ipad</key> <array> <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> </dict> </plist>
为了通过Monotouch为iOS平台创建程序集,需要一个转换处理。当在MonoDevelop中发出构建操作时,该转换过程是预处理步骤的一部分。如果使用第三方库或为一些资源定义了非标准位置,则需要相应更新convert.properties文件。下面是一个示例:
A conversion process is required in order to create the assemblies needed by Monotouch for the iOS platform. This processing is done as part of the pre-build step when you issue the build operation in MonoDevelop. If you are using third party libraries or have non-standard locations defined for some of your source, you will need to update the convert.properties file accordingly. An example file is below:
SRC = ../my-gdx-game/src/ CLASSPATH = ../my-gdx-game/libs/gdx.jar EXCLUDE = IN = -r:libs/ios/gdx.dll -recurse:target/*.class OUT = target/my-gdx-game.dll
该文件指定了构成my-gdx-game.dll程序集的输入文件。
HTML5/GWT应用的主入口点是 GwtApplication。打开my-gdx-game-html5工程里的 GwtLauncher.java :
package com.me.mygdxgame.client; import com.me.mygdxgame.MyGdxGame; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.backends.gwt.GwtApplication; import com.badlogic.gdx.backends.gwt.GwtApplicationConfiguration; public class GwtLauncher extends GwtApplication { @Override public GwtApplicationConfiguration getConfig () { GwtApplicationConfiguration cfg = new GwtApplicationConfiguration(480, 320); return cfg; } @Override public ApplicationListener getApplicationListener () { return new MyGdxGame(); } }
该主入口由两个方法:GwtApplication.getConfig() 和 GwtApplication.getApplicationListener() 。前者需要返回一个GwtApplicationConfiguration 实例,该实例指定了多数HTML5应用的配置。GwtApplication.getApplicatonListener() 方法返回 ApplicationListener以运行。
GWT 需要为每一个引用的 jar/项目 编写Java代码。此外,每个jar/项目需要一个以gwt.xml结尾的模块定义文件 。
在工程中创建的示例里,html5工程的模块文件如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit trunk//EN" "http://google-web-toolkit.googlecode.com/svn/trunk/distro-source/core/src/gwt-module.dtd"> <module> <inherits name='com.badlogic.gdx.backends.gdx_backends_gwt' /> <inherits name='MyGdxGame' /> <entry-point class='com.me.mygdxgame.client.GwtLauncher' /> <set-configuration-property name="gdx.assetpath" value="../my-gdx-game-android/assets" /> </module>
该文件指定了另外两个模块以继承(gdx-backends-gwt 和核心工程),同时还有入口类(上面的GwtLauncher) 和一个相对于html5工程根目录的路径,指向assets目录。 gdx-backend-gwt jar 和核心工程都有同样的模块文件来指定其他依赖。不能使用不含模块文件和源码的 jars/projects!
请参考 GWT Developer Guide 获取更多关于模块和依赖的信息。
因各种原因,GWT不支持Java反射。 Libgdx 有一个内部模拟层来生成一小部分内部类的反射信息。就是说如果使用libgdx 的 Json serialization 功能,就会出问题。你可以通过指定生成哪个包和类的反射信息来修复这个问题。要这样做,需要在GWT工程的gwt.xml文件中配置如下属性:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <module> ... other elements ... <extend-configuration-property name="gdx.reflect.include" value="org.softmotion.explorers.model" /> <extend-configuration-property name="gdx.reflect.exclude" value="org.softmotion.explorers.model.HexMap" /> </module>
可以通过extend-configuration-property元素来添加多个包和类。
该功能还处在实验阶段,使用请注意风险。
libgdx HTML5 应用会预加载gdx.assetpath下找到的所有资源。在此加载过程中,会显示一个通过GWT组件实现的加载画面。如果要自定义加载画面,可以简单地重写下GwtApplication.getPreloaderCallback()方法(上面例子中的GwtLauncher)。下面的例子通过Canvas画了一个非常简陋的加载画面:
long loadStart = TimeUtils.nanoTime(); public PreloaderCallback getPreloaderCallback () { final Canvas canvas = Canvas.createIfSupported(); canvas.setWidth("" + (int)(config.width * 0.7f) + "px"); canvas.setHeight("70px"); getRootPanel().add(canvas); final Context2d context = canvas.getContext2d(); context.setTextAlign(TextAlign.CENTER); context.setTextBaseline(TextBaseline.MIDDLE); context.setFont("18pt Calibri"); return new PreloaderCallback() { @Override public void done () { context.fillRect(0, 0, 300, 40); } @Override public void loaded (String file, int loaded, int total) { System.out.println("loaded " + file + "," + loaded + "/" + total); String color = Pixmap.make(30, 30, 30, 1); context.setFillStyle(color); context.setStrokeStyle(color); context.fillRect(0, 0, 300, 70); color = Pixmap.make(200, 200, 200, (((TimeUtils.nanoTime() - loadStart) % 1000000000) / 1000000000f)); context.setFillStyle(color); context.setStrokeStyle(color); context.fillRect(0, 0, 300 * (loaded / (float)total) * 0.97f, 70); context.setFillStyle(Pixmap.make(50, 50, 50, 1)); context.fillText("loading", 300 / 2, 70 / 2); } @Override public void error (String file) { System.out.println("error: " + file); } }; }
注意,仅可用纯GWT设备来显示加载画面,预加载完成后libgdx API才可用。