Friday, September 5, 2014

Coefest.at Blog-Eintrag: Spiele-Entwicklung mit Unity und Visual Studio 2013

Ein Unity Spiel entwickelt mit Visual Studio 2013

Die Indie-Spieleentwicklung für die Microsoft Plattform hat mittlerweile auch schon eine längere Geschichte. Als damals das XNA Game Studio auf dem Markt erschienen ist, war das für alle Programmierer, die schon immer Ihr eigenes Spiel entwickeln wollten, sensationell. Monogame hat dann quasi die Weiterentwicklung, bzw. eher die Neuentwicklung von XNA fortgesetzt und hat es uns ermöglicht die Spiele mit Xamarin auch auf iOS und Android zu bringen. Tja, und was soll ich sagen, seit ca. 2 Jahren wird die Geschichte der Spieleentwicklung durch Unity quasi neu geschrieben.
Es macht unheimlichen Spaß sein eigenes Spiel mittels Unity zu entwickeln. Die Scripts wurden zu Beginn noch in JavaScript geschrieben, mittlerweile klappt das hervorragend in C#. Die IDE, die dafür verwendet wurde war bisher MonoDevelop. Das war schon sehr, sehr nett, allerdings ist es für einen eingefleischten C# Entwickler doch noch eine nette Zugabe, wenn das auch in und mit Visual Studio funktioniert.
Und was soll man noch mehr sagen? Das klappt. Dank Microsoft. Durch den Zukauf der Firma SyntaxTree, das ist jene Firma, die ein Produkt mit dem Namen UnityVS entwickelt hatte.
Und jetzt ist es soweit, Visual Studio Tools für Unity wurden durch eben diesen Zukauf von Microsoft released. Ein guter Grund sich das einmal genauer anzusehen. Ich dachte mir ich baue ein kleines Spielchen für zwischendurch.

Das Unity VS 2013 Spiel

Die Idee ist eigentlich recht einfach:
· Wir sehen einen Windows Store.
· Da regnet es Windows Phone’s vom Himmel herunter.
· Diese können mit einer Microsoft Tasse aufgefangen werden, das gibt dann Bonuspunkte.
· Aber leider fallen da auch „böse“ iPhones herunter, werden diese gefangen, dann werden natürlich Punkte abgezogen, da diese der Microsoft Tasse nun einmal gar nicht schmecken.
Probespielen ist jederzeit in eurem Browser möglich: http://hambogames.azurewebsites.net/phoneCatcher.html
image
Ich hoffe ich habe euch etwas neugierig gemacht? Nun denn, lasst uns loslegen.
Für diejenigen, die es gar nicht erwarten können, gibt es den Source Code hier zu finden:
https://github.com/BerndtHamboeck/PhoneCatcher

Implementierung

Um das kleine Spielchen mit mir „mitzuprogrammieren“ benötigt Ihre folgendes:
· Einen Rechner mit Visual Studio 2010, 2012, oder 2013(ich verwende 2013 Update 3).
· Und natürlich eine Installation von Unity, die Testversion erfüllt durchaus unsere Zwecke, diese bekommt Ihr hier:https://unity3d.com/unity/download, ich verwende in diesem Beispiel die Version 4.5.2.
· Visual Studio Tools für Unity von hier: http://unityvs.com/, diese bitte herunterladen und installieren.
· Da Unity einen älteren C#-Compiler verwendet benötigt Ihr auch den .NET Framework 3.5 (wichtig, falls Ihr schon unter Windows 8/8.1 entwickelt).
Das war es dann auch schon, wir können loslegen.

Notwenige Schritte

Damit ihr gleich einmal vorab sehen könnt, was euch in den folgenden Abschnitten erwartet hier eine kleine Zusammenfassung:
· Schritt eins:
o Erstellen des Unity Projektes
o Anlegen der Projektstruktur
o Importieren der Game-Sprites
· Schritt zwei:
o Aufbau der Spielansicht
   Hintergrund-Bild hinzufügen
   Microsoft-Tasse zum Auffangen hinzufügen
   Physik hinzufügen
o Erstes Scripting
   Konfiguration der Visual Studio 2013 Unity Integration
   Erstellen des C#-Scripts zum Bewegen der Tasse
   Implementieren des Scripts
   Debuggen mit Visual Studio 2013
· Schritt drei:
o Fallende Windows Phones und iPhones
o Auffangen der Game Sprites
o Zerstören gefangener, bzw. ‚verlorener‘ Game Sprites
o Re-Spawn neuer Telefone
· Schritt vier:
o Punkte für gefangene Windows Phones
o Abzüge für gefangene iPhones
o Ende des Spiels nach N-Sekunden
o Neustart des Spiels
Das klingt nach einer Menge Arbeit, ist aber mit etwas Geschick, und, falls Ihr Unity schon ein wenig kennt, in circa 2h zu schaffen. Bei Probleme könnt Ihr jederzeit im fertigen Source Code nachsehen.
Erstellen des Unity Projektes
Los geht’s, wir öffnen Unity um ein neues Spiel zu erstellen. Ich bin schon richtig aufgeregt, ich hoffe euch geht es genauso!
Dazu müssen wir:
· Unity öffnen
· Im Project Dialog von Unity ein neues Projekt erstellen mit folgenden Einstellungen
   o Project Location: C:\GameDev\PhoneCatcher
   o Setup Defaults for: 2D
