Tutorial #06: Pong Reloaded

In diesem Beispiel soll wiederum das erlernte Wissen angewandt und die erste Version des Spiels Pong verbessert werden:

  • Probleme mit der Fenstergröße beheben
  • Sound-Unterstützung
  • Multitouch-Unterstützung

Zum Abschluss wird noch gezeigt, wie die App auf die unterschiedlichen Zielplattformen aufbereitet werden kann.

Version 4: Ändern der Fenstergröße

Die erste Implementierung von Pong war für ein Fenster mit einer Größe von 800x600 Pixel ausgelegt. Sobald sich aber das Seitenverhältnis des Fensters ändert, treten Probleme auf:

  • Teile des Spielfelds werden abgeschnitten
  • Die Umrechnung der Mauskoordinaten in Weltkoordinaten stimmt nicht mehr
tut0106_window_v3.png
Pong V3-Ausgabefenster mit abgeschnittenem Rand bei verkleinerter Fenstergröße

Um das Abschneiden von Teilen des Spielfelds zu verhindern soll das Spielfeld mit Rahmen eingepasst werden (siehe Bild einpassen mit Rahmen).

Dafür müssten lediglich im Kamera-Knoten die zwei Attribute fieldOfViewY="300" und aspectRatio="1" gesetzt werden. Zu Übungszwecken führen wir die Anpassung aber selbst im Logik-Code durch, indem wir je nach Seitenverhältnis zwischen gesetztem Kamera-Attribut fieldOfViewX und fieldOfViewY umschalten.

void App::PongLogic::AdjustCamera(const Logic::IState* state)
{
    const IAppConfiguration* appConfig = state->GetEngineConfiguration()->GetAppConfiguration();
    if (appConfig->HasDisplaySurfaceSizeChanged(mDisplaySurfaceSizeInspector))
    {
        UInt32 width = appConfig->GetDisplaySurfaceSizeX();
        UInt32 height = appConfig->GetDisplaySurfaceSizeY();
        if (Real(800.0 / 600.0) > (Real(width) / Real(height)))
        {
            mCamera->SetFieldOfViewX(400);
            mCamera->SetFieldOfViewY(0);
        }
        else 
        {
            mCamera->SetFieldOfViewX(0);
            mCamera->SetFieldOfViewY(300);
        }
    }
}

Auch für die korrekte Umrechnung der Mauskoordinaten verwenden wir die Kamera. Die X/Y-Werte des Mauszeigers liegen immer im Bereich von -1 bis +1. Für die Umrechnung haben wir bisher einfach den Wert der Y-Koordinate mit 300 multipliziert. Bei einem höheren Fenster wird auch der angezeigte Bereich der virtuellen Welt größer. Der Korrekturwert 300 stimmt also nicht mehr und muss entsprechend angepasst werden.

Für eine einfache Umrechnung zwischen Bildschirmkoordinaten und Weltkoordinaten stellt das Kamera-Objekt die Methoden GetWorldPositionFromScreen() und GetLocalPositionFromScreen() zur Verfügung. Als Parameter benötigen die beiden Methoden die X/Y-Werte und die Entfernung von der Kamera.

Zu beachten
Achtung: Die Entfernung muss in Blickrichtung der Kamera negativ angegeben werden, da im Standard-Koordinatensystem die Hauptrichtung der Kamera entlang der negativen Z-Achse verläuft, wie bereits im Cube-Tutorial erläutert wurde.

Für unser Beispiel lautet der Aufruf:

mCamera->GetWorldPositionFromScreen(posX, posY, -800);
tut0106_view_frustum_top.png
Draufsicht auf das Kamera-Frustum

Die Methode GetWorldPositionFromScreen() liefert immer die absoluten Weltkoordinaten, egal welche Blickrichtung oder Position die Kamera hat. Die Methode GetLocalPositionFromScreen() liefert die Koordinaten relativ zur Kamera.

GetWorldPositionFromScreen() multipliziert also die Werte von GetLocalPositionFromScreen() noch mit der Transformationsmatrix für die Kamera. Wenn die Kameraposition (0/0/0) und die Blickrichtung entlang der Z-Achse nach -∞ ist, liefern GetWorldPositionFromScreen() und GetLocalPositionFromScreen() dieselben Werte.

