Game-Binaries mit Versionsnummer versehen

Versionsnummern spielen bereits während der Entwicklung eine große Rolle, denn es muss feststellbar sein, in welchem Entwicklungsstand sich die gerade ausgeführte Software befindet. Am offensichtlichsten ist dies bei Aktualisierungen. Taucht beispielsweise ein Bug im Programm auf, so wird dieser behoben, eine neue Programmversion erstellt und veröffentlicht. Natürlich macht es nur Sinn, dass die Spieler auf die neueste Version Deiner Software aktualisieren. Dazu enthält jede ausführbare Datei eine Versionsnummer, mittels derer sich die aktuellste Datei ermitteln lässt.

Aufbau einer Versionsnummer

Diese Nummern bestehen in der Regel aus einer Zeichenfolge, die vier Nummern enthält, die durch Punkte voneinander getrennt sind, etwa:

1.2.3.4

  1. Die erste Zahl ist der „Major“-Bestandteil, also die Haupt-Versionsnummer. Sie ändert sich meist nur über große Programmversionen hinweg, d.h. wenn sich die Inhalte der Software maßgeblich ändern. Beim klassischen Software-Kauf muss der Kunde normalerweise für jede neue Hauptversion bezahlen. Bei Spielen ist das nicht unbedingt der Fall, denn häufig ist ein Spiel ja ein einziges Produkt, das nur einmal gekauft wird und alle Updates kostenlos erhält. Erweiterungspakete, die viel neue Spielinhalte ausliefern, könnte man als kostenpflichtiges Major-Update realisieren. In der Praxis bewähren sich dafür aber eher separate Software-Module, z.B. in Form von DLCs. Ich habe in meinem episodischen Spiel A Room Beyond (kostenlose) Major-Updates bei der Veröffentlichung jedes neuen Kapitels eingesetzt, zumal alle Inhalte Teil der selben Spieldatei waren.
  2. Die zweite Zahl nennt sich „Minor“-Bestandteil, also Unterversionsnummer. Sie ändert sich öfter als die Major-Nummer und wird zum Beispiel verwendet, um Ergänzungen im Funktionsumfang einer Software zu veröffentlichen. Bei Anwendungssoftware sind Minor-Updates häufig kostenlos und Kunden erhalten beim Kauf die Zusage, dass sie mit der Einmalzahlung auch alle Updates bis zur nächsten Hauptversion (Major) kostenlos erhalten. Bei Games eignen sich Minor-Updates gut, um kleinere Ergänzungen, eventuell auch Fehlerbehebungen, zu veröffentlichen.
  3. Die dritte Zahl beschreibt den „Revision“-Bestandteil, auf deutsch etwa Überarbeitungsversion. Sie ändert sich oft und wird meist eingesetzt, um Fehler zu beheben. Automatische Updates über das Internet liefern häufig Revisions-Update aus, um Sicherheitslücken zu schließen und Programmfehler zu beheben. Weil diese Updates keine Funktionalität hinzufügen, sondern die Software nur warten, eignen sie sich gut, um z.B. im Hintergrund zu laufen.
  4. Die vierte Zahl beschreibt den „Build“-Bestandteil, also die Kompilierungsnummer. Diese Nummer ändert sich bei jeder einzigen Umwandlung des Quelltextes in eine  ausführbare Programmdatei. Sie gewährleistet, dass zwei unterschiedliche Kompilierungen (Builds) garantiert über unterschiedliche Versionsnummern verfügen und sich dadurch eindeutig unterschieden lassen. Meistens vergibt die Compilersoftware bereits automatisch Build-Nummern. Manchmal wird dieser Nummernteil aber wegen seiner eher schlechten Lesbarkeit weggelassen oder in der Programmoberfläche nicht angezeigt.