imageimage
Die Einstellung für 2D ist von Vorteil, da es sich ja auch um ein 2D Spiel handeln wird und Unity einige Einstellungen dann bereits vorab für uns (und 2D) optimiert.
Sehr gut, das Projekt sollten wir also nun erstellt haben, als nächstes wollen wir die Dateistruktur anlegen und die Grafiken einbinden, die wir in diesem Spiel verwenden wollen.
Anlegen der Projektstruktur
Da wir einige Dateien im Zuge der Entwicklung des Spiels anlegen werden, empfiehlt es sich eine vernünftige Projektstruktur anzulegen. Wir benötigen in diesem Projekt folgende Ordner:
· Prefabs – wir lassen Objekte den Bildschirm herunter regnen. Diese haben nicht nur ein Aussehen, damit diese fallen können, müssen diese Objekte auch den Regeln der Physik gehorchen – also sprich: fallen. Und nicht nur das, wenn diese kollidieren, dann müssen wir reagieren (es gibt dann Punkte, oder eben nicht). Also lange Rede kurzer Sinn, was sind Prefabs? Objekte, die mehr können als nur hübsch aussehen, also zusammengesetzte Objekte, die wir als einzelnes (neues) Objekt verwenden können.
· Scenes – das sind die einzelnen Screens, die in dem Spiel verwendet werden. Wir erstellen dieses Mal nur einen Screen, aber falls weitere dazukommen, hat schon alles seine Ordnung.
· Scripts – Programmieren wird uns in diesem Spiel nicht erspart werden, und schließlich ist es ja genau das, was einem bei der Spiele-Entwicklung am meisten Spaß macht. Der Code, der plötzlich die „Puppen tanzen lässt“ (bei uns sind es aber Texturen von Mobiltelefonen).
· Sprites – Herr, lass Telefone regnen. Die wir auffangen wollen, ach und einen Hintergrund benötigen wir auch noch. Tja, und diese Texturen kommen in diesen Folder. Geht ganz einfach, machen wir dann gleich als nächstes.
Am Ende sollte das in der „Project“-Ansicht dann so aussehen:
image
Das Vorgehen ist immer das Gleiche, ich zeige es euch für den Sprites-Folder. Ihr klickt mit der rechten Maustaste auf Assets und wählt Create -> Folder.
image
Jetzt gebt Ihr den gewünschten Namen ein image. Das führt Ihr bitte für alle Folder aus, bis alle vorhanden sind. Den Sprites Folder wollen wir gleich einmal befüllen.
Importieren der Game-Sprites
Welche Texturen benötigen wir denn nun genau? Ich würde vorschlagen, wir verwenden für dieses Spiel folgende:
· „Gute“ Telefone, Windows Phones
· „Böse“ Telefone, iPhones
· Einen Becher, mit dem wir die Telefone fangen können
· Einen Hintergrund, damit das Ganze nicht so langweilig aussieht.
Ich habe für euch einige Texturen vorbereitet, wie Ihr weiter unten sehen könnt.
image
Ihr könnt diese aus dem Verzeichnis https://github.com/BerndtHamboeck/PhoneCatcher/tree/master/Assets/Sprites einzeln abspeichern, bzw. aus dem Zip von github herauskopieren. Wie auch immer ihr euch entscheidet, anschließend zieht ihr diese in den Sprites Folder, das müsste dann so aussehen:
image
Sehr gut, das Spiel nimmt langsam etwas Formen an, sicherheitshalber speichern wir die momentane (noch leere) „Scene“ gleich einmal ab, solltet Ihr eine kurze Pause benötigen, dann könnt Ihr das Projekt danach jederzeit wieder laden. Das geht ganz schnell, durch File -> Save Scene und wir geben dieser den Namen ‚Main‘. Sieht am Ende so aus:
image
Gut, jetzt wollen wir unsere Spielszene einmal mit Objekten bevölkern.

Aufbau der Spielansicht