In unserem Fall ist es egal welche Methode wir verwenden. Die Kamera hat die Position (0/0/800) und blickt in Richtung -∞. Daher liefern beide Methoden dieselben Werte für die X/Y-Position. Mit folgendem Code können wir uns die Positionswerte ausgeben lassen:

        Real posX, posY;
        deviceHandler->GetMousePosition(posX, posY);
        Vector vectorLocal = mCamera->GetLocalPositionFromScreen(posX, posY, -800);
        Vector vectorWorld = mCamera->GetWorldPositionFromScreen(posX, posY, -800);
        Debug::Trace("world %f %f %f", vectorWorld.x, vectorWorld.y, vectorWorld.z);
        Debug::Trace("local %f %f %f", vectorLocal.x, vectorLocal.y, vectorLocal.z);

Das Ergebnis bestätigt die Annahme. Die X/Y-Werte sind identisch, die Z-Werte sind allerdings um 800 Einheiten verschoben.

world 239.799744 65.609344 0.000061
local 239.799744 65.609344 -799.999939
world -272.841034 300.000000 0.000061
local -272.841034 300.000000 -799.999939
world -400.000000 240.901489 0.000061
local -400.000000 240.901489 -799.999939

Aufgrund von Rechenungenauigkeiten, die immer beim Rechnen mit Gleitkommazahlen auftreten, sind die Werte für z nicht exakt 0 bzw. -800. Daher ist es in vielen Fällen sinnvoll den z-Wert nach der Umrechnung noch auf den tatsächlichen Wert zu korrigieren.

Vector vectorWorld = mCamera->GetWorldPositionFromScreen(posX, posY, -800);
vectorWorld.z = Real(0);

Da wir hier nur am Y-Wert interessiert sind, verwenden wir die Methode GetLocalPositionFromScreen() ohne Z-Wert-Korrektur.

    // Right Paddle Mouse
    if (deviceHandler->WasMouseMoved())
    {
        Real posX, posY;
        deviceHandler->GetMousePosition(posX, posY);
        Vector vectorLocal = mCamera->GetLocalPositionFromScreen(posX, posY, -800);
        //Vector vectorWorld = mCamera->GetWorldPositionFromScreen(posX, posY, -800);
        //Debug::Trace("world %f %f %f", vectorWorld.x, vectorWorld.y, vectorWorld.z);
        //Debug::Trace("local %f %f %f", vectorLocal.x, vectorLocal.y, vectorLocal.z);
        
        mPaddleRightPosY = vectorLocal.y;
        if (mPaddleRightPosY > Real(300)) 
            mPaddleRightPosY = Real(300);        
        if (mPaddleRightPosY < Real(-300)) 
            mPaddleRightPosY = Real(-300);

        mPaddleRightTransform->SetPositionY(mPaddleRightPosY);
    }

Version 5: Sound

Passende Sound-Dateien laden wir wieder von https://www.freesound.org herunter:

Die wav-Dateien liegen in einem 32Bit Float-Format vor und müssen daher vor der Verwendung in ein Integer-Format z.B. 16Bit PCM-Format konvertiert werden. Dafür eignet sich z.B. das freie Audiobearbeitungs-Programm Audacity (audacity.sourceforge.net).

In der Datei package.xml werden die neuen Dateien bekannt gemacht. Die übrigen Soundknoten werden wie im Audio-Tutorial in die Dateien graph_sound_instance.xml und graph_sounds.xml verpackt.

<?xml version="1.0" ?>

<Package id="package_main">
    
    <!-- Sound resources -->
    <Resource id="sfx_boing_paddle" fileName="sounds/sfx_boing_paddle.wav"/>
    <Resource id="sfx_boing_wall" fileName="sounds/sfx_boing_wall.wav"/>
    
    <!-- Graph resources -->
    <Resource id="graph_main" fileName="graph_main.xml"/>
    <Resource id="graph_line" fileName="graph_line.xml"/>
    <Resource id="graph_segment" fileName="graph_segment.xml"/>
    <Resource id="graph_mat" fileName="graph_materials.xml"/>
    <Resource id="graph_sound_instance" fileName="graph_sound_instance.xml"/>
    <Resource id="graph_sounds" fileName="graph_sounds.xml"/>
    
    <!-- Graph instances -->
    <Instance graphResourceId="graph_main"/>
    
</Package>

In der Datei graph_main.xml erzeugen wir eine Listener-Instanz sowie die Sound Instanzen und aktivieren den Listener mit einem ListenerState-Knoten:

    <Listener
        id="listener"
        viewId="view"
    />
    <ListenerState
        listenerId="listener"
    />
    
    <Instance graphResourceId="package_main:graph_sounds"/>

Zum Abspielen erstellen wir zwei TimelineNode-Objekte und zwei passende Methoden.

            Logic::TimelineNode mSFXBoingWall;
            Logic::TimelineNode mSFXBoingPaddle;
            void SFXPlayWall();
            void SFXPlayPaddle();
    AddGraphNode(mSFXBoingPaddle.GetReference(root, "sounds/sfx_boing_paddle/timeline"));
    AddGraphNode(mSFXBoingWall.GetReference(root, "sounds/sfx_boing_wall/timeline"));
void App::PongLogic::SFXPlayWall()
{
    mSFXBoingWall->Rewind();
    mSFXBoingWall->Start();
}

void App::PongLogic::SFXPlayPaddle()
{
    mSFXBoingPaddle->Rewind();
    mSFXBoingPaddle->Start();
}
void App::PongLogic::UpdateBallPosition(Logic::IDeviceHandler* deviceHandler, Double tickDuration)
{
    if (mGameIsPaused)
        return;
    
    mBallPosX += mBallDirectionX*mBallSpeed*tickDuration;
    mBallPosY += mBallDirectionY*mBallSpeed*tickDuration;
        
    // Intentional ignore ball width for collisionPosition
    // collisionPositionX = paddlePosition - paddleWidth/2 = 350 - 20/2 = 340
    if (mBallPosX >= 340) 
    {
        Real distance = mPaddleRightPosY - mBallPosY;
        if (Math::Abs(distance) <= 55)
        {
            SetAndNormalizeBallDirection(Real(-1),Real(-2)*distance/55);    
            mBallSpeed += Real(50);
            SFXPlayPaddle();
        }
        else
        {
            MissedBall();
        }
    }

    // Intentional ignore ball width for collisionPosition
    if (mBallPosX <= -340)
    {
        Real distance = mPaddleLeftPosY - mBallPosY;
        if (Math::Abs(distance) <= 55)
        {
            SetAndNormalizeBallDirection(Real(1), Real(-2)*distance/55);
            mBallSpeed += Real(50);
            SFXPlayPaddle();
        }
        else
        {    
            MissedBall();
        }
    }
    
    if (mBallPosY > 295)
    {
        mBallPosY = 590 - mBallPosY;
        mBallDirectionY *= -1;
        SFXPlayWall();
    }
    else if (mBallPosY < -295)
    {
        mBallPosY = -590 - mBallPosY;
        mBallDirectionY *= -1;
        SFXPlayWall();
    }

    mBallTransform->SetPositionX(mBallPosX);
    mBallTransform->SetPositionY(mBallPosY);
}

Version 6: Multitouch

Als letztes wollen wir noch Multitouch-Unterstützung hinzufügen. Das IAppConfiguration-Objekt stellt verschiedene Methoden zur Verfügung, um die Aktivierung und Deaktivierung des Multi-Touch-Gerätes zu überwachen und zu steuern.

Mit der Methode GetNumberOfTouchDevices() kann abgefragt werden, wie viele virtuelle Touch-Geräte existieren bzw. wie viele Finger vom Gerät gleichzeitig erkannt werden können. Die Methode GetTouchPosition() liefert die Touch-Position, wobei die X- und Y-Werte zwischen -1 und +1 liegen. Mit der Methode GetLocalPositionFromScreen() bzw. GetWorldPositionFromScreen() können diese Werte wieder in Kamera- bzw. Weltkoordinaten umgerechnet werden.

tut0106_input_coordinates.png
Touchscreen-Koordinatenbereich

