Alle Beiträge

  • Individuelle Projekt-Parameter für Benutzer oder Rechner

    Individuelle Projekt-Parameter für Benutzer oder Rechner

    Wird ein Unity-Projekt auf unterschiedlichen Rechnern, z.B. von unterschiedlichen Bearbeitern, geöffnet, kann es vorkommen, dass sich individuelle Details in den Projekteinstellungen unterscheiden. In diesem Beitrag stelle ich eine Möglichkeit vor, wie man unterschiedliche Parameter und Werte innerhalb eines Unity-Projektes speichert, so dass Benutzer-Profile entstehen, die sich mitsamt des Projekts beliebig kopieren, verschieben und versionieren lassen. Zwischen Profilen zu wechseln ist dabei mit einem Klick möglich.

    Anwendungsbeispiel individuelle Importpfade

    In einigen meiner Spiele verwende ich das GDP-Toolkit, das aus zwei ins Projekt zu importierenden DLLs besteht. Da ich das GDP-Toolkit selbst parallel zum Spiel bearbeite, schreibe ich dessen Code in einem separaten Visual Studio-Projekt. Die DLLs und zugehörige Dateien kopiere ich dann mittels des FilePatching-Tools mit nur einem Befehl ins Unity-Projekt des Spiels.

    Das GDP-Toolkit synchronisiere ich mit einem Git-Repository. Auf dem Standrechner habe ich das Repository unter c:\Daten\Git\GPD ausgecheckt, auf meinem Laptop unter c:\Git\GDP.

    Möchte ich das Unity-Projekt meines Spiels nun auf beiden Rechner öffnen und bearbeiten, so entsteht das Problem, dass das FilePatching-Tool mit unterschiedlichen Pfaden arbeiten muss. Auf dem Standrechner sollen die DLLs aus c:\Daten\Git\GPD, auf dem Laptop aus c:\Git\GDP ins Projekt kopiert werden.

    Warum nicht über EditorPrefs lösen?

    Unity bietet mit der Klasse EditorPrefs bereits eine Möglichkeit, um Editor-Einstellungen zu speichern. Im einfachen Beispiel oben, ließen sich die Dateipfade auch als Editor-Präferenzen speichern. EditorPrefs sind dafür gedacht, einfache lokale Editor-Einstellungen zu sichern, wie zum Beispiel Fensterlayouts. Sie sind nur auf dem Rechner verfügbar, auf dem die Einstellungen vorgenommen wurden und sie können nicht mit dem Projekt gespeichert/kopiert werden. Bei Neuinstallation oder Versions-Updates gehen sie im Zweifelsfall verloren.

    Editor-Profile als individuelle Wertespeicher

    Ich habe daher im GDP-Toolkit Editor-Profile umgesetzt. Es handelt sich dabei um einen allgemeinen Datenspeicher auf Basis des ScriptableObject, der als lokale Datei in den Projekt-Assets liegt. Die Idee ist, dass man im Projekt für jeden Benutzer oder Rechner eine separate Profil-Datei erstellt. Das funktioniert natürlich auch ganz allgemein immer dann, wenn irgendeine Form alternativer Konfigurationen erforderlich werden.

    Die Schlüsseleigenschaft liegt darin, dass es immer genau ein aktives Editor-Profil gibt, das als aktive Konfiguration angesehen wird. Um ein Profil zu wechseln, aktiviert man einfach ein anderes Editor-Profil, so dass sich mit nur einem Kommando eine ganze Reihe von Wertänderungen anwenden lassen.

    [seealso title=“Sieh Dir ein Beispiel an“]Im Beitrag Bibliotheks-Code in mehreren Unity-Projekten nutzen beschreibe ich, wie ich dieses System konkret einsetze, um Dateien unter Berücksichtigung maschinenspezifischer Pfade zu importieren.[/seealso]

    [bildnachweis]Beitragsbild: People-Avatars: Designed by Freepik[/bildnachweis]

  • Reagieren, wenn der SceneManager ein Level nicht lädt

    Reagieren, wenn der SceneManager ein Level nicht lädt

    Wenn man in Unity versucht, eine nicht-existente Szene über den SceneManager zu laden, dann erscheint zwar eine Fehler-Meldung im Log-File bzw. der Editor-Konsole, aber es gibt scheinbar keinen direkten weg, um per Code darauf zu reagieren. Das Abfangen von Exceptions, sofern solche überhaupt auftauchen, wirkt in Unity generell etwas unzuverlässig, da das Log-System bereits viel abfängt und verarbeitet. Unklar erscheint mir zudem, wieso die Methode SceneManager.LoadScene nicht einfach einen Rückgabewert liefert, der über den Erfolg der Methode Auskunft gibt. Möglicherweise hängt es damit zusammen, dass Ladevorgänge auch asynchron ablaufen können und daher Fehler unter Umständen nicht sofort ermittelbar sind.

    Durch Systemüberwachung Rückschlüsse auf den SceneManager ziehen

    Nun ist es zwar so, dass der Level-Ladefehler meistens beim Testen im Editor auffallen dürfte und schlicht durch Inklusion der fehlenden Szenen-Assets zu beheben ist. Dennoch hat mich die Frage beschäftigt, wie man auf den Fehler reagieren kann. Die gefundene Lösung besteht darin, dass man das Log-System während des Ladevorgangs überwacht. Bei jeglichem Protokollierungsvorgang (also Debug.Log...) werden hier eingeschriebene Funktionen benachrichtigt, so dass man kurzum prüfen kann, ob während des Ladens ein Fehler auftrat. Wenn dem so ist, ist in Schlussfolgerung das Fehlschlagen des Ladevorgangs zu vermuten.

    Folgender Abschnitt skizziert das Prinzip:

    ...
    bool errors=false;
    ...
    Application.logMessageReceivedThreaded += Application_logMessageReceived;
    SceneManager.LoadScene("nonexisting"); 
    Application.logMessageReceivedThreaded -= Application_logMessageReceived;
    if (errors)
    {
       ... scene could not be loaded
    }
    ...
    
    private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
    {
       if (type==LogType.Error) errors=true;
    }

    Schnelle und einfache Lösung mit WatchForError-Klasse

    Ich habe dem GameDev-Profi-Toolkit eine Klasse namens WatchForError hinzugefügt, die nach dem oben beschriebenen Prinzip Log-Fehler zählt. Sie lässt sich für fehlerhaftes Szenen-Laden als auch ähnliche Situationen anwenden.

    using GameDevProfi.Utils; //https://github.com/renebuehling/GDP-Toolkit
    ...
    WatchForError watch = WatchForError.startNew();
    SceneManager.LoadScene("NonExistingLevel"); 
    if (watch.stop().errors>0) 
       Debug.Log("Scene could not be loaded.");

    Eine Exception werfen, wenn die Szene fehlt

    Möglicherweise willst Du bei Auftreten unterschiedlichster Umstände oder Fehler in die selbe Fehlerbehandlung springen. Dann eignet sich ein Try-Catch-Block, der sich mit WatchForError ebenfalls bedienen lässt:

    try
    {
        WatchForError watch = WatchForError.startNew();
        SceneManager.LoadScene("doesnotexists");
        if (watch.stop().errors>0) throw new Exception("Scene not loadable.");
        //... do things if scene loading was ok ...
    }
    catch (System.Exception e)
    {
        Debug.Log("Scene not loadable! Return to levelmap.");
        //... do something when any error occurred above ...
    }

     

  • Listen unterschiedlicher Klassen speichern und laden (Serialisierung)

    Listen unterschiedlicher Klassen speichern und laden (Serialisierung)

    In den meisten Spielen müssen irgendwelche Daten im Spiel ist Daten-Serialisierung erforderlich, also das Speichern und Laden von eigenen Werten. Das kann zum Beispiel der Spielfortschritt sein oder auch Level-Karten, wie man sie üblicherweise für Gelegenheitsspiele braucht. Es ist zwar möglich, aber sehr mühsam und schlecht wartbar, wenn man für ein Casual-Game jedes Level als Szene in Unity anlegt. Immerhin ist die Spielmechanik im Wesentlichen ja einheitlich und nur die aktiven Features oder Details wie das Spielbrettlayout variieren im Spielverlauf. Da ich gerade an einem solchen Mobile-Game arbeitete, war meine Idee deshalb, das Spiel so zu programmieren, dass die konkreten Level in Level-Dateien hinterlegt werden. Es sollte sich um ein schlichtes und kompaktes Format handeln, das so wenig Daten wie möglich transportieren muss. Zudem sollen Leereinträge vermieden werden. Wenn in einem Level etwas nicht vorkommt, dann soll es in der Datei (und dann in den aus der Datei generierten Datenobjekten) auch nicht vorkommen. Also nicht etwa eine Variable die auf Null zeigt, sondern wirklich gar keine Existenz des Datenelements.

    Die Standard-XML-Serialisierung produziert sehr viel Text

    Mangels Erfahrung hatte ich im Projekt A Room Beyond einige Klassen so aufgesetzt, dass am Schluss viele Situationen entstanden in denen Objekte leere Felder hatten, weil die entsprechende Eigenschaft oder Funktion nur in einzelnen Szenen benötigt, aber in einer an vielen Stellen eingesetzten Klasse implementiert wurde. Auch die Savegames, die in XML formuliert wurden, waren unnötig aufgebläht. Diese Problem entstand vor allem dadurch, dass die Standard-XML-Serialisierung sehr gerne verschachtelte Objekte generiert.

    <sl k="enbl">
      <v xsi:type="xsd:boolean">false</v>
    </sl>

    In dem oben eingefügten Beispiel sieht man, dass die Eigenschaft enabled=false für ein Objekt in der XML-Serialisierung recht ausführlich formuliert wird. Optimiert sähe das Fragment zumindest ungefähr so aus:

    <sl enbl="false" />

    Der Variablen-Name wird in diesem Beispiel direkt als XML-Attribut-Name verwendet und der Wert als Attribut-Wert übernommen. Der alte Code ist deshalb erheblich größer, weil ein zusätzliches Unterelement v eingesetzt wurde und dadurch wiederum der umschließende Tag sl doppelt so groß wird, da er einen Abschluss mit /sl braucht.

    Der Teufel steckt im Detail

    Das Problem bei der Sache ist die Typisierung. Während beim dynamisch typisierten JavaScript zunächst egal wäre, ob die gespeicherte Eigenschaft eine Zahl, Text oder wahr/falsch ist, ist das bei C# nicht so einfach. Um Objekt und ihre Eigenschaften später wieder laden zu können, müssen wir entweder fest definierte Felder verwenden, also zum Beispiel eine Klasse erzeugen, die genau definierte Felder hat. Dann könnte der Deserialisierer beim Laden in die Klasse schauen und daraus den Typ identifizieren. Nun kann es aber andererseits sein, dass wir eben keine festen Felder vorgeben wollen, sondern im Prinzip jederzeit beliebige Daten dem Spielstand hinzufügen/entfernen können wollen. Dann muss der Datentyp mit gespeichert werden, damit der Deserialisierer später weiß, um welche Art von Inhalt es sich handelt. Und hier wird es kompliziert.

    Die Wunschvorstellung: Eine Liste, gefüllt mit egal-was

    Idealerweise wäre es so: Man legt eine Instanz einer Liste an und fügt ihr beliebige Klassen hinzu. Man speichert und lädt einfach die gesamte Liste. Der Vorteil wäre dabei, dass man in den Datenklassen beliebige Felder umsetzen kann und dabei nur die Werte gespeichert werden müssen, da sich die Typen ja schon aus der Klassendeklaration ergeben.

    public class A
    {
      public int feldVonA = 333;
    }
    
    public class B
    {
      public int feldVonB = 0;
    }
    
    public List items = new List();
    items.add(new A());
    items.add(new B());

    Es tauchen einige Problem auf. Zunächst muss der Liste ein Typ zugewiesen werden, das geht relativ einfach, indem wir A und B eine Superklasse X zuweisen und diese als Listentyp anwenden.

    public class X{}
    public class A:X
    {
      public int feldVonA = 333;
    }
    
    public class B:X
    {
      public int feldVonB = 0;
    }
    
    public List<X> items = new List<X>();
    items.add(new A());
    items.add(new B());

    Versucht man jetzt, diese Liste mit dem System.XML Serialisierer zu speichern, wird dies zunächst nicht funktionieren, weil die Listenelemente alle über ihre Superklasse X behandelt werden und daher nicht zwischen A und B unterschieden werden. Dies wird durch den Fehler The type of the argument object ‚A‘ is not primitive. beschrieben.

    Die Lösung, um eine Liste mit unterschiedlichen Klasseninstanzen nach XML zu serialisieren

    Der Trick liegt in Meta-Attributen, Erweiterungen des Source-Codes um Descriptoren, die bestimmte Hinweise zu Interpretation und Verarbeitung geben. Für die Speicherung in XML können wir zum Beispiel das XMLElement-Attribut vor ein Feld schreiben, und so dem XML-Serialisierer sagen, wie der Tag heißen soll mit dem das Objekt in XML gespeichert wird:

    [XmlElement("MyElementName")]
    public class A:X
    

    …resultiert in…

    <MyElementName ...>

    Dieses Attribut lässt sich nun auch auf Listen anwenden. Das folgende Beispiel zeigt, wie sich alle Elemente einer Liste (hier DataBlock-Instanzen) in der XML-Ausgabe umbenennen lassen (und zwar von <DataBlock> zu <d>):

    //[XmlArray("r")] //-> wrapper <r> containing child elements <DataBlock>
    [XmlElement("d")] //-> direct children, each named <d> (instead of DataBlock)
    public List<DataBlock> raw = new List<DataBlock>();

    Der Trick für unsere heterogene Liste besteht nun darin, dass man dieses Attribut für Listen auch stapeln kann! Dabei werden die Parameter erweitert: Neben dem gewünschten Namen für das XML-Element wird zusätzlich noch die Klasse angegeben für die diese Benennung gilt:

    [XmlElement("A", typeof(A))]
    [XmlElement("B", typeof(B))]
    public List<X> items = new List<X>();

    Durch diese Zuweisung weiß das Serialisierungssystem nun, welche Klasse zu welchem XML-Element gehört und kann die gemischte Liste speichern und laden.

    Komprimierung der XML-Darstellung

    Am Anfang dieses Artikels habe ich über die Ausführlichkeit gesprochen mit der XML-Darstellungen im Normalfall erzeugt werden. Mit Attributen lässt sich auch dies beeinflussen:

    public class A:X
    {
        //[XmlAttribute("sf")]
        public int subfield = 333;
    }
    
    /*
    XML Serialization:
    <?xml version="1.0" encoding="utf-8"?><xml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><A><subfield>333</subfield></A><B ixi="0" /></xml>
    */

    …während…

    public class A:X
    {
        [XmlAttribute("sf")]
        public int subfield = 333;
    }
    /*
    XML Serialization:
    <A sf="333" />
    */

     

    Andere Ansätze zum Speichern und Laden von Daten in Unity

    • ScriptableObject Sehr bequem, da man sich so gut wie garnicht um Speichern und Laden kümmern muss. Nachteil: Speichern funktioniert nur im Editor, da das Format als benutzerdefiniertes Asset-Format gedacht ist. Speichern von ScriptableObjects im fertigen Spiel geht nicht.
    • JSON Mit Unity’s relativ neuer Klasse JsonUtility können Objekte sehr einfach über das JSON-Format dargestellt werden. Nachteile: Der Serialisierungsvorgang kann nicht wie bei XML, beeinflusst oder erweitert werden, Sonderfälle sind damit nicht abgedeckt. Mit Listen hat es in meinen Versuchen überhaupt nicht funktioniert und wegen der zuvor genannten Statik kann man daran auch nicht viel machen.

     

     

     

  • Unitys MovieTexture crasht auf dem Mac [Lösungen]

    Unitys MovieTexture crasht auf dem Mac [Lösungen]

    Videos innerhalb eines Spiels abzuspielen, ist mit einigen technischen Herausforderungen verbunden. So müssen z.B. verhältnismäßig viele Daten in kurzer Zeit in den Videospeicher gestreamt werden, um sie auf einer MovieTexture im 3D-Raum rendern zu können. Unity nimmt uns hier glücklicherweise durch seine „Just works“-Mentalität wieder einmal viel Last ab. Dennoch kann es zu Problemen kommen, wie folgendes Beispiel zeigt:

    Am Ende der Demo von A Room Beyond sollte ein Marketing-Video abgespielt werden. Dazu wird eine separate Szene im Spiel geladen, in der sich das Video als MovieTexture eines RawImage-Objekts befindet. Lief alles prima, bis ich den Build auf einem Mac testete. Das Spiel hat schlicht nicht mehr reagiert anstatt in die Video-Szene zu springen.

    Ursache des fehlerhaften Ladens der MovieTexture ermitteln

    Das Fehlerprotokoll des Players sah in etwa so aus:

    Receiving unhandled NULL exception
    Obtained 25 stack frames.
    #0  0x00000100e09e2e in BaseVideoTexture::InitVideoMemory(int, int)
    #1  0x00000100b47b49 in PersistentManager::IntegrateObjectAndUnlockIntegrationMutexInternal(int)
    #2  0x00000100b1289d in TimeSliceAwakeFromLoadQueue::IntegrateTimeSliced(int)
    #3  0x00000100b126fd in LoadOperation::IntegrateTimeSliced(int)
    #4  0x00000100b13dee in LoadSceneOperation::IntegrateTimeSliced(int)
    #5  0x00000100b10d37 in PreloadManager::UpdatePreloadingSingleStep(PreloadManager::UpdatePreloadingFlags, int)
    #6  0x00000100b11592 in PreloadManager::WaitForAllAsyncOperationsToComplete()
    #7  0x00000100932ee5 in RuntimeSceneManager::LoadScene(UnityStr const&, UnityStr const&, UnityStr const&, UnityGUID const&, int, RuntimeSceneManager::LoadingMode)
    #8  0x00000100aa7489 in PlayerStartFirstScene(bool)
    #9  0x0000010106f2d8 in -[PlayerAppDelegate UpdatePlayer]
    #10 0x007fff8ae2fdec in __NSFireTimer
    #11 0x007fff91fa9af4 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
    #12 0x007fff91fa9783 in __CFRunLoopDoTimer
    #13 0x007fff91fa92da in __CFRunLoopDoTimers
    #14 0x007fff91fa07d1 in __CFRunLoopRun
    #15 0x007fff91f9fe38 in CFRunLoopRunSpecific
    #16 0x007fff998b9935 in RunCurrentEventLoopInMode
    #17 0x007fff998b976f in ReceiveNextEventCommon
    #18 0x007fff998b95af in _BlockUntilNextEventMatchingListInModeWithFilter
    #19 0x007fff923d6df6 in _DPSNextEvent
    #20 0x007fff923d6226 in -[NSApplication _nextEventMatchingEventMask:untilDate:inMode:dequeue:]
    #21 0x007fff923cad80 in -[NSApplication run]
    #22 0x007fff92394368 in NSApplicationMain
    #23 0x0000010106de92 in PlayerMain(int, char const**)
    #24 0x00000100001a34 in start

    Zeile 1 zeigt, dass es zu einer Nullpointer-Exception kam. Der Funktionsname InitVideoMemory in Zeile 3 lässt darauf schließen, dass das Problem mit dem Laden des Videos zusammenhängt. Der Rest des Stacktraces zeigt außerdem auf Unity-interne Methoden, das Problem liegt also wahrscheinlich nicht an meinem Code.

    Lösungsansätze, um die MovieTexture auch auf dem Mac abspielen zu können

    Im Unity-Forum vermutet man, dass das Problem mit dem Laden, bzw. dem Entladen von Videos abspielenden Szenen zusammenhängen könnte. Folglich habe ich versucht, das Video verzögert zu laden, d.h mit Hilfe einer Coroutine, die einige Frames wartet, bevor das Video dem RawImage zugewiesen und abgespielt wird. Wenn es am Laden liegt, müsste das Problem ja behoben sein, wenn sich die Software erst mit dem Video befasst, nachdem der Szenen-Ladevorgang abgeschlossen ist. Leider blieb das Problem aber bestehen.

    Weil die fehlgeschlagene Funktion den Begriff „VideoMemory“ enthielt, fragte ich mich dann, ob das Video womöglich zu groß sei und der Testrechner (ein Macbook) schlicht zu wenig Speicherkapazität hätte. Allerdings war das Video lediglich 10MB groß, so dass dieses Problem sehr unwahrscheinlich schien. Um dennoch auszuschließen, dass etwas grundsätzlich mit der Video-Datei nicht stimmt und um mich an das Problem näher heranzutasten, begann ich ein einfaches Experiment.

    Ich legte auf dem Mac schlicht ein neues Unity-Projekt an und erstellte eine Test-Szene, die nichts außer einem Canvas mit RawImage enthielt, dem die Video-Datei zugewiesen wurde. Der Standalone-Build lief einwandfrei, das Video wurde sauber in-game abgespielt. Worin unterschied sich nun also mein Testprojekt von meinem Spielprojekt?

    Ich fand das Problem relativ schnell: Es liegt am Build-Typ des Mac-Players. Scheinbar funktioniert das Laden der Videos nicht richtig, wenn ein Universal-Build erzeugt wird. Die Lösung bestand letztlich also im Umstellen auf den 32-bit-Architektur-Typ x86.

    Wird in den Build-Settings die Architektur von Universal auf x86 geändert, spielt die MovieTexture auf dem Mac problemlos ab.
    Umstellen der Architektur-Einstellung von Universal auf x86

    Ich hätte auch auf x86_64 umstellen können, dann läuft das Spiel jedoch nur noch auf 64-bit Rechnern. Der 32-bit-Build dagegen sollte sowohl auf 64- wie auch 32-bit-Systemen laufen.

  • Text UND Grafik eines Buttons bei MouseOver einfärben

    Text UND Grafik eines Buttons bei MouseOver einfärben

    Für Eingabeelement, wie z.B. Schaltflächen, möchte man die Interaktivität üblicherweise grafisch ausdrücken. In Unity verfärbt sich beispielsweise eine Schaltfläche, wenn der Spieler mit der Maus darauf zeigt, das Element anklickt oder wenn das Steuerelment gesperrt (disabled) wird. Leider berücksichtigt Unitys neues GUI-System nur ein grafisches Element, das als Ziel für die Farbveränderung angegeben werden kann. Somit lässt sich z.B. nur der Button selbst oder der darauf dargestellte Text automatisch einfärben.

    Die Lösung scheint zunächst kompliziert, zumal die aktuelle Farbe des Buttons nicht direkt ausgelesen werden kann. Der entscheidende Trick dabei ist, nicht den Button, sondern den Renderer der RenderTarget-Komponente abzufragen. In ihr versteckt sich eine color-Eigenschaft, die die aktuelle Renderfarbe enthält. Folglich lässt sich mit einer Komponente, die allen ebenfalls einzufärbenden Komponenten (z.B. Texte oder Bilder auf dem Button) hinzugefügt wird, sehr leicht eine einheitliche Färbung realisieren.

    //The trick to synchronize text color with the button's current color state is to refer to the CanvasRenderer:
    //text.color=button.GetComponent<CanvasRenderer>().GetColor();  //Unity < 5.3.1f1 (?)
    target.color=button.targetGraphic.canvasRenderer.GetColor(); //in Unity 5.3.1f1+

    [thrive_link color=’blue‘ link=’https://github.com/renebuehling/unity-snippets/blob/master/utils/GUICopyButtonTint.cs‘ target=’_blank‘ size=’small‘]Download des gesamten Scripts[/thrive_link]

     

  • Den SHA1-Wert für Android finden

    Den SHA1-Wert für Android finden

    Möchte man sein in Unity umgesetztes Spiel für Android compilieren und im Google PlayStore veröffentlichen, kommt man bei der Einrichtung der Anwendung in der Developer Console zu einem Fenster, in dem die Angabe eines SHA1-Werts verlangt wird. Doch wo findest Du diesen Wert?

    Vorbereiten des Unity-Projekts

    Zunächst muss erstmal ein Keystore im Unity-Projekt eingebunden oder angelegt werden:

    • Menüpunkt Edit > Project Settings > Player aufrufen.
    • Im Inspector den Android-Tab auswählen und bis zu den Publishing Settings runterscrollen.
    • Unter Keystore bei „Create a new Keystore“ einen Haken setzen.
    • Mit „Browse Keystore“-Schalter einen Speicherort für die (neue) Keystore-Datei angeben. Standard ist eine Datei namens user.keystore im Projektwurzelordner.
    • In die Felder „Keystore password“ und „Confirm password“ ein beliebiges Passwort für die Datei vergeben (und merken oder notieren).
    • Im Abschnitt „Key“ unter „Alias“ im Dropdown den Eintrag „Create a new key“ auswählen.
    • Das sich öffnende Formular ausfüllen und bestätigen. Am besten Umlaute und Sonderzeichen vermeiden.
    • Nun im Dropdown („Key“ > „Alias“) den neu generierten Schlüssel auswählen.

    Wird der Android-Build nun generiert, so wird die resultierende .apk-Datei automatisch mit den eben angelegten Angaben signiert.

    Eingabefelder für die Android-Keys

    Schlüsselcodes mit dem JDK-Keystore-Tool auslesen

    Um nun den SHA1-Schlüssel zu erhalten, wie folgt vorgehen. Eine Installation des JDK ist dazu erforderlich.

    • Eine Kommandozeile im bin-Pfad des JDK öffnen (z.B. c:\Program Files\Java\jdk1.7.0_79\bin).
    • Ausführen: keytool -list -v -keystore c:\pfad\zum\eigenen\user.keystore
    • Nach Eingabe des Passworts zeigt das keytool eine Liste mit Zertifikat-Fingerprints. Darunter findet sich eine Zeile, die mit „SHA1:“ beginnt. Die dort angegebene Zeichenkette muss nun in die Developer Console des PlayStore eingefügt werden.

    Quellen und weiterführende Infos: zum Signieren, zum Keytool.

    Ablesen des SHA1-Schlüssels mit dem Keystore-Tool

  • Standard-Inspector in eigenen Editor integrieren

    Standard-Inspector in eigenen Editor integrieren

    Schreibt man eine Erweiterung für den Unity-Editor, so kann es vorkommen, dass man teils eigene GUILayouts zeichnet, teils aber die selbe Funktionalität wie der Standard-Inspector benötigt. Leider ist es nicht ohne weiteres möglich, den Standard-Inspector in ein eigenes EditorWindow zu integrieren, obwohl das mit den sehr sequentiell angelegten GUI-Funktionen doch eigentlich kein großes Problem sein sollte.

    Beispiel: Eigene Komponentenliste mit Standard-Inspector für eine ausgewählte Komponente.
    Beispiel: Eigene Komponentenliste mit Standard-Inspector für eine ausgewählte Komponente.

    So bindest Du den Inspector für Deinen Editor ein

    Beim Blick in den Code stellt sich erfreulicherweise heraus, dass sich Editor-Felder des Standard-Inspectors recht einfach zu generieren sind:

    //-- Draw default inspector editor inside custom EditorWindow code --
     SerializedObject obj = new UnityEditor.SerializedObject( ... ); //...=i.e. a Component instance
     EditorGUI.BeginChangeCheck();
     obj.Update();
     SerializedProperty iterator = obj.GetIterator();
     bool enterChildren = true;
     while (iterator.NextVisible(enterChildren))
     {
      if (iterator.type!="PPtr<MonoScript>") //optional line: Skip the Script field
      {
       EditorGUILayout.PropertyField(iterator, true, new GUILayoutOption[0]);
       enterChildren = false;
      }
     }
     obj.ApplyModifiedProperties();
     EditorGUI.EndChangeCheck();

    Hinweis: Ich habe keinen tiefergehenden Einblick in die internen Abläufe des Unity-Editors. Es kann daher sein, dass obiger Code nachteilige Seiteneffekte hat. Ich weiß es schlicht nicht und kann nur sagen, dass es augenscheinlich zumindest funktioniert.

  • ShapeKeys, Mirror-Modifier und Export für Unity

    ShapeKeys, Mirror-Modifier und Export für Unity

    Der folgende Artikel betrachtet Bearbeitungsschritte beim Erstellen eines Characters inklusive ShapeKeys in Blender und dem Import in Unity. Bei der Erstellung eines virtuellen Characters in Blender besteht ein gängiges Verfahren darin, nur eine Körperhälfte zu modellieren und die gegenüberliegende, im Prinzip identische Seite, durch einen Mirror-Modifikator generieren zu lassen. Fügt man dem Modell nun noch ShapeKeys hinzu, so taucht das Problem auf, dass diese beim Export in eine FBX-Datei verloren gehen und folglich auch nicht in Unity zur Verfügung stehen.

    Warum tauchen die ShapeKeys nicht in Unity auf?

    Normalerweise müssten in Unity 5 Modelle, die über ShapeKeys/BlendShapes/MorphTargets verfügen, beim Import automatisch einen SkinnedMeshRenderer (statt des üblichen MeshRenderer) erhalten. In diesem tauchen die ShapeKeys in einem Abschnitt „BlendShapes“ auf – wenn alles klappt.

    ShapeKeys alias BlendShapes im Unity-Inspector
    ShapeKeys alias BlendShapes im Unity-Inspector

    Theoretisch funktioniert das für Collada, FBX als auch dem direkten .blend-Import. Um nachzuvollziehen, warum ShapeKeys nicht in Unity erscheinen, kann man die Schritte des Export- und Import-Vorgangs manuell nachvollziehen.

    • Um festzustellen, ob das Problem beim Export aus Blender oder beim Import in Unity liegt, kann die exportierte Datei zunächst auch in ein leeres Blender-Projekt importiert werden. Sind die ShapeKeys richtig exportiert, so müssen sie auch beim Re-Import in Blender wieder auftauchen. Das ist in meinem Beispiel nicht Fall, d.h. schon der Export geht schief.
    • Der Exporter wendet zunächst alle Modifikatoren dauerhaft an. Dieses „Backen“ kann man mit dem Apply-Button im Modifikator auch manuell durchführen. Versucht man den Mirror-Modifier in einem Mesh mit ShapeKeys anzuwenden, so erscheint die Fehlermeldung „Modifier cannot be applied to a mesh with shape keys“.

    Ein Problem scheint also zu sein, dass sich der Spiegelmodifikator nicht auf ein Mesh mit ShapeKeys anwenden lässt – weder manuell noch automatisch während des Exports.

    Wie lässt sich der Mirror-Modifikator inklusive ShapeKeys trotzdem backen?

    Zunächst liegt der Verdacht nahe, man könne den Mesh-Modifikator einfach löschen, im Edit-Mode die Mesh-Hälfe duplizieren und spiegeln. Das funktioniert leider nicht, weil die ShapeKeys der gespiegelten Seite dann nicht mehr richtig funktionieren und verschoben werden.

    ShapeKeys-Fehler beim Spiegeln im Edit-Mode
    ShapeKeys-Fehler beim Spiegeln im Edit-Mode

    Der Trick zur Lösung besteht darin, nicht im Edit-Mode, sondern im Objekt-Mode zu duplizieren, zu spiegeln und dann die separaten Objekte zu einem zu verschmelzen. (Video siehe unten)

    1. Lösche zunächst den Mirror-Modifkator.
    2. Dupliziere das Mesh, das nun nur noch eine Körperhälfte enthält, im Objekt-Mode (Shift+D). Tipp: Rechtsklick nach dem Duplizieren-Befehl verhindert, dass das neue Objekt verschoben wird.
    3. Spiegel das neue Objekt durch negative Skalierung: Tastenfolge: S X - 1Davon ausgehend, dass das Objekt entlang der X-Achse gespiegelt wird. Wurde das Mesh so gestaltet, dass die Schnittfläche mit der 0-Position übereinstimmt, so sollten die Hälften jetzt bereits richtig aufeinander liegen.)
    4. Verbinde die beiden Objekte durch einen Join (beide markieren, dann Strg+J).
    5. Im Edit-Mode des entstandenen Meshes alles markieren und im Shading/UV-Tab der Werkzeugleiste (T) den Befehl Normals: Recalculate ausführen.

    Man hat nun ein durch Spiegelung vervollständigtes Mesh unter Beibehalt der ShapeKeys gewonnen.

    Verschmelzen der Kanten

    Weil die beiden Hälfte noch nicht verbunden sind, ist noch eine sichtbare Kante in der Mitte vorhanden. Diese durch Verschmelzen der Hälften beseitigen:

    1. Im Edit-Mode den Schalter Auto-Merge aktivieren. (Entweder in der Menüzeile unten den Befehl Mesh -> Auto Merge Editing anhaken oder den Schalter Blender: Editmode-Automerge-Modus drücken.)
    2. Eine Hälfte des Meshs markieren und geringfügig bewegen (z.B. bei gedrückter Strg-Taste = Einschnapp-Modus, so dass das Mesh leicht wieder an die Ausgangsposition zurück springt). Beim Loslassen sollten durch den Auto-Merge-Modus nun die überlagernden Punkte an den Schnittkanten automatisch miteinander verschmolzen werden.

    Zusammenfassender Abschluss

    Oben beschriebener Weg kann dazu verwendet werden, einen Spiegel-Effekt wie er vom Mirror-Modifikator bekannt ist, durch Kopieren und Verschmelzen auf Objekt-Ebene manuell nachzubauen. Somit wird ein gespiegeltes Mesh inklusive eventuell schon angelegter ShapeKeys vollständig exportierbar. Das entstandene Objekt kann z.B. in eine FBX-Datei exportiert und diese in Unity importiert werden. Dabei erkennt Unitys Skinned Mesh Renderer die BlendShapes und macht sie der Spieleentwicklungsumgebung zugänglich.