Beginnen wir mit dem Einfügen der Microsoft Tasse. Dazu zieht ihr diese aus der Assets -> Sprites Folder Ansicht in die „Hierarchy“ Ansicht. In dieser Ansicht sind alle Objekte angezeigt, die in der momentanen Scene verwendet werden. Bisher ist dort nur die „Main Camera“ zu sehen.
Die Kamera ist dafür da, dem Spieler eure Szene zu zeigen. Dazu kann die Kamera statisch über der Szene angebracht sein (wie in diesem Spiel) oder z. B. einem Charakter folgen, wie das z. B. in einem Jump & Run der Fall wäre. In einer Szene können auch mehrere Kameras vorhanden sein, diese können auch gleichzeitig im Spiel angezeigt werden, wie das in einem Spiel mit Split-Screen der Fall wäre, oder z. B. in einem Rennspiel für eine kleine Übersichtskarte der Rennstrecke.
image
Die Kamera, ist nun nicht ideal positioniert, die Tasse sollte weiter unten zu sehen sein. Wir wollen die Positionierung dieser nun im „Inspector“ noch individuell anpassen. Und zwar wählen wir dafür die „Main Camera“ in der Hierarchy an und ändern die Y Position auf 4.5.
image
Im nächsten Schritt fügen wir das Hintergrundbild hinzu. Dazu ziehen wir wiederum aus Assets -> Sprites den Background in die „Hierarchy“ Ansicht.
image
Das Resultat ist, nun ja, nennen wir es, durchwachsen. Das Bild ist nicht ganz passend, darum sollten wir die Größe und Positionierung anpassen. Dazu gibt es 2 Möglichkeiten, die erste Variante ist, dass der Background Sprite angewählt wird und im Inspector der Wert „Pixels to Units“ angepasst wird. Achtung, erst nachdem Apply gedrückt wird, wird die Änderung sichtbar. Ihr könnt gerne mit den Werten experimentieren, 150 dürfte für unsere Belange genügen.
imageimage
Die zweite Variante wäre gewesen, das Background Objekt in der Scene anzuwählen und die Eigenschaften im Abschnitt „Scale“ anzupassen. Das ist nun nicht mehr notwendig könnt ihr aber ebenfalls gerne einmal ausprobieren. Ihr solltet die Position so anpassen, dass der Hintergrund passend in der Scene zu sehen ist.
image
Soweit so gut, jetzt ist allerdings unsere Tasse verschwunden. Wir sollten Unity mitteilen, in welcher Reihenfolge Objekte zu zeichnen sind. Dafür gibt es den „Sorting Layer“, der steht auf „Default“ (im oberen Bild sehr schön zu sehen), sowohl am Background Objekt, als auch an der Tasse. Um weitere Layer für die Sortierung einzufügen, klickt auf die kleinen Pfeilchen rechts neben Sorting Layer im Sprite Renderer und wählt „Add Sorting Layer“.
image
Wir benötigen zwei weitere Layer. Einen für den Hintergrund (das Hintergrundbild) und einen weiteren für den Vordergrund (die Tasse).
image
Sobald die beiden Layer angelegt wurden, sollten diese zugewiesen werden, dazu wählt ihr zuerst den Background an und weist den „Background“ Layer für die Eigenschaft Sorting Layer zu.
imageimage
Das Gleiche für die Tasse, allerdings den „Foreground“ Layer als Sorting Layer.

Physik ermöglichen

Damit sich Objekte physikalisch realistische verhalten können, steht in Unity für 2D Spiele die Rigidbody2D-Komponente zur Verfügung. Solche ‚gepimpten‘ Objekte sind in einer Szene nicht statisch, sondern fallen von oben nach unten, oder können durch Kollisionen, bzw. Scripte bewegt werden. Und das ist es eigentlich, was wir mit unserer Tasse machen wollen. Diese von links nach rechts bewegen, das herunterfallen müssen wir unterbinden, das bleibt dann später unseren Telefonen vorbehalten.
imageimageimage
Wir aktivieren die Eigenschaft „Is Kinematic“, diese deaktiviert die Physik des Rigidbodies, das Objekt kann nur noch über ein Script bewegt werden. Das ist genau was wir wollen, wir kümmern uns selbst um die Bewegung des Bechers, nämlich darf er sich nach links, oder rechts bewegen.
image
Der nächste Schritt ist nun das Script zu erstellen, welches auch wirklich die Bewegung ausführt.

Erstes Scripting mit Visual Studio 2013

Unity installiert mit MonoDevelop einen eigenen Code-Editor mit. Das ist an und für sich schon toll, wir wollen allerdings Visual Studio verwenden, dazu muss wie eingangs erwähnt Visual Studio Tools für Unity installiert sein, das war es aber noch nicht ganz. Es sind noch weitere Schritte notwendig.

Konfiguration der Visual Studio 2013 Unity Integration

Zuerst muss eine Plattform unter der das Spiel abläuft eingestellt werden, wo die Kommunikation zwischen Unity und dem Editor - also Visual Studio – reibungslos funktionieren kann, das ist als Plattform image. Der Web Player beispielsweise klappt nicht reibungslos, also führen wir diese Einstellungen gleich einmal durch, das macht Ihr über File->Build Settings und wählt PC, Mac & Linux Standalone, Target Plattform ‚Windows‘, aktiviert Development Build und Script Debugging. Abschließend schließt ihr einfach das Fenster.
imageimage
Im zweiten Schritt müsst Ihr das Visual Studio Unity Package installieren. Mit Unity Packages bietet Unity ein Werkzeug, um einzelne Dateien, Objekte, Texturen, bis hin zu ganzen Scenes zu bündeln, die man dann z. B. an jemanden versenden und dann wieder in ein anderes Projekt importieren kann. Und genau das haben die Kollegen von SynaxTree für uns getan, die Visual Studio Integration wird durch ein Unity Package unterstützt und dieses müssen wir zu unserem Projekt hinzufügen. Das geht mit Asset -> Import Package -> Visual Studio 2013 Tools, im neuen Fenster klickt Ihr auf Import.
imageimage
Das war es schon, ob es geklappt hat, sehen wir im nächsten Schritt, wenn wir ein Script erstellen und dieses sich in Visual Studio 2013 öffnen soll.

Skript erstellen