Um das Spiel zu starten, sollen mindestens 3 Finger auf dem Touchscreen platziert werden:

    // Multi-Touch Game Start
    UInt32 numTouchPressed = 0;
    if (mGameIsPaused)
    {
        UInt32 numTouch = deviceHandler->GetNumberOfTouchDevices();
        for (UInt32 i = 0; i < numTouch; i++)
        {
            if (deviceHandler->IsTouchPressed(i))
                numTouchPressed++;
        }
    }
    
    // Game Pause
    if ((deviceHandler->WasRawKeyPressed(RAWKEY_SPACE)) ||
        (deviceHandler->WasMouseButtonPressed(IEnums::MOUSE_BUTTON_LEFT)) ||
        (numTouchPressed > 2))
    {
        mGameIsPaused = !mGameIsPaused;

        if (mGameIsPaused == false)
            if ((scoreLeft == 9) || (scoreRight == 9))
                ResetScore();
    }

Für die Steuerung des linken Paddles, soll das Touch-Device mit der kleinsten X-Position auf der linken Bildschirmhälfte verwendet werden. Analog wird das rechte Paddle mit dem Touch-Device mit der größten X-Position auf der rechten Bildschirmhälfte gesteuert:

    // Multi-Touch Left and Right Paddle
    UInt32 numTouch = deviceHandler->GetNumberOfTouchDevices();
    if (numTouch > 1)
    {
        Real posX = 0, posY = 0, actualPosXLeft = 0, actualPosXRight = 0;
        for (UInt32 i = 0; i < numTouch; i++)
        {
            if (deviceHandler->IsTouchPressed(i))
            {
                deviceHandler->GetTouchPosition(posX, posY, i);
                
                if ((posX < 0) && (posX < actualPosXLeft))
                {
                    actualPosXLeft = posX;
                    Vector vectorLocal = mCamera->GetLocalPositionFromScreen(posX, posY, -800);
                    mPaddleLeftPosY = vectorLocal.y;
                }
                if ((posX > 0) && (posX > actualPosXRight))
                {
                    actualPosXRight = posX;
                    Vector vectorLocal = mCamera->GetLocalPositionFromScreen(posX, posY, -800);
                    mPaddleRightPosY = vectorLocal.y;
                }
            }
        }
    }

Mit diesen Änderungen kann das Spiel auch mit Multitouch-Geräten gespielt werden.

Bildschirmausrichtung

Um sicherzustellen, dass das Bild auf Smartphones und Tablets auch im Querformat dargestellt wird, muss noch die erlaubte Bildschirm Orientierung auf Querformat (mit dem IAppConfiguration Objekt auf SCREEN_ORIENTATIONS_LANDSCAPE) festgelegt werden und Autorotation sowie das OrientationDevice aktiviert werden:

    if (platformConfig->IsTargetClassMatching(IEnums::TARGET_CLASS_COMPUTER))
    {
        appConfig->SetDisplaySurfaceSize(800, 600);
        appConfig->SetFullScreenEnabled(false);
    }
    else if (platformConfig->IsTargetClassMatching(IEnums::TARGET_CLASS_HANDHELD))
    {
        appConfig->SetAutoRotationActive(true);
        appConfig->SetOrientationActive(true);
        appConfig->SetAllowedScreenOrientations(IEnums::SCREEN_ORIENTATIONS_LANDSCAPE);
    }

Für Android muss die Bildschirmausrichtung auch im Makefile project/common/gnumake/module_pong_v6.mk angegeben werden, da die Ausrichtung unter Android nicht zur Laufzeit festgelegt werden kann (siehe auch Android Deployment):

MURL_ANDROID_SCREEN_ORIENTATION := landscape
tut0106_tablet.png
Pong V6 auf einem Android-Tablet

Target Deployment

Dieser Abschnitt beschreibt, wie das Pong-Spiel für die verschiedenen Zielplattformen aufbereitet werden kann.

Windows Deployment

Konfiguration

Bisher haben wir alle Projekte mit dem Debug-Profil gebuildet. Um eine Release-Version für Windows zu erstellen, muss das Compiler-Profil (Configuration) umgestellt werden. Verfügbare Profile sind Debug und Release.

