1. 程式人生 > >Libgdx Developer's Guide(Libgdx開發者手冊)-6(啟動類與配置)

Libgdx Developer's Guide(Libgdx開發者手冊)-6(啟動類與配置)

對於每個平臺,必須寫一個啟動類。該類例項化一個特定後臺的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才可用。