Das Script ist in Unity erst einmal nichts weiter als eine weitere Komponente. Ihr wählt also die Tasse an und drückt im Inspector wieder den imageButton. Die Komponente ist ein „New Script“ mit dem Namen MSCupLogoController und als Sprache verwenden wir CSharp (und nicht JavaScript).
imageimage
Ich würde euch empfehlen das Script in den Scripts-Folder mit Drag & Drop zu verschieben, das Script landet per Default im Assets Folder. Das sollte bei euch dann so aussehen, wie in dem folgenden Bild: Links ist der Scripts Folder angewählt, darin ist unser Script (mittlerer Pfeil) und rechts sieht man, dass das Script aktiv ist und der Tasse zugewiesen ist.
image
Nun wird es spannend, klickt mit der Maus doppelt auf das Script (mittlerer Pfeil). Wenn alles richtig konfiguriert ist, dann sollte sich Visual Studio mit dem von uns erstellten Script öffnen.
imageimage
Solltet Ihr diese Meldung sehen (nachfolgend), dann habt Ihr das Unity Package für Visual Studio nicht hinzugefügt, diese Meldung würde auch erscheinen, wenn Ihr ein anderes Unity Projekt – möglicherweise ein Beispielprojekt aus dem Internet, oder ein komplett neues (wie in meinem Fall auf dem Bild) – öffnet:
image
Die Integration mit Visual Studio könnt Ihr bei Bedarf auch wieder ausschalten, und zwar über das Menü Visual Studio Tools -> Configuration, dann wird wieder MonoDevelop als IDE verwendet.
imageimage
Ich gehe einmal davon aus, dass die Integration zwischen Unity und Visual Studio klappt und Ihr nun vor einem Script-Editor in Visual Studio sitzt. Ok, dann wollen wir die Tasse bewegen!

Implementieren des Scripts

In diesem Script werden wir zwei Methoden implementieren:
· Start – diese wird von Unity für jedes Objekt einmal aufgerufen, hier wollen wir uns die Bildschirmgröße merken, sowie die Breite der Tasse (das Script ist der Tasse zugewiesen, dadurch ist der Zugriff sehr einfach möglich) von der maximalen Breite abziehen. Warum? Erfahrt Ihr im nächsten Schritt.
· FixedUpdate – wird periodisch zu einem fixen Intervall aufgerufen und ist dafür gedacht physikalische Objekte anzupassen. Wir wollen die x-Achsenposition anpassen, passend zur Mausposition. Allerdings darf die Tasse dabei nicht aus dem Bild herausfallen, also die maximale Breite nicht über, bzw. unterschreiten (und darum das Abziehen von der Breite der Tasse, diese würde sonst aus dem Blickfeld des Spielers verschwinden).
Das Script sollte im Endeffekt so wie folgt aussehen, ich habe eine public Variable mit dem Namen cam eingefügt. Public Variablen können in Unity selbst, z. B. durch Drag & Drop zugewiesen werden. Ich benötige diese Variable eigentlich nicht (es ist die aktuelle Kamera), sie wird sich aber im nächsten Schritt als sehr nützlich erweisen.
using UnityEngine;
public class MSCupLogoController : MonoBehaviour
{ 
    public Camera cam;
    private float maxWidth;
    void Start ()
    {
        if (cam == null)
            cam = Camera.main;
 
        var corner = new Vector3(Screen.width, Screen.height, 0f);
        var targetWidth = cam.ScreenToWorldPoint(corner);
        float playerWidth = renderer.bounds.extents.x;
        maxWidth = targetWidth.x - playerWidth;
    } 
    void FixedUpdate ()
    {
        var currentPosition = cam.ScreenToWorldPoint(Input.mousePosition);
        float targetWidth = Mathf.Clamp(currentPosition.x, -maxWidth, maxWidth);
        var newPosition = new Vector3(targetWidth, 0f, 0f);
        rigidbody2D.MovePosition(newPosition);
    }
}
Ihr müsst in Visual Studio nichts kompilieren, sobald Ihr zu Unity zurück wechselt wird automatisch für euch übersetzt und etwaige Fehler werden sofort angezeigt. Ihr könnt auch z. B. in Unity kontrollieren, ob die public Variable cam im Editor auftaucht:
image
Ich hoffe euer Code ist fehlerfrei, denn wir wollen gleich einmal sehen, wie die Tasse über den Bildschirm gleitet (zumindest von links nach rechts).
Starten des Spiels
Am Besten versuchen wir es gleich einmal. Wechselt zurück zu Unity und drückt den Play Button.
image
Und es sollte losgehen (wobei das Ergebnis noch nicht wirklich den Anspruch erhebt ein Welthit zu werden). Die Ansicht wechselt auf „Game“ – von zuvor „Scene“. Mit der Maus könnt Ihr die Tasse von links nach rechts bewegen, diese fällt dabei nicht aus dem Bild (weder links, rechts, noch oben, oder unten).
image
Wichtig ist, dass Ihr nun keine Änderungen an Objekten vornehmt, da diese Änderungen nur während der Laufzeit Gültigkeit hätten. Das ist zwar praktisch, um Änderungen sofort im Spiel zu sehen, aber wie gesagt, diese würden verloren gehen, wenn ihr den Stopp Button drückt (der das Spiel beendet). Solltet ihr also etwas anpassen wollen, weil euch beim Testen des Spiels etwas auffällt, bitte immer zuerst den Stopp Button drücken und erst danach die Änderungen durchführen, dann bleiben diese natürlich erhalten.

Debuggen mit Unity

Visual Studio ist wie Ihr bestimmt wisst ein sehr mächtiges Werkzeug und eines der Stärken ist der eingebaute Debugger. Um diesen mit Unity zu nutzen, setzt Ihr am Besten einmal einen Breakpoint (die if-Abfrage für die cam Variable bietet sich an) und danach imagedrücken.
image
Nun wechselt ihr zurück zu Unity und drückt den Play Button.
image
Das Spiel startet und ihr läuft auf den Breakpoint, setzt das Programm fort (F5-Taste) und ihr könnt wieder die Tasse bewegen.
image
Die Tasse bewegt sich, jetzt wollen wir uns der herabfallenden Telefone widmen, diese müssen erzeugt, gefangen und zerstört werden.

Fallende Windows Phones und iPhones

Jetzt wollen wir einmal etwas “Action” in unser Spiel bringen. Dazu ziehen wir einmal eines der Telefone aus dem Assets->Sprites Folder auf den Hierarchy Bereich. Damit wird das Telefon zu unserer Spielscene hinzugefügt.
Ich habe im Inspector folgende Werte angepasst:
· Damit das Windows Phone am oberen Bildschirmrand angezeigt wird - Y: 8
· Die Textur der Telefone ist etwas zu gross, das wollen wir kleiner haben, ähnlich wie bei dem Hintergrund, dieses Mal aber mit der anderen Variante, nämlich durch setzen von Scale Y - Y: 0.75
· Das Telefon muss auf den richtigen Layer – so wie die Tasse zuvor, also Sorting Layer: Foreground
Jetzt müssen wir dem Telefon wieder das Fallen beibringen, also fügen wir wieder einen RigidBody 2D zu dem Telefon-Objekt hinzu. Wieder, so wie (zuvor an der Tasse) mit dem imageButton im Inspector am Telefon-Objekt.
imageimage
Am Besten ihr versucht gleich wieder einmal das Spiel zu starten, das Telefon wird herunterfallen, wenn ihr versucht das Telefon mit der Tasse zu fangen wird aber noch nichts passieren, das Telefon fällt vor eurer Tasse herunter, das sieht so aus, wie in dem Bild links, sollte aber so aussehen, wie im Bild rechts:
imageimage
Damit das Telefon hinter der Tasse erscheint, setzen wir den Wert an dem Tassen-Objekt für Order in Layer: 1
image

Auffangen der Game Sprites

Die Schwerkraft haben wir jetzt auch schon einmal in unserem Spiel kennen gelernt. Jetzt wollen wir Objekte miteinander kollidieren lassen. Und zwar unsere Telefone mit der Tasse, oder besser gesagt - in Unity-Sprache - zwei Rigidbodies, die aufeinanderstoßen (beim Auffangen, also wenn das Telefon in der Tasse verschwindet). Beim „Zusammenprall“ sollen diese ein physikalisch korrektes Kollisionsverhalten aufweisen, diese müssen, damit das in Unity klappt beide einen sogenannten Collider zugewiesen haben. Wir verpassen also erst einmal unserem Telefon einen Box Collider 2D– wieder mittels image- unter Physics 2D findet sich (unter anderem) der Box Collider 2D. Wenn das geklappt hat, erhält unser Telefon-Objekt in der Scene einen grünen Rahmen. Das passt hervorragend, da das genau der Bereich ist, bei dem eine Kollision mit einem anderen Objekt erkannt werden soll.
imageimageimage
Das war der erste, einfachere, Teil. Jetzt müssen wir der Tasse ebenfalls einen Collider zuweisen. Besser gesagt eigentlich zwei. Wenn das Telefon gefangen wird, dann wollen wir es ‚verschlucken‘, wenn das Telefon allerdings mit der Außenseite des Bechers getroffen wird, dann muss das Telefon abprallen.
Ein Box Collider 2D hilft uns in diesem Fall nicht weiter, wir benötigen eine andere Art von Collider, einen, den wir nach unseren Wünschen formen können. Einen Edge Collider 2D. Fügt diesen nun bitte wiederum mit image hinzu - unter Physics 2D findet ihr den Edge Collider 2D.
Dieser ist als grüne Linie an der Tasse zu sehen. Wenn ihr die Shift-Taste gedrückt haltet, dann könnt ihr den grünen Strich nach euren Wünschen „formen“. Der Collider sollte den Außenbereich der Tasse trapezförmig umschließen. Das erfordert ein klein wenig Geduld und etwas Übung, aber ich bin guter Dinge, dass das am Ende so aussieht, wie bei mir im rechten Bild.
imageimageimage
Am Besten ihr versucht das gleich wieder einmal, wenn ihr das Telefon mit der Aussenseite erwischt, dann müsste es (physikalisch korrekt) weggeschleudert werden. In dem nachfolgenden Bild habe ich die Tasse von rechts nach links verschoben und dabei das Telefon mit der Aussenkante erwischt, sodass diese nach links weggeschleudert wird.
image
Fügt danach noch einen zweiten Edge Collider 2D hinzu, an diesem aktiviert ihr „Is Trigger“, da dieser Collider für den Innenbereich der Tasse dient. Wenn ein Telefon mit diesem Edge Collider 2D kollidiert, dann soll ein Script ausgeführt werden, welches das gefangene Telefon zerstört.
image
Verbreitert den zweiten Edge Collider 2D etwas und schiebt ihn etwas höher. Solltet ihr Probleme haben mit dem Bearbeiten des zweiten Edge Colliders, dann könnt ihr den ersten einstweilen mit der Checkbox links oben deaktivieren.
image
Kollidieren und Fangen von Telefonen ist nun theoretisch möglich.