tut0106_vs2010release.png
Visual Studio 2010 Project-Konfiguration

Ressourcen-Datei

Anschließend müssen wir die Projekt-Ressourcen-Datei den eigenen Bedürfnissen anpassen. Die Ressourcen-Datei hat die Endung .rc und befindet sich im Unterverzeichnis resources:

  • resources/v6/win32/pong.rc

Um die Datei in Visual Studio editieren zu können, markieren wir die Datei, drücken die rechte Maustaste und wählen den Befehl "View Code". Im ersten Abschnitt sind allgemeine Informationen über die Applikation definiert. Diese Informationen werden in der finalen Applikation übernommen und können z.B. über rechte Maustaste -> Eigenschaften -> Details angezeigt werden.

#define APP_TITLE       "pong_v3"
#define APP_VERSION     "1.0.0.0"
#define APP_VERSION_INT  1,0,0,0
#define APP_COMMENTS    "Authors: Spraylight GmbH"
#define APP_COMPANY     "Spraylight GmbH"
#define APP_COPYRIGHT   "2012 Spraylight"

Mit IDI_APP_ICON ICON "murl.ico" kann das Icon für die Applikation ausgewählt werden. In unserem Fall wird die Datei murl.ico als Icon verwendet. Das Icon wird als Grafikressource in die finale Applikation gepackt und als Programmicon angezeigt. Ein eigenes Icon kann z.B. mit der Standard-Windowsanwendung Paint erstellt werden.

IDI_APP_ICON    ICON    "../../../resources/common/win32/murl.ico"

Unter Windows werden Murl Ressourcen-Pakete bei Release-Versionen standardmäßig im Unterverzeichnis resources gesucht. Die Verzeichnisstruktur muss nach der Installation also so aussehen:

.../Pong.exe
.../resources/startup.murlpkg
.../resources/main.murlpkg

Alternativ können die Murl Ressourcen-Pakete mit RCDATA direkt der finalen Applikation hinzugefügt werden. Dadurch werden alle Ressourcen in eine Datei gepackt und es sind im Installationsverzeichnis abgesehen von der ausführbaren .exe-Datei keine weiteren Dateien notwendig.

Wir packen das startup und das main Paket in die finale Applikation.

//
// Game resources
//

#ifndef _DEBUG

startup.murlpkg   RCDATA "../../../data/v6/packages/startup.murlpkg"
main.murlpkg      RCDATA "../../../data/v6/packages/main.murlpkg"

#endif // _DEBUG

Weitere Informationen über das Format von RC-Files finden sich in der MSDN Dokumentation von Microsoft.

Build Solution

Im Menü können wir mit Build->Build Solution (F7) die finale Applikation erzeugen. Die Applikation befindet sich als .exe-Datei im Unterverzeichnis binaries/win32/ gefolgt von der Compiler-Version (vs2008/vs2010) und dem Namen des gewählten Profils (in unserem Fall Release).

  • binaries/win32/vs2010/Release/pong_v6.exe

Abgesehen von der ausführbaren .exe-Datei befindet sich auch noch eine .pdb-Datei (pong_v6.pdb) im Verzeichnis. Dabei handelt es sich um eine Programmdatenbankdatei, die Debug- und Projektzustandsinformationen enthält. Diese Datei wird zum Ausführen der Applikation nicht benötigt und kann ignoriert oder gelöscht werden.

  • binaries/win32/vs2010/Release/pong_v6.pdb

Die .exe-Datei ist ohne zusätzliche Programmpakete und ohne Installation lauffähig. Wenn gewünscht, kann natürlich ein Installer-Paket mit jeder beliebigen Installations-Software (wie etwa Inno Setup www.jrsoftware.org/isinfo.php) erzeugt werden.

Android Deployment

Konfiguration

Um eine Android-Version erstellen zu können, müssen wir zuerst das Makefile passend konfigurieren. Im Verzeichnis project/common gibt es ein gemeinsames Makefile für alle Plattformen, die Make als Build-Tool verwenden (z.B. Android, Linux, Rasperry):

  • project/common/module_pong_v6.mk