Software-Aktualisierungen folgen nicht zwingend aufeinander. Die letzte Version einer Software sollte immer alle erforderlichen Aktualisierungen der älteren Versionen enthalten, was besonders relevant ist, wenn Aktualisierungsaktionen nötig sind. Wenn es zum Beispiel drei Versionen Deines Spiels gibt, 1.0, 1.1 und 1.2 und die letzten beiden Updates z.B. jeweils etwas am Format der vorhandenen Speicherstände verändern, so muss sichergestellt sein, dass ein Spieler, der Version 1.0 installiert hat und dann einfach auf 1.2 aktualisiert, auch die Korrekturen erhält, die normalerweise in Update 1.1 ausgeliefert werden.

Assembly mit Versionsnummer versehen

Wir können die Versionierungswerkzeuge, die das .net-Frameworks in C# bereitstellt, in Unity einsetzen, um unsere ausführbaren Spieldaten mit Versionsnummern zu versehen.

Beachte den Geltungsbereich

Die hier beschriebenen Versionsnummern arbeiten auf Assembly-Ebene, d.h. für Untiy vereinfacht gesagt, dass jedes Modul bzw. jede DLL-Datei eine eigene Versionierung hat. Das ist dann besonders relevant, wenn Du Bibliothekscode in DLLs auslagerst.

Du brauchst zunächst eine C#-Klasse, an die die Versionsinformation angehängt wird. Dazu eignet sich eine zentrale Klasse sehr gut, z.B. eine, die das Spiel insgesamt repräsentiert. Füge dann AssemblyVersion-Meta-Anweisung vor der Klasse ein und füge die Klausel using System.Reflection am Anfang der C#-Datei ein:

...
using System.Reflection; //required for AssemblyVersion

[assembly:AssemblyVersion ("1.0.*")] //TODO: Update version for each release
public class MyClass: MonoBehaviour 
{
...

Als Parameter erhält AssemblyVersion eine Zeichenkette, die dem oben beschriebenen Versionsnummer-Muster entspricht. Zahlen (1.0) werden exakt in die Nummer übernommen, der Stern (*) wird vom Compiler automatisch mit der Build-Nummer ersetzt. Vor dem Erstellen einer neuen Major- oder Minor-Version müssen die festgeschriebenen Zahlen einfach manuell im Code erhöht werden.

Erstelle das Ausgabeprojekt, z.B. als PC-Standalone-Fassung, über den Build-Befehl im File-Menü. Öffne den Ausgabeordner im Explorer/Finder und suche in den erzeugen Dateien dann die Datei Assembly-CSharp.dll. In den Datei-Eigenschaften ist die Versionsnummer zu sehen:

Die dem Assembly hinzugefügt Versionsnummer. Die Build und Revisonskomponenten wurden automatisch gesetzt.

Die dem Assembly hinzugefügt Versionsnummer. Die Build- und Revisonskomponenten wurden automatisch gesetzt.

Versionsnummer im Code abfragen

Natürlich können wir jetzt diese Nummer im C#-Code auslesen und entsprechend darauf reagieren. Dazu eignet sich die Klasse System.Version. Das für jede Klasse verfügbare Assembly-Objekt liefert eine Instanz der Versionsklasse:

Debug.Log("Assembly-Version is "+typeof(MyClass).Assembly.GetName().Version); 
// Output:
// Assembly-Version is 1.0.6512.28670

Außerdem lässt sich ein Versionsobjekt für eine Zeichenkette erzeugen:

Version v = new Version("1.0.7000");

Beachte, dass nicht alle Versionskomponenten angegeben werden müssen. In diesem Beispiel fehlt etwa die Build-Nummer (bzw. Revisions-Nummer, Erklärung folgt gleich).

Man kann die einzelnen Versionsnummer-Bestandteile nun über die Eigenschaften des Versionsobjekts einzeln abfragen oder zwei Versionsobjekte miteinander vergleichen:

Version av = typeof(MyClass).Assembly.GetName().Version;
Debug.Log("Assembly-Version: "+av);
Version v = new Version("1.0.7000");
Debug.Log("v=" + v);
Debug.Log("v.Major=" + v.Major);
Debug.Log("v.Minor=" + v.Minor);
Debug.Log("v.Revision=" + v.Revision);
Debug.Log("v.Build=" + v.Build);
if (v < av) Debug.Log("V is older than Assembly!");
else Debug.Log("V is equal or newer than Assembly!");

/*
Output:
Assembly-Version: 1.0.6512.29090
v=1.0.7000
v.Major=1
v.Minor=0
v.Revision=-1
v.Build=7000
V is equal or newer than Assembly!
*/

Das Beispiel bringt zwei wichtige Erkenntnisse:

  • Undefinierte Versionsnummer-Komponenten erhalten den Wert -1, wodurch jede andere Version, selbst 0 in einem Vergleich als neuer bewertet wird (ist richtig).
  • Microsoft hat die Reihenfolge von Revision und Build vertauscht! Das ist eigentlich falsch, aber nicht zu ändern.

Eine einfache Lösung für das Reihenfolge-Problem kann darin bestehen, Revision und Build gedanklich zusammenzufassen und die Versionsnummer so zu behandeln, als hätte sie nur die drei Stellen Major.Minor.Build. In der Praxis spielt die vierte Komponente dann einfach keine wesentliche Rolle mehr.

Updates über die Versionsnummer entdecken

Mit diesem Verfahren lassen sich nun der Aktualisierungsbedarf abfragen. Wenn das Spiel nicht über einen Client wie Steam ausgeliefert wird, sondern auf einem Webserver zum Download bereit steht, könnte man die auf dem Server hinterlegte Versionsnummer der Online-Version mit der Version des laufenden Spiels vergleichen. Ist die Version auf dem Server neuer, wird eine Update-Meldung angezeigt (oder das automatische Update gestartet).

Ich empfehle Dir bereits während der Entwicklung, spätestens aber wenn erste Spieler testen/spielen, die aktuelle Versionsnummer der ausführbaren Datei im Spielstand mitzuspeichern. Das Spiel kann beim Laden eines Spielstandes dann die Version aus dem Savegame mit der Programmversion vergleichen und feststellen, wenn ein Spielstand mit einer älteren Spielversion gespeichert wurde. Manchmal kommt es vor, dass ein Programmfehler eine Korrektur von alten Spielständen erfordert. Das lässt sich auf diese Weise sehr einfach umsetzen.

Versionierung und Mobile Games

Das oben beschriebene Prinzip funktioniert auch für Handy-Spiele. Allerdings hat das App-Paket, dessen Version der Verwaltung durch App-Stores unterliegt, noch eine weitere separate Versionsnummer. Diese findet sich über das Menü Edit » Project Settings » Player und dann im Inspektor-Feld Version. Diese Nummer muss manuell gesetzt werden, spielt aber lediglich in der fertigen Version eine Rolle. Vor dem Build des Projekts muss diese Nummer also manuell angepasst werden, am besten auf ähnliche Werte wie die Assembly-Nummer.

Dieser Wert lässt sich per Code übrigens über PlayerSettings.bundleVersion auslesen.

 

Werbung

 

Zusammenfassung

Zur Versionsnummerierung von ausführbaren Unity-Spielen kann die Assembly-Versionierung von .net zum Einsatz kommen. Das Auslesen von Versionsnummern, die in der Regel aus drei oder vier Zahlenkomponenten bestehen, ist in der Klasse System.Version bereits eingebaut. Das Vergleichen dieser Versionobjekte erleichtert die Umsetzung von Update-Vorgängen erheblich. Es ist anzuraten, die aktuelle Programmversion auch in Savegames zu hinterlegen, um auf in zukünftigen Versionen anfallende Wartungsläufe bereits von Anfang an vorbereitet zu sein.

Bildnachweise:

Dr. René Bühling

Hi, mein Name ist René und ich möchte Dir dabei helfen, deinen Traum vom eigenen Computerspiel Wirklichkeit werden zu lassen. Mein erstes kommerziell veröffentlichtes Spiel habe ich Mitte der 1990er Jahre als Hobby-Projekt mit einem Basic-Dialekt unter Windows entwickelt. Seither verfolge ich das Thema Spieleentwicklung in Hobby, Studium und Beruf. Ich habe über 20 Jahre Erfahrung in allen Phasen des Entwicklungsprozesses, die ich gerne mit dir teilen möchte.