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
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);
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.
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:
https://www.freesound.org/people/NoiseCollector/sounds/4391/
https://www.freesound.org/people/NoiseCollector/sounds/4361/
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.
config->SetMultiTouchActive(Bool enable)
config->IsMultiTouchActive()
config->HasMultiTouchActiveChanged(ChangeInspector& inspector)
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.
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
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.
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:
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:
Source-Code Dateien:
Ressourcen-Verzeichnisse, die vor dem Erstellen der Applikation mit dem Resource Packer gepackt werden sollen:
Ressourcen-Pakete (und andere Dateien), die in die Applikation gepackt werden sollen:
Verschiedene Android spezifische Angaben:
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.
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
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
.
- 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
.
- 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.