Zerstören gefangener Telefone

Jetzt benötigen wir ein neues Script. Wenn das Telefon gefangen wurde, soll es zerstört werden.
Also, wiederum ist der Button imageunser Freund und wir wählen Script mit dem Namen DestroyOnContact – ihr solltet das Script wieder in den Scripts-Folder verschieben, da es per Default unter Assets angelegt wird. Wichtig ist, dass das neue Script an der Tasse zu sehen ist.
image
Fehlt nur noch der Code, der ist recht kurz gehalten, da Unity die komplette Arbeit für uns erledigt. Durch das Anwählen der „Is Trigger“ CheckBox am Edge Collider 2D wird das Script aufgerufen – genauer die OnTriggerEnter2D Methode. Praktischerweise bekommen wir auch das Objekt, das mit der Tasse zusammenstößt, unser Telefon, mitgeliefert. Dieses wird kurzerhand aus dem Spiel genommen.
using UnityEngine;
using System.Collections;
 public class DestroyOnContact : MonoBehaviour {
     void OnTriggerEnter2D(Collider2D second)
    {
        Destroy(second.gameObject);
    }
}
Das solltet ihr gleich wieder „Probespielen“. Gefangene Telefone müssen sich jetzt in Luft auflösen. Langsam wird es ja ein richtiges Spiel. Wir haben allerdings noch ein Problem zu lösen. Was passiert mit dem Telefon, wenn es nicht gefangen wird?

Zerstören verpasster Telefone

Nun, nicht gefangene Telefone fliegen unendlich lange, immer weiter und weiter. Das ist natürlich nicht gut, denn wenn wir viele Telefone erzeugen würden – wir wollen schließlich noch mehr als ein Telefon auffangen, es soll ja ein richtiges Spiel werden – würden wir eine Menge Speicher und Ressourcen für die Kalkulation der unendlich fallenden Game-Objekte verbraten.
Wir sind uns also einig, dass verfehlte Telefone zerstört werden müssen. Dazu fügt ihr ein leeres Game-Objekt ein. Das ist ein beliebter Trick in Unity, wenn man nicht sichtbare Objekte in seinem Spiel benötigt, oder Objekte gruppieren möchte. Eingefügt wird es über das Menü GameObject und Create Empty.
image
Ändert den Namen von imageauf imageund weist ihm einen Edge Collider 2D zu, wiederum mit der Eigenschaft „Is Trigger“ aktiviert. Ausserdem zieht ihr das zuvor erstellte Script DestroyOnContact aus dem Asset->Scripts Folder auf das Cleanup-Objekt, da wir dieses Script gleich wiederverwenden wollen (es funktioniert tadellos, also warum ein neues Script schreiben).
image
Soweit so gut, der Edge Collider 2D gehört noch angepasst, dieser sollte außerhalb, mit einem gewissen Respektabstand zur eigentlichen Spielfläche platziert werden - dem Spieler soll hier gar nicht auffallen, dass wir die Telefone zerstören. Wir benötigen den Edge Collider 2D links, unterhalb und rechts der Spielfläche, da das Telefon ja abprallen kann und relativ weit weggeschleudert werden könnte (darum ist der Edge Collider 2D links und rechts auch etwas höher als das Spielfeld). Das sollte im Endeffekt dann ungefähr so aussehen:
image
Wenn ihr das Spiel wieder startet und das Telefon nicht auffängt, dann fällt es zwar aus dem Schirm, kollidiert aber mit dem neuen Edge Collider 2D und wird zerstört. Das wird ersichtlich, wenn ihr die Hierarchy-Ansicht beobachtet, dort verschwindet das Telefon-Objekt, sobald es zerstört wurde.

Re-Spawn neuer Telefone

Ein einzelnes Telefon, welches eingefangen werden kann ist nicht gerade ein spektakuläres Spiel. Es soll mehrere Telefone in verschiedenen Abständen „vom Himmel regnen“. D. h. wir müssen unser Telefon mehrmals erzeugen. Um ein Objekt in einem Spiel mehrfach nutzen zu können, stellt Unity sogenannte Prefabs zur Verfügung. Ein Prefab stellt ein beliebiges, möglicherweise aus mehreren Komponenten zusammengesetztes Objekt dar. Das klingt ganz nach dem Telefon-Objekt, welches wir bereits erstellt haben, also erstellen wir ein „Prefab“ aus unserem Telefon. Dazu zieht ihr das Telefon aus der Hierarchy-Ansicht in den Prefabs-Folder.
image
Fertig, wir haben nun ein Prefab, welches alle Eigenschaften unseres Telefons aufweist, dieses können wir beliebig oft erzeugen. Das Telefon-Objekt in der Hierarchy-Ansicht sollte übrigens weggelöscht werden, da wir dieses nicht mehr benötigen – wir haben nun das Prefab-Objekt, welches wir in unserem Script „on-the-fly“ erzeugen werden. Um das bewerkstelligen zu können, benötigen wir wiederum ein leeres Game-Objekt, dem wir den Namen GameControllervergeben und ein neues Script mit dem Namen GameController mittels imagezuweisen. Nicht zu vergessen ist die Y-Position von 9, da im folgenden Script zwar die X-Position zufällig – innerhalb der erlaubten Grenzen – bestimmt wird, die Y-Position aber vom GameController verwendet wird und diese außerhalb des Bildschirms liegen sollte, damit die Telefone in den Bildschirm fliegen.
image
Dieses neue Script hat als Herzstück die Spawn Methode, diese instanziiert neue Telefone und zwar im Intervall zwischen ¼ Sekunde und ½ Sekunde.
using UnityEngine;
using System.Collections; 
public class GameController : MonoBehaviour
{
    public Sprite[] sprites;
    public Camera cam;
    public GameObject phone;
    private float maxWidth;
    public float timeLeft = 30;
    void Start()
    {
        if (cam == null)
            cam = Camera.main;
        var corner = new Vector3(Screen.width, Screen.height, 0f);
        var targetWidth = cam.ScreenToWorldPoint(corner);
        float phoneWidth = phone.renderer.bounds.extents.x;
        maxWidth = targetWidth.x - phoneWidth;
        StartCoroutine(Spawn());
    }
 