Die Datei kann einfach in einem Texteditor editiert werden und ist in logische Abschnitte unterteilt. Kommentare beginnen mit einem #-Zeichen.

Default Settings, Ausgabepfad und Ausgabedatei:

include $(MURL_MODULE_EXECUTABLE_BEGIN)
include $(MURL_MODULE_EXECUTABLE_DEFAULTS)
# ==== Output path and file ====
MURL_MODULE_BIN_PATH := binaries/$(MURL_PLATFORM)/$(MURL_BUILD)/v6/$(MURL_CPU)
MURL_MODULE_BIN_FILE := pong_v6
MURL_MODULE_BIN_NAME := Pong V6

Source-Code Dateien:

# ==== Source files ====
MURL_MODULE_SRC_PATH := source/v6
MURL_MODULE_SRC_FILES += pong_app.cpp
MURL_MODULE_SRC_FILES += pong_logic.cpp
MURL_MODULE_SRC_FILES += pong.cpp

Ressourcen-Verzeichnisse, die vor dem Erstellen der Applikation mit dem Resource Packer gepackt werden sollen:

# ==== Resources to pack ====
MURL_MODULE_RES_PATH := data/v6/packages
MURL_MODULE_RES_FILES += main.murlres

Ressourcen-Pakete (und andere Dateien), die in die Applikation gepackt werden sollen:

# ==== Packages to include ====
MURL_MODULE_PKG_PATH := data/v6/packages
MURL_MODULE_PKG_FILES += startup.murlpkg
MURL_MODULE_PKG_FILES += main.murlpkg

Verschiedene Android spezifische Angaben:

# ==== Android-specific ====
MURL_ANDROID_PACKAGE_NAME := at.spraylight.murl.pong_v6
MURL_ANDROID_VERSION_CODE := 1
MURL_ANDROID_VERSION_NAME := 1.0
MURL_ANDROID_TARGET_API_LEVEL := 10
MURL_ANDROID_MINIMUM_API_LEVEL := 10
MURL_ANDROID_PERMISSIONS :=
# MURL_ANDROID_PERMISSIONS += android.permission.INTERNET
# MURL_ANDROID_PERMISSIONS += android.permission.VIBRATE
# MURL_ANDROID_PERMISSIONS += android.permission.ACCESS_FINE_LOCATION
MURL_ANDROID_SCREEN_ORIENTATION := landscape
MURL_ANDROID_RESOURCE_PATH := resources/v6/android
MURL_ANDROID_RESOURCE_FILES += drawable/icon.png
# MURL_ANDROID_RESOURCE_FILES += drawable-land-nodpi/loader_background.png
# MURL_ANDROID_SPLASH_IMAGE := drawable/loader_background
include $(MURL_MODULE_EXECUTABLE_END)

Die angegebenen Android spezifischen Werte werden größtenteils direkt in die Android Manifest Datei übertragen. Die möglichen Angaben können in der Android Developer Dokumentation nachgelesen werden (siehe developer.android.com/.../manifest-intro.html).

Mit dem Parameter MURL_ANDROID_PERMISSIONS können die App-Berechtigungen definiert werden. Da für unser Spiel keine zusätzlichen Berechtigungen notwendig sind, geben wir das mit "MURL_ANDROID_PERMISSIONS := " explizit an. Die Zeile könnte aber genausogut weggelassen werden. Die weiteren, auskommentierten Zeilen dienen lediglich als Beispiel dafür, wie mehrere Berechtigungen definiert werden können.

Der Parameter MURL_ANDROID_SCREEN_ORIENTATION definiert die erlaubten Bildschirm-Orientierungen - in unserem Fall Querformat (siehe auch developer.android.com/...android:screenOrientation).

Splash Screen (optional)

Mit der optionalen Angabe von MURL_ANDROID_SPLASH_IMAGE kann ein Bild als "Splashscreen" während des Startvorgangs angezeigt werden. Mit dem Parameter MURL_ANDROID_SPLASH_SCALE_TYPE kann die Skalierung des Bildes bestimmt werden. Erlaubte Werte sind:

  • center
  • centerCrop
  • centerInside
  • fitCenter (default)
  • fitEnd
  • fitStart
  • fitXY

