Mit der Veröffentlichung von iOS 7 und das damit verbundene Xcode 5 hat nun Jeder die Möglichkeit mit Xcode-Bordmitteln einfache 2d Spiele zu entwickeln. Kritiker mögen behaupten, dass die Qualität der im AppStore angebotenen Spiele darunter leiden könnte, wenn Jeder Hinz und Kunz nun seine Spiele anbietet. Oder, dass erfolgreiche Projekte wie Cocos2d von Apple adaptiert werden und somit deren Existenz bedrohen. Ich denke dennoch, dass es generell gut ist, Jedem die Möglichkeit zu bieten seine Ideen umzusetzen. Das wird die (App)Welt nur bereichern.
Wenn man nun mit Xcode 5 ein neues SpriteKit Game erstellt, sieht das Ganze erst mal recht vielversprechend aus.

Man bekommt mit dem Template ein Setup für ein Projekt, auf dem sich sehr gut aufbauen lässt. Die vorhandenen Methoden in der MyScene.m leuchten ein: was passiert wenn ich eine Touch-Geste ausführe (touchesBegan Methode), was passiert jede Runde wenn der Game Loop durchläuft (update Methode), etc. Leider bekommt man mit diesem Template Projekt eine Scene mit falschen Dimensionen. Das ist ein Problem, welches am Besten zu Beginn eines Projekts gelöst werden sollte. Bei der späteren Anpassung der Dimensionen einer Scene und des Grid-Systems werden die Child-Nodes entsprechend skaliert. Dadurch stimmen die Größenverhältnisse nicht mehr und man müsste von vorn beginnen mit den Größenangaben und der Positionierung seiner Elemente. Eine schöne Tortur, wenn man die Grafiken für das Artwork bereits fertiggestellt hat.

Das Problem

Im Detail betrachtet werden in der Methode viewDidLoad die Bounds der Orientierung im Portrait Modus wiedergegeben. Auch wenn sich die neue SpriteKit App im Landscape Modus befindet und definiert wurde. Die Standard-Dimensionen im Landscape Modus betragen 1024×768. Aber egal wie man das Device dreht um die Orientierung zu verändern, der view wird immer “behaupten” er sei 768×1024:

Die Koordinaten des Portrait Modus werden also verwendet, auch wenn die Darstellung im Landscape Modus ist. Es gestaltet sich sehr schwierig neue Objekte zu positionieren, wenn der Ursprung des Grid-Systems unten links (0,0) ist, diese Koordinate aber nicht im sichtbaren Bereich liegt. Die Koordinaten die also zum setzen und auslesen von Positionierungen im Code zur Verfügung stehen, unterscheiden sich von dem, wie sie tatsächlich dargestellt werden. Um nun z.B. ein Sprite am dargestellten Ursprung, also am linken unteren Rand eines iPads zu positionieren muss die Position mit 0 und 224 angegeben werden.

Um vernünftig programmieren zu können, wird es schwer, sich mit solchen Werten zu arrangieren. Insbesondere wenn relative Positionierungen zum gesamten View ausgerichtet werden sollen. Als temporären Lösungsversuch könnte man mit der Skalierung experimentieren (SKSceneScaleModeAspectFill, etc), oder fixe Dimensionen verkehrt herum setzen:

Spätestens, wenn man die App aber für das iPad und zusätzlich für das iPhone entwickeln möchte, müssen für jedes Device komplett eigene Lösungen programmiert werden. Die Positionierungen von Elementen wird unverständlich sein, da die Positionsangaben nie im Verhältnis zu Breite und Höhe mit dem View übereinstimmen werden.

Die Lösung

anstatt der initialen Methode im ViewController viewDidLoad sollte viewWillLayoutSubviews verwendet werden. Damit gibt die Nutzung der bounds Attribute die korrekten Werte wieder:

Das liegt daran, dass zum Zeitpunkt des Aufrufs vom System der viewDidLoad Methode noch kein View in der Aufruf-Hierarchie hinzugefügt wurde. Deshalb kann der View auch nicht an die Orientierung des Geräts angepasst und resized werden. Wenn wir einige Ebenen später in die Hierarchie “einsteigen”, sind alle Views mit den benötigten Attributen wie etwa Größe und Orientierung vorhanden.

Nun können alle Objekte korrekt Positioniert werden und die Darstellung unterscheidet sich nicht mehr von den verwendeten Koordinaten. Ich freue mich immer über Kommentare oder Vorschläge für neue Artikel.

3 Kommentare

  1. Hallo Thomas, leider bin ich da auch nicht fündig geworden.
    Habe zur Zeit nur eine Notlösung über 3 ViewController.
    (der Root Viewcontroller startet automatisch einen 2. VC in Portrait und wenn ich die nächste Scene in Landscape haben will springt der 2. VC zurück zum RootViewcontroller und der startet dann den 3. VC und das ganze auch wieder umgekehrt).
    Das ganze mache ich über Notification.
    Habe damit aber große Probleme beim pausieren einer Scene über das AppDelegate.
    Die Scene bleibt nicht aktiv, es wird automatisch der RootViewController angesprungen. Grummel.
    Leider stehe ich mit den Problem wohl alleine da 🙁
    trotzdem Danke für deine Antwort.

    Mit freundlichen Grüßen
    Kayne

    Antworten
  2. Hallo Thomas , erst einmal danke für das tolle Tutorial.

    Ich habe auch gleich mal eine Frage dazu. Angenommen ich hätte zwei SKScene Objekte. Die erste SKScene soll nur die Portrait Ansicht unterstützen und die zweite SKScene soll nur die Landscape Ansicht unterstützen und eigentlich von der ersten SKScene gestartet werden. Kann man das so umsetzen, oder geht das nur mit zwei ViewController.

    Viele Grüsse Kayne

    Antworten
    • Thomas Zinnbauer

      Hi Kayne,
      kann ich auf Anhieb gar nicht sagen. Aber ein Orientation-Change-Problem gab es bestimmt schon einmal. Ich bin mir sicher, dazu findet sich was z.B. auf stackoverflow.com

      Antworten

Einen Kommentar abschicken

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *