Deployment für ein Projekt mit der Vektoria-Engine durchführen


Wenn sich ein (kleineres) Softwareprojekt – wie unser DV-Projekt – dem Ende neigt, wird es Zeit, sich ein paar Gedanken um das Deployment zu machen (angeregt durch den Kommentar von Taiki sei hier nochmals erwähnt, dass dies keine generelle Aussage sein soll, sondern sich konkret auf unser Projekt bezieht). Konkret geht es um die Fragestellung, wie man eine ausführbare Datei (.exe unter Windows) erstellt, welche auch auf Rechnern gestartet werden kann, die nicht als Entwicklungsumgebung eingerichtet sind. Um die notwendigen Schritte und Besonderheiten soll es in diesem Blogeintrag gehen.

Das Ziel soll es also sein, einen Ordner mit einer ausführbaren Datei zu erstellen, welche direkt gestartet werden kann. Dabei ist davon auszugehen, dass auf dem Zielsystem noch nichts installiert ist. Man muss also sicherstellen, dass spezielle Abhängigkeiten mit in den Ordner kopiert werden. Abhängigkeiten kann es dabei viele geben, welche sich auch von Projekt zu Projekt unterscheiden. Als Beispiel können die verwendeten Texturen, Sounds oder benötigte Dateien der Vektoria-Engine selbst genannt werden.

Es gibt eine Abhängigkeit, welche (so gut wie) jedes C++-Projekt besitzt: die Abhängigkeit zur C(++) Runtime-Library. Diese wird benötigt, wenn man Elemente aus der STL verwendet, wobei zwischen einer C und einer C++ Laufzeitbibliothek unterschieden wird (meistens wird beides benötigt). Grundsätzlich ist es so, dass man hier die Wahl hat, ob man die Bibliothek dynamisch zur Laufzeit als .dll laden möchte oder bereits zur Kompilierungszeit statisch gegen die Laufzeitbibliothek linkt. Im Falle eines Projektes mit der Vektoria-Engine hat man hier gar keine Wahl, da die Bibliotheken der Engine bereits dynamisch linken und man dies daher für seinen Code ebenso handhaben muss.

Um die abhängigen Dateien mitzugeben, gibt es mehrere Möglichkeiten. Man kann eine extra Datendatei erstellen, alles in die .exe packen oder - der einfachste Fall - die relevanten Ordner kopieren. In unserem Projekt haben wir uns für die letzte (einfachste) Möglichkeit entschieden. Im Folgenden nun eine Auflistung aller Abhängigkeiten und deren Auflösung:

  • Zuerst sollte man sicherstellen, dass in den Projekteigenschaften bei den Konfigurationseigenschaften -> C/C++ -> Codegenerierung -> Laufzeitbibliothek der Schalter /MD gesetzt ist.
  • Nun das Projekt im Release-Modus kompilieren und die erstellte .exe in den Zielorder kopieren.
  • Da man dynamisch gegen die Laufzeitbibliothek gelinkt hat, muss man diese auch mit zur Verfügung stellen. Dazu die Dateien msvcp120.dll (C++-Bibliothekt) und msvcr120.dll (C-Bibliothek) aus dem Ordner C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\redist\x64\Microsoft.VC120.CRT in den Zielordner kopieren.
  • Den Ordner res aus dem Projekt in den Zielordner kopieren. Hier sind benötigte Dateien der Vektoria-Engine enthalten.
  • Der Ordner shaders muss ebenfalls in den Zielordner kopiert werden, damit auf dem Zielsystem auch die Shader gefunden werden können.
  • Zuletzt noch alle sonstigen Ordner, welche für das Spiel relevant sind und wofür relative Pfadangaben gemacht wurden, mit in den Zielordner kopieren (z. B. textures, sounds, etc.)

Der entsprechende Ordner sieht in unserem Projekt beispielsweise wie folgt aus:

Ordnerstruktur

Zuletzt muss man noch dafür sorgen, dass auf dem Zielrechner die DirectX-Runtime-Library installiert ist. Das sind die allgemeinen Bibliotheken, welche benötigt werden, wenn man DirextX verwenden möchte. Diese sind häufig bereits vorhanden (z. B. wenn bereits ein anderes Spiel auf dem System installiert ist). Daher muss man hier nicht unbedingt etwas installieren. Falls beim Programmstart aber Fehler kommen, wie z. B. das Fehlen der d3dx11_43.dll Datei, muss man die Bibliotheken noch installieren.

Wenn alles funktionierte, könnt ihr nun euren erstellten Ordner auf einen anderen Rechner kopieren und dort eure Anwendung starten. Sollte es nicht funktionieren, fehlt entweder noch eine Abhängigkeit oder der Zielrechner besitzt keine geeignete Grafikkarte und/oder aktualisierte Treiber. Wenn der Zielrechner nicht mit der Vektoria-Engine verwendet werden kann, äußert sich dies meistens darin, dass das Programm gestartet aber sofort wieder beendet wird. In diesem Fall kann man leider nichts machen und muss es auf einem anderen Rechner versuchen.

Animierte Texturen für die Vektoria-Engine erzeugen


Sich bewegende Texturen in einem 3D-Spiel sind etwas, was sofort für Dynamik im Spiel sorgt. Auch wir verwenden in unserem DV-Projekt animierte Texturen. In diesem Blogeintrag soll es daher darum gehen, wie diese mit der Vektoria-Engine verwendet werden können. Als Beispiel dient ein Icon, welches bei uns einen Streik der Mitarbeiter repräsentieren soll. Das komplette Icon sieht folgendermaßen aus und für die Animation soll es entsprechend radial verschwinden.

Strike-Symbol

Die Basis für eine animierte Textur ist eine Sequenz von Bildern, welche die Animation repräsentiert. Aus diesen Einzelbildern muss dann eine gesamte, fortlaufende Textur erstellt werden. In unserem Beispiel gibt es 30 Einzelbilder für die Animation, welche als fortlaufende PNG-Sequenz vorliegen (Dateinamen von strike/strike_00.png bis strike/strike_29.png):

Dateien von der Sequenz für das Strike-Symbol

Zum aktuellen Zeitpunkt funktionieren die Animationen in der Vektoria-Engine am besten, wenn das Gesamtbild mit einem 2-Zeilenlayout erstellt wird. In unserem Beispiel sollte das fertige Bild am Ende so aussehen:

Sequenz für das Strike-Symbol

Um das Erstellen dieses Bildes zu vereinfachen habe ich ein Konsolenprogramm geschrieben, welches das Gesamtbild aus der Sequenz automatisch erzeugt. Es ist diesem Blogeintrag am Ende angehängt. Der allgemeine Aufruf und der für unser Beispiel ist im Folgenden zu sehen.


CreateAnimatedTextures <path_before_numbers> <number_of_images>
CreateAnimatedTextures strike/strike_ 30

Der erste Parameter erwartet den Pfad zu den Bildern, bevor die Nummern kommen. Der zweite Parameter ist für die Gesamtanzahl an Bildern gedacht. Das Ergebnis ist das Bild strike/strike_png, wie oben zu sehen. Für die Einzelbilder gelten folgende Einschränkungen:

  • Es werden nur PNG Bilder unterstützt.
  • Alle Bilder müssen das gleiche Format und die gleiche Größe aufweisen.
  • Die Gesamtanzahl an Einzelbildern muss gerade sein (ansonsten geht das 2-Zeilenlayout nicht auf).

Der Quellcode für das Programm ist auch bei unserem Projekt auf GitHub einsehbar.

Um die Textur in der Vektoria-Engine zu verwenden, muss ein entsprechendes Material angelegt und dort die Animation aktiviert werden. Für letzteres gibt es die Methode CMaterial::SetAni(int ixPics, int iyPics, float fFps). Die ersten beiden Parameter geben das Layout des Gesamtbildes an (Aufteilung in Spalten und Zeilen). Mit dem dritten Parameter kann man die Abspielungsgeschwindigkeit beeinflussen. Dabei wird angegeben, wie viele Einzelbilder aus der Sequenz pro Sekunde weitergeschallten werden sollen. Da es sich hier um einen float Wert handelt, sind auch beliebige Zeiten möglich.

In unserem Beispiel soll der komplette Durchlauf der Animation 120 Sekunden betragen. Bei insgesamt 30 Bildern ergeben sich so 30/120 = 0.25 fps. Der Aufruf gestaltet sich dann in etwa so


CMaterial materialAnimated;
materialAnimated.MakeTextureSprite("textures/animations/strike_.png");  // Oder Diffuse o. ä.
materialAnimated.SetAni(15, 2, 30.0 / 120.0);   // Spalten, Zeilen, fps

Zum Schluss noch wie versprochen das Programm CreateAnimatedTextures, welches das Gesamtbild erzeugt.

VektoriaAnimierteTexturen_Tool.zip

Vollständiges Umbenennen eines Visual Studio-Projektes


Wenn man mit der Vektoria-Engine arbeitet, möchte man häufig neue Testprojekte erstellen. Nichts liegt dann näher, als ein vorhandenes Projekt als Basis zu nehmen. Da man für das neue Projekt wahrscheinlich einen anderen Namen vergeben möchte, kommt man schnell zu der Aufgabe, sein Projekt auch vollständig (!) umzubenennen. Leider gestaltet sich dieser Schritt schwieriger, als man auf den ersten Blick glauben mag. Deswegen im Folgenden eine kurze Anleitung dazu.

  1. Zuerst den Hauptordner umbenennen, d. h. denjenigen, der alle weiteren Dateien enthält. Damit aber auch die anderen Projektteile den neuen Namen bekommen, ist noch etwas mehr zu tun.
  2. Auf der ersten Ebene eures Projektes folgende Einträge bearbeiten:
    1. Den Ordner für eure Projektmappe (könnte z. B. noch VektoriaApp heißen) umbenennen.
    2. Die Dateien mit den Endungen *.sln, *.sdf, *.suo und *.v12.suo (jeweils nur sofern vorhanden) umbenennen.
    3. Die *.sln-Datei mit einem normalen Editor (z. B. Notepad++) öffnen und in der entsprechenden Zeile den neuen Namen für die Projektmappe und den Pfad anpassen (das ist der Ordner, der in Schritt 2.1 geändert wurde). Für das SampleProject sieht die Zeile folgendermaßen aus:
      
      Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SampleProject", "SampleProject\SampleProject.vcxproj", "{875CCC02-C17C-4145-A074-6C7FB9250A48}"
                      
      
  3. Auf der zweiten Ebene eures Projektes sind noch folgende Dateien zu bearbeiten:
    1. Die Dateien mit den Endungen *.vcxproj, *.vcxproj.filters und *.vcxproj.user umbenennen (wieder nur sofern vorhanden).
    2. Die *.vcxproj-Datei in einem normalen Editor öffnen und im RootNamespace-Tag den neuen Namen eintragen.

Achtet darauf, dass bei Dateien mit zusätzlichen Endungen (z. B. *.vcxproj.filters) auch nach dem Umbenennen noch die komplette Endung erhalten bleibt. Unter Windows ist das ein leichter Fehler, da beim Umbenennen die zusätzlichen Endungen mit markiert werden.

Nachdem alle Schritte erfolgreich durchgeführt wurden, ist das Projekt vollständig umbenannt. In der folgenden Auflistung sind noch einmal die Schritte im Dateiexplorer für ein Projekt zusammengefasst, welches von VektoriaApp nach SampleProject umbenannt wurde.


VektoriaApp --> SampleProject
|-- VektoriaApp --> SampleProject
|   |-- VektoriaApp.vcxproj --> SampleProject.vcxproj
|   |-- VektoriaApp.vcxproj.filters --> SampleProject.vcxproj.filters
|   |-- VektoriaApp.vcxproj.user --> SampleProject.vcxproj.user
|   `-- ...
|-- VektoriaApp.sdf --> SampleProject.sdf
|-- VektoriaApp.sln --> SampleProject.sln
|-- VektoriaApp.suo --> SampleProject.suo
|-- VektoriaApp.v12.suo --> SampleProject.v12.suo
`-- ...

Konkret für ein Projekt der Vektoria-Engine könnte das Ergebnis wie in den folgenden beiden Bildern gezeigt aussehen.

Ordnerstruktur des Projektmappenverzeichnisses nach Umbenennung des Projektes Ordnerstruktur des Projektverzeichnisses nach Umbenennung des Projektes
Dateiexplorer nach dem Umbenennen eines Projektes der Vektoria-Engine. Links ist das Projektmappenverzeichnis und rechts das Projektverzeichnis gezeigt.

Es sei noch angemerkt, dass ein einfaches Umbenennen des entsprechenden Hauptordners auch seinen Zweck erfüllt. Nur ist es dann so, dass eurer Projekt in Visual Studio sowie die bereits vorhandenen Projektdateien alle noch den alten Namen tragen. Die hier beschriebene Methode ist also vor allem für diejenigen gedacht, welche ihr Projekt in der Art umbenennen möchten, so dass am Ende der alte Name nicht mehr auftaucht.

Es ist schade, dass Visual Studio für diesen Schritt keinen Assistenten oder Ähnliches anbietet. Fall jemand eine Extension kennt, welche die obigen Schritte durchführt, so würde ich mich über einen entsprechenden Hinweis sehr freuen.

Projekteigenschaftenblatt für die Vektoria-Engine


Die Vektoria-Engine ist in C++ geschrieben und derzeit nur für Windows erhältlich. Gedacht ist die Verwendung in Visual Studio 2013, wobei die Engine selbst als statische Library eingebunden wird. Des Weiteren wird auch das DirectX SDK benötigt, da Vektoria diese API als Basis verwendet.

Dementsprechend muss ein Visual Studio-Projekt erst einmal korrekt konfiguriert werden, so dass der Pfad zu den Libraries und den Includes bekannt gemacht wird. Um diesen Schritt zu vereinheitlichen, möchte ich im Folgenden eine portable Lösung auf Basis von Visual Studio Project Property Files (Projekteigenschaftenblätter) vorstellen.

Zuerst zur Frage, was es mit diesen Dateien auf sich hat. Um in Visual Studio die Einstellungen am aktuellen Projekt vorzunehmen, gibt es zwei verschiedene Möglichkeiten:

  • Zum einen kann man die Projekteigenschaften selbst verändern (Projekt → Eigenschaften) und dort Einstellungen für die entsprechende Kombination aus Konfiguration und Plattform vornehmen (z. B. Debug, x64). Damit sind die Einstellungen fest an ein bestimmtes Projekt gebunden und eine Portierung auf andere Projekte geht damit verloren bzw. wird sehr umständlich.
  • Zum anderen bietet Visual Studio die Möglichkeit an, mit den bereits genannten Project Property Files zu arbeiten. Dazu gibt es sogar ein Extrafenster Eigenschaften-Manager: Eigenschaften-Manager in Visual Studio Für jede Kombination aus Konfiguration und Plattform gibt es hier einen Ordner und jedem Ordner können mehrere Eigenschaftenblätter hinzugefügt werden. In diesen Eigenschaftenblättern kann man dann die gleichen Einstellungen vornehmen, wie man sie auch bei den Projekteigenschaften vornehmen würde. Der Unterschied ist jetzt aber, dass die Einstellungen nicht im Projekt selbst, sondern in einer Extradatei abgespeichert werden (Im Projekt selbst wird dann nur noch der Pfad zu dieser Datei abgespeichert.). Dadurch hat man natürlich ein hohes Maß an Portabilität gewonnen, denn einmal vorgenommene Einstellungen können so leicht in andere Projekten importiert werden.