Siehe developer.android.com/.../ImageView.ScaleType.html für weitere Informationen.

Zu beachten
Achtung! Bei Splashscreen Änderungen sollte auch ein Android->Clean All ausgeführt werden, um sicher zu stellen, dass alle Dateien neu erstellt werden.

Ressourcen

Spezielle Android Ressourcen wie die Icon Datei (drawable/icon.png) oder das Bild für den optionalen Splashscreen (drawable-land-nodpi/loader_background.png) befinden sich im Verzeichnis:

  • resources/v6/android

Mit den Parametern MURL_ANDROID_RESOURCE_PATH und MURL_ANDROID_RESOURCE_FILES werden die Android Ressourcen spezifiziert. Die angegebenen Dateien werden direkt in das res-Verzeichnis der .apk-Datei kopiert. Für weitere Informationen über Android Ressourcen und die Möglichkeit verschiedene Ressourcen für verschiedene Bildschirmauflösungen und verschiedene Bildschirmgrößen bereitzustellen siehe developer.android.com/...AlternativeResources.

Build

Ein Android Build kann am einfachsten mit dem Tool Dashboard durchgeführt werden. Im Menü "Android" gibt es zahlreiche Kommandos um Android Apps als Debug- oder Release-Variante zu erstellen und auf Android Geräten zu installieren.

tut0106_dashbaord.png
Dashboard Android Release Build

Alternativ können auch die Shell Skripten im Verzeichnis project/android/gnumake verwendet werden.

Alle während des Build-Vorgangs erstellten Dateien werden im Verzeichnis project/android/gnumake/build gefolgt von der Konfiguration (debug oder release) gespeichert. Beispielsweise kann die erzeugte Android Manifest Datei für die Debug Konfiguration in folgendem Verzeichnis eingesehen werden:

  • project/android/gnumake/build/debug/apk/pong_v6/AndroidManifest.xml

Release Build, Signatur

Für einen Release-Build wird zusätzlich ein Signatur Schlüssel benötigt, welcher mit dem Java-Tool keytool erzeugt werden kann (siehe auch developer.android.com/.../app-signing.html). Am einfachsten geht das mit dem Dashboard und dem Menü-Befehl

  • Android->Build Release->Generate Signing Key

Der erstellte Schlüssel wird als .keystore-Datei im Verzeichnis project/android/gnumake gespeichert und bei einem Android Release-Build automatisch verwendet. Die Datei sollte sicher aufbewahrt werden, da alle Updates im App-Store mit demselben Schlüssel signiert werden müssen.

  • project/android/gnumake/murl_release.keystore

Wurde der Signatur Schlüssel erstellt, kann im Dashboard ein Android Release-Build durchgeführt werden. Nach dem Erstellen befindet sich die fertige App als .apk-Datei im Verzeichnis binaries/android/release/v6.

  • binaries/android/release/v6/pong_v6.apk

Die .apk-Datei kann z.B. in den Google Play Store hochgeladen und veröffentlicht werden.

Abgesehen von der .apk-Datei befinden sich auch noch die Unterverzeichnisse armeabi und armeabi-v7a mit "shared object" und "archive library" Dateien im Verzeichnis. Diese werden nicht benötigt und können ignoriert oder gelöscht werden.

iOS Deployment

Konfiguration

Für einen Release Build müssen wir die "Build Configuration" von Debug auf Release ändern:

  • Klick auf aktuelles Scheme: Edit Scheme -> Info -> Build Configuration -> Release
tut0106_xcodeios.png
Xcode Build Configuration

Ressourcen

In den Projekteinstellungen können im Abschnitt "General" die App Informationen wie Versionsnummer, Identifier, Device Orientations, Icons etc. angepasst werden. Alle Einstellungen werden automatisch in die Info.plist Datei übernommen. In unserem Fall ist das die Datei

  • resources/common/ios/murl-Info.plist

Alle weiteren iOS relevanten Ressourcen wie Icons, Launch-Image, Artwork Datei etc. liegen ebenfalls im Verzeichnis resources/common/ios. Siehe developer.apple.com/.../App-RelatedResources.html für weitere Informationen.