    IEnumerator Spawn()
    {
        yield return new WaitForSeconds(2f);
        while (timeLeft > 0)
        {
            var spawnPosition = new Vector3(
                Random.Range(-maxWidth, maxWidth), transform.position.y, 0f);
            var spawnRotation = Quaternion.identity;
            Instantiate(phone, spawnPosition, spawnRotation);
            var spriteNum = Random.Range(0, sprites.Length - 1);
            phone.GetComponent().sprite = sprites[spriteNum];
            yield return new WaitForSeconds(Random.Range(0.25f, 0.5f));
        }
        yield return new WaitForSeconds(2f);
    }
}
Nun müssen alle Variablen in Unity zugewiesen werden:
· Sprites haben wir insgesamt 9 Stück – 6 Windows Phones, 3 iPhones
· Phone ist unser erstelltes Prefab
Die Variablen können durch Drag & Drop zugewiesen werden. Die Elemente 0 – 8 für die Sprites kommen aus dem Sprites-Folder. Achtet bitte darauf, dass die ersten drei Sprites die iPhones sind, das wird sich später bei der Punkte-Vergabe als hilfreich erweisen.
image
Der Phone Variable wird das Prefab, ebenfalls durch Drag & Drop zugewiesen:
image
Tja, was soll ich sagen, wir sollten das Spiel wieder einmal testen. Jetzt ist es ja auch fast schon ein richtiges Spiel, in unregelmäßigen Abständen fällt ein neues Telefon „vom Himmel“, welches wir mit der Tasse fangen können.

Spiel-Anzeigen und Neustart

Bisher ist es noch ein „Endlosspiel“, das heißt ihr könnt so lange spielen, bis ihr einfach keine Lust mehr habt. Das ist, nun ja, „nett“, und was noch dazu kommt, es gibt keinerlei Punkte für gefangene Windows Phones und keine „Schlechtpunkte“ für gefangene iPhones. Also ich finde, das sollten wir ändern.
Wir bauen zuerst die Anzeigen ein, die wir benötigen:
· Eine Anzeige für die verbleibende Zeit.
· Eine weitere Anzeige für die erzielten Punkte.
· Die verbleibende Spielzeit soll ebenfalls angezeigt werden.
· Abschließend noch eine weitere für einen „Game Over“-Text.
Das Ganze soll also zu einem richtigen Spiel „wachsen“ und sollte ungefähr so aussehen:
image
Dazu benötigen wir 4 neue Game Objekte und zwar vom Typ „GUI Text“. Diese fügt ihr mit Create -> GUI Text im Hierarchy Fenster hinzu (oder über das Menü GameObject->Create Other-> GUI Text).
imageimage
Das Ganze macht ihr somit insgesamt 4 Mal und gebt den Objekten bitte sprechende Namen, ich habe mich für die Namen: Timer Info, Score Info, GameOver Info und Restart Info entschieden.
image
Die Einstellungen für die ersten Beiden (Timer Info und Score Info) sind relativ simpel, ein vernünftiger Text und die richtige Platzierung und es ist geschafft. Einen Vorschlag könnt ihr untenstehend sehen.
imageimage
Ähnlich verhält es sich mit dem GUI Text für das GameOver Info Objekt. Zusätzlich wird dies noch unsichtbar geschaltet, da es erst sichtbar sein soll, wenn das Spiel auch tatsächlich zu Ende ist.
Genauso ist es bei dem GUI Text Restart Info Objekt, hier wird zusätzlich zum unsichtbar schalten noch ein weiteres Script benötigt – mit dem Namen Restart - mit dem das Spiel gestartet werden kann (nämlich dann, wenn der Text sichtbar ist und der Spieler auf dieses Objekt klickt).
imageimage
Das Restart-Script lädt den aktuellen Level erneut, dadurch werden alle Werte zurückgesetzt und der Spass kann von neuem beginnen.
using UnityEngine;
public class Restart : MonoBehaviour {
    void OnMouseDown()
    {
        Application.LoadLevel(Application.loadedLevel);
    }
}

Punkte-Vergabe

