对于每个平台,必须写一个启动类。该类实例化一个特定后台的Application实现和实现了应用逻辑的ApplicationListener。该启动类依赖于平台,让我们看看怎样为每个后台实例化和配置一个启动类。
该篇假定你已经完成了Project Setup中的说明并将生成的核心,桌面,Android 和 HTML5工程导入到了Eclipse。
桌面应用(LWJGL)
打开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
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上下文,这很耗时且意味着所有的图形资源都要被重新加载。
AndroidManifest.xml 文件
除了 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>
关于 Sdk 版本
如果你的应用是打算运行在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>
该表单定义了:
- 它使用动态壁纸功能,查看 <uses-feature>.
- 绑定壁纸的权限,查看 android:permission
- 用来设置activity
- 动态壁纸服务,指向 livewallpaper.xml 文件,查看 meta-data
注意,动态壁纸功能仅从Android 2.1 (SDK level 7)版本开始支持。
LWPs 的触摸输入有一定的局限。通常只记录点击/删除。如果你需要全触摸,可以查看设置 AndroidApplicationConfiguration#getTouchEventsForLiveWallpaper 标志为 true 以接收全部多点触摸事件。
Daydreams
从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
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
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>
convert.properties
为了通过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
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元素来添加多个包和类。
该功能还处在实验阶段,使用请注意风险。
Loading 画面
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才可用。