Bei den Einstellungen selbst besteht bei diesen beiden Methoden vor allem bei Wahl der Konfiguration und der Plattform ein großer Unterschied. Während man bei den Projekteigenschaften eine Auswahl treffen muss, so sind die Einstellungen in einem Eigenschaftenblatt erst einmal unabhängig von der gewählten Kombination. Diese Wahl trifft man erst, wenn man das Eigenschaftenblatt dem Projekt hinzufügt. Denn wie bereits erwähnt, gibt es im Eigenschaften-Manager mehrere Ordner, welche die jeweilige Kombination repräsentieren. Das Eigenschaftenblatt wird nämlich immer genau der Kombination hinzugefügt, welche dem Ordner entspricht, dem man das Blatt hinzugefügt hat. Wenn man beispielsweise ein Eigenschaftenblatt dem Ordner Debug | Win32 hinzufügt, so gelten die gewählten Eigenschaften im Blatt auch nur für diese Kombination.

Eine Wiederverwendung von einem Eigenschaftenblatt für mehrere Kombinationen wird nun dadurch möglich, dass man ein Blatt auch mehreren Ordnern hinzufügen kann.

Zeit die Vektoria-Engine ins Spiel zu bringen. Wie eingangs erwähnt muss man ein Visual Studio-Projekt entsprechend konfigurieren, um die Engine verwenden zu können. Konkret muss man die Library- und Include-Pfade zum DirectX SDK und zur Engine selbst angeben. Um die genannten Vorteile (Portabilität) bei der Konfiguration nutzen zu können, habe ich für die Engine entsprechende Eigenschaftenblätter angelegt.

Wegen einer aktuell noch nicht einheitlichen Namensgebung der Library-Ordner in der Engine1, müssen dafür zwei verschiedene Eigenschaftenblätter importiert werden. Das für die x86-Plattform sieht folgendermaßen aus:


<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup>
    <IncludePath>$(DXSDK_DIR)Include;..\..\..\Lib;$(IncludePath)</IncludePath>
    <LibraryPath>$(DXSDK_DIR)Lib\$(PlatformTarget);$(LibraryPath);..\..\..\Lib\$(Configuration)</LibraryPath>
  </PropertyGroup>
  <ItemDefinitionGroup />
  <ItemGroup />
</Project>

Für die x64-Plattform ergibt sich analog:


<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup>
    <IncludePath>$(DXSDK_DIR)Include;..\..\..\Lib;$(IncludePath)</IncludePath>
    <LibraryPath>$(DXSDK_DIR)Lib\$(PlatformTarget);$(LibraryPath);..\..\..\Lib\$(Configuration)64</LibraryPath>
  </PropertyGroup>
  <ItemDefinitionGroup />
  <ItemGroup />
</Project>

Beide Eigenschaftenblätter gehen davon aus, dass sich der Lib-Ordner von Vektoria 3 Ebenen über dem Projektordner befindet. Der einzige Unterschied besteht also zwischen einem zusätzlichen 64 im LibraryPath bei der x64-Plattform-Variante2.

Spannend ist in beiden Dateien eigentlich nur der IncludePath- respektive LibraryPath-Tags. Nennenswert ist hier vielleicht noch die Verwendung der speziellen Makrovariablen, welche man praktischerweise in Visual Studio verwenden kann. Dies können zum einen Systemvariablen sein (DXSDK_DIR) oder Programmvariablen (PlatformTarget), welche nur in Visual Studio verfügbar sind. Wie bereits ersichtlich, kann der Wert dieser Variablen mit $() ausgelesen werden. Dadurch erreicht man eine enorme Flexibilität bei der Konfiguration seiner Projekte. Die Programmvariable Configuration hält zum Beispiel die aktuell gewählte Konfiguration (Debug bzw. Release im einfachsten Falle).

Für ein Projekt mit der Vektoria-Engine bedeutet das, dass man nur diese beiden Dateien importieren muss und schon sollten alle benötigten Einstellungen getroffen sein. Beide Dateien habe ich auch diesem Blog angehängt.

List of attached files:


1. Es gibt die Ordner Debug64 und Debug. Einheitlich wäre es meiner Meinung nach, wenn der zweite Ordner Debug32 hieße.
2. Auch diesen könnte man sich sparen, wenn der entsprechende Ordner Debug32 und nicht Debug hieße.