In diesem Spiel soll es Punkte für gefangene Windows Phones geben, nämlich genau einen, allerdings auch einen Punkteabzug, wenn ein iPhone in der Tasse landet. Dazu fügen wir ein Script mit dem Namen Score unserer Tasse hinzu.
imageimage
In diesem Skript wird jedes Mal, wenn der Trigger an dem Edge Collider 2D ausgelöst wird festgestellt, ob es ein „gutes“ Telefon ist, oder ein „böses“ Telefon. Nur der „innere“ Edge Collider 2D löst an der Tasse den Trigger aus, also sind das wirklich nur gefangene Telefone, nicht diejenigen die einfach nur an der Tasse abprallen. Je nachdem, werden dann Punkte aufaddiert, oder abgezogen und die Anzeige aktualisiert. Dazu gibt es eine public Variable mit dem Namen scoreText.
using UnityEngine;
public class Score : MonoBehaviour
{
    public GUIText scoreText;
    public int catchValue = 1;
    private int score = 0;
    void Start()
    {
        score = 0;
        UpdateScore();
    }
    void OnTriggerEnter2D(Collider2D second)
    {
        Debug.Log("Tag on trigger: " + second.tag);
        if (second.tag == "Good")
            score += catchValue;
        else
            score -= catchValue;
        UpdateScore();
    }
    private void UpdateScore()
    {
        scoreText.text = "Score: " + score.ToString();
    }
}
Diese wird mit dem GUI Text Score Info Objekt – wiederum in Unity - verbunden.
image
Die Tags, die in dem Script verwendet werden, müssen allerdings noch definiert werden, dazu ist unser Prefab anzuwählen und die ComboBox für die Tags herunterzuklappen und „Add Tag…“ anzuwählen. Die beiden Tags, die wir benötigen sind: Good und Bad.
image
Jetzt müssen wir beim instanziieren eines neuen Telefons noch den Good/Bad Tag setzen. Wenn die Nummer in unserem Sprite-Array für die Telefone kleiner als 3 ist, dann ist es ein iPhone (Bad), ansonsten ein Windows Phone (Good). Dazu reicht eine Zeile Code im GameController Script in der Spawn Methode.
phone.GetComponent().sprite = sprites[spriteNum];
phone.tag = spriteNum < 3 ? "Bad" : "Good";
yield return new WaitForSeconds(Random.Range(0.25f, 0.5f));
Ich würde vorschlagen, dass jetzt wieder eine Runde probe spielen angesagt wäre. Schließlich gibt es jetzt schon Punkte abzustauben!

Ende des Spiels nach N-Sekunden

Das Spiel sollte nach, nun sagen wir einmal, 30 Sekunden beendet werden. Sobald das Spiel zu Ende ist, werden die beiden GUI Text Objekte GameOver Info und Restart Info eingeblendet. Dazu müssen wir unser GameController-Script erweitern.
using UnityEngine;
using System.Collections; 
public class GameController : MonoBehaviour
{
    public Sprite[] sprites;
    public Camera cam;
    public GameObject phone;
    private float maxWidth;
    public float timeLeft = 30; 
    public GUIText timerText;
    public GUIText gameOverText;
    public GUIText restartText; 
    void Start()
    {
        if (cam == null)
            cam = Camera.main;
        var corner = new Vector3(Screen.width, Screen.height, 0f);
        var targetWidth = cam.ScreenToWorldPoint(corner);
        float phoneWidth = phone.renderer.bounds.extents.x;
        maxWidth = targetWidth.x - phoneWidth;
        StartCoroutine(Spawn());
        UpdateTimerText();
    }
    void FixedUpdate()
    {
        timeLeft -= Time.deltaTime;
        if (timeLeft > 0)
            UpdateTimerText();
    }
 
    private string UpdateTimerText()
    {
        return timerText.text = "Time Left: " + 
                Mathf.RoundToInt(timeLeft).ToString();
    }
 
    IEnumerator Spawn()
    {
        yield return new WaitForSeconds(2f);
 
        while (timeLeft > 0)
        {
            var spawnPosition = new Vector3(
                Random.Range(-maxWidth, maxWidth), transform.position.y, 0f);
            var spawnRotation = Quaternion.identity;
 
            Instantiate(phone, spawnPosition, spawnRotation);
            var spriteNum = Random.Range(0, sprites.Length - 1);
            phone.GetComponent().sprite = sprites[spriteNum];
            phone.tag = spriteNum < 3 ? "Bad" : "Good";
            yield return new WaitForSeconds(Random.Range(0.25f, 0.5f));
        }
        yield return new WaitForSeconds(2f);
        gameOverText.enabled = true;
        restartText.enabled = true;
    }
}
Fast geschafft, die drei neuen public Variablen wollen noch in Unity durch Drag & Drop befüllt werden.
image
Das war’s das Spiel ist fertig und wartet nur darauf von euch gespielt zu werden!

Zusammenfassung

Die Spiele-Entwicklung mit Unity macht einen Riesenspaß. Die Erfolge stellen sich sehr schnell ein. Mit Visual Studio 2013 ist es noch einmal einen Tick einfacher (für C#- Entwickler) geworden Spiele zu entwickeln. Ich bin gespannt, ob es euch gelingt einen Welthit mit dieser fantastischen Entwicklungs-Kombi zu programmieren. Ich wünsche euch auf jeden Fall alles Gute und viel Spaß mit der Entwicklung eures Spiels!

No comments:

CSharpCodeFormatter