Während der Entwicklung wird normalerweise mit den Ressourcen-Verzeichnissen gearbeitet. Für den Release sollten aber unbedingt die gepackten Ressourcen-Pakete verwendet werden. Wir aktivieren daher die "Target Membership" für die Datei main.murlpkg und deaktivieren diese für das Verzeichnis main.murlres.

tut0106_xcodeios2.png
Xcode Target Membership
Zu beachten
Hinweis: Alle Bundle-Ressourcen können auch über Projekteinstellungen -> Build Phases -> Copy Bundle Resources geändert werden.

Build App

Mit dem Menü Befehl Product -> Build kann dann ein Release Build erstellt werden. Nach dem Erstellen befindet sich die fertige App als .app-Bundle im Verzeichnis binaries/ios/Release-iphoneos.

  • binaries/ios/Release-iphoneos/pong_v6.app

Die Ressourcen-Pakete sind im Bundle inkludiert und können mit dem Menü-Befehl Paketinhalt zeigen überprüft werden.

Ist ein iOS Entwickler Gerät (provisioned for development) am Computer angeschlossen, kann durch Auswahl des Geräts und drücken des Run-Buttons ("Build and then run the current scheme") die App kompiliert und direkt auf das Gerät installiert werden.

Eine Murl App kann, gleich wie jede andere iOS App, direkt mit Xcode in den Apple App Store hochgeladen werden. Genauere Informationen über die Distribution im Apple App Store findest du in der App Distribution Guide von Apple.

Mac OS X Deployment

Der Build Prozess für OS X ist dem Build Prozess für iOS sehr ähnlich.

Konfiguration

Wie schon für iOS, muss auch für einen OS X Release-Build die "Build Configuration" von Debug auf Release geändert werden:

  • Klick auf aktuelles Scheme: Edit Scheme -> Info -> Build Configuration -> Release

Ressourcen

In den Projekteinstellungen können im Abschnitt "General" die App Informationen wie Versionsnummer, Identifier, Icons etc. angepasst werden. Alle Einstellungen werden automatisch in die Info.plist Datei übernommen. In unserem Fall ist das die Datei

  • resources/common/osx/murl-Info.plist

Auch das App Icon befindet sich im selben Verzeichnis (resources/common/osx/Icon.icns) und kann bei Bedarf angepasst werden.

Zusätzlich gibt es im Verzeichnis resources/v6/osx noch die Datei Credits.rtf. Die Datei beinhaltet die Informationen, welche in der About-Box der Applikation angezeigt werden.

Während der Entwicklung wird normalerweise mit den Ressourcen-Verzeichnissen gearbeitet. Für den Release sollten aber unbedingt die gepackten Ressourcen-Pakete verwendet werden. Wir aktivieren daher die "Target Membership" für die Datei main.murlpkg und deaktivieren diese für das Verzeichnis main.murlres.

tut0106_xcodeosx.png
Xcode Target Membership
Zu beachten
Hinweis: Alle Bundle-Ressourcen können auch über Projekteinstellungen -> Build Phases -> Copy Bundle Resources geändert werden.

Build App

Mit dem Menü Befehl Product -> Build kann dann ein Release Build erstellt werden. Nach dem Erstellen befindet sich die fertige Applikation als .app-Bundle im Verzeichnis binaries/osx/Release.

  • binaries/osx/Release/pong_v6.app

Die Ressourcen-Pakete sind im Bundle inkludiert und befinden sich im Unterverzeichnis Contents/Resources. Mit dem Menü-Befehl Paketinhalt zeigen kann geprüft werden, ob auch die richtigen Ressourcen-Pakete inkludiert wurden.

Abgesehen von der .app-Datei befindet sich auch noch eine .app.dSYM-Datei im selben Unterverzeichnisse. Diese Datei beinhaltet die Debug-Symbole, wird zum Ausführen der Applikation nicht benötigt und kann ignoriert oder gelöscht werden.

Information über die Distribution im Mac App Store findest du in der Mac Developer Dokumentation auf der Seite Submitting to the Mac App Store.


Copyright © 2011-2018 Spraylight GmbH.