Eigene Modelle aus Blender & Substance Painter für Anno 1800/2205
Vorwort
Zuersteinmal Danke an Judekw der sich komplett auf sich allein gestellt die Grafikengine der 3D-Annos erschlossen hat, und an kskudlik und Cyborg für die RDM-OBJ Conversion. Ohne diese Arbeit wäre das hier gar nicht möglich.
Ich gehe jetzt davon aus, dass ich mein Modell bereits erstellt, UV Unwrapped ( beides mit Blender) habe, es in Substance Painter texturieren möchte, und es dann nach Anno bringen will. Zum Modellieren, UV-Unwrappen und Texturieren gibt es auf Youtube bessere Tutorials, als ich das geben könnte. Außerdem setzt diese Anleitung ein höheres Level an Modding-Erfahrung mit Anno 1800 voraus, wie man ein Gebäude in der .cfg richtig zusammensetzt, in der .ifo Blocker schreibt oder wie man aus einer Grafik ein baubares Gebäude macht sollten bereits bekannt sein. Darauf wird hier nicht weiter eingegangen, es geht nur darum, wie man sein Gebäude aus Blender und Substance Painter in Anno reinbekommt.
Benötigte Programme
Ich benutze für die Erstellung des Modells und das UV Unwrapping Blender, für die Texturierung Substance Painter und für die rdm conversion den rdm-obj converter von kskudlik: https://github.com/kskudlik/Anno-1800-Model-Converter/releases
Grundlagen
Ein Modell in Anno ist folgendermaßen aufgebaut:
- Ordner "maps" (von jeder Map gibt es drei Variationen, diff_0 ist doppelt so groß wie diff_1, diff_1 ist doppelt so groß wie diff_2.
- diffuse maps - Farbinformationen in RGB, Transparenzinfo in Alpha.
- normal maps - Normalmap in RG, Glowmap in B, Glossiness in Alpha.
- metallic maps - RGB Metallic, einfärben möglich. Klassisch weiß bedeutet, dass die Farbinformation der diffuse übernommen wird. Alphakanal speichert eine Ambient Occlusion Map.
- mask maps - RGB, Leuchtende Stellen bei Nacht. Weiß bedeutet, dass dieser Teil bei Nacht beleuchtet wird.
- dye maps - RGB, Weiß bedeutet, dass dieser Teil mit der Spielerfarbe eingefärbt wird (Achtung: diffuse und dye werden vermittelt!)
- height maps: Graustufen, damit lässt sich Höhenunterschied ganz gut faken. Eignet sich daher gut für Bodentexturen.
- Ordner "rdm" (hierbei steht das lod_0 für das lodlevel der Grafik (0-4, je nach Distanz, sind weniger vorhanden wird automatisch das letzte bis zum Ende genommen, man kann also auch nur eine lod0 ablegen)
- Alle benötigten Modelle im .rdm Format
- Ordner "anim" (Für uns nicht benötigt, da kein bisher geschriebener Konverter diese unterstützt)
- Alle benötigten Animationen im .rdm Format
- <model>.fc (Zuständig für die Loops des visuellen Feedbacks. Eine Art xml-Dokument, die allerdings einige Teile in Binary hat, die entweder im Hexeditor bearbeitet oder erst konvertiert werden müssen, siehe Maug-Forum ).
- <model>.cfg (Setzt Modelle zusammen, eine Art xml-Dokument, kann mit einem xml-Formatter wesentlich einfacher lesbarer gemacht werden.)
- <model>.ifo (Legt Blocking der Grafik fest, eine Art xml-Dokument, kann ebenso mit xml-Formatter lesbarer gemacht werden.)
- <model>.bfg (Global Illumination, der Unterschied, wenn diese Datei fehlt, ist marginal, daher braucht man diese nicht. Man könnte sie auch eh nicht selbst schreiben.)
Aufsetzen eines Projektes mit Substance
In Substance Painter lassen sich die Eigenschaften der Anno-Engine relativ einfach reproduzieren. Euer Projekt benötigt folgende Kanäle:
- Base Color
- Metallic
- Normal
- Glossiness
- Opacity
- Reflection (Weiß in diesem Kanal bedeutet, dass dieser Teil der Textur bei Nacht leuchtet.)
Mithilfe von Templates können in Substance Painter Projekte direkt mit diesen Kanälen erstellt werden. Dafür kann beim Erstellen eines Projektes die entsprechende Template ausgewählt werden:
Anno 1800-spezifischer Shader
Anno 1800 ist ein wenig besonders in der Hinsicht, dass es PBR mit Metal/Gloss anstatt Metal/Rough umsetzt. Der Unterschied ist nicht allzu groß, es gibt in Substance Painter aber keinen Shader, der das von Haus aus unterstützt. Insofern hier eine kleine Bearbeitung des Pbr-metal-rough-with-alpha-blending Shaders, die statt Roughness eben Glossiness benutzt. Das sollte ausreichend sein, um nicht jedes Mal erst das Spiel zu starten, um zu schauen, ob die Texturen, die in Substance perfekt wirken, denn in 1800 auch gut aussehen. Der Unterschied ist hauptsächlich das Lighting, die Materials sind aber im wesentlichen identisch.
Hier ein Vergleich:
Den Shader könnt ihr rechts einstellen, ein mit der Template aufgesetztes Projekt wird diesen automatisch verwenden.
Export der Texturen aus Substance
Substance ermöglicht nicht nur das Aufsetzen von Projekten mit den Eigenschaften wie Anno 1800, sondern auch das exportieren. Anno 1800 fasst dabei mehrere Informationen in eine Textur zusammen, im Alphakanal der Diffuse Textur steckt bspw. die Opacity Map. Für Anno 1800 sieht das ganze dann so aus:
So und jetzt zum exportieren hier die Template einstellen:
Mein Projekt ist ein ganz normales Gebäude, was keine Heightmap benötigt, daher ist dies im Export abgestellt.
Jetzt exportieren und mit der Template Medium und halber Auflösung sowie der Template Low in Viertel-Auflösung wiederholen.
Downloads
Project-Template, Export-Templates und Shader können hier heruntergeladen werden: Google Drive
Alle drei Ordner müssen unter Documents/Allegorithmic/Substance Painter/shelf abgelegt werden und suchen sich ihren Ort dann selbst.
Der letzte Schritt: Texturen nach dds konvertieren
Die Texturen müssen für Anno in .dds, BC7_UNORM Format vorliegen. Substance Painter unterstützt jedoch kein dds, weshalb man die Texturen noch konvertieren muss. Dazu kann man sie zwar einfach alle in GIMP öffnen und nach dds exportieren, am besten mit automatisiertem Texture Tool, nach dieser Anleitung.
Schneller geht manuelles Konvertieren mit texconv.exe mit folgenden Argumenten, dann wird direkt der ganze Ordner konvertiert.
-f BC7_UNORM -ft DDS -sepalpha *.png
Damit hat man dann alles was man an Texturen benötigt.
Tipps zum schreiben der cfg und ifo
Damit sind die Maps erstellt, bleiben die .cfg und die .ifo. Diese müssen vielleicht nicht von Grund auf neu erstellt werden, aber Partikel und Props, etc. müssen von Hand hinzugefügt werden.
Dabei besonders hilfreich ist, dass man für ORIENTATION_TRANSFORMS auch einfach Blender benutzen kann um bspw Props und Particles dort zu positionieren wo man das möchte. Beachtet dabei allerdings bitte, dass Blender ein Z-Up Koordinatensystem benutzt, während Anno Y-Up arbeitet. Daher müssen sämtliche y- und z- Positionswerte immer vertauscht werden beim copy+pasten.
Rotationen lassen sich sogar aus Blender direkt ablesen (Anno speichert diese im Quaternion-Format, dieses kann man in Blender auch zur Anzeige auswählen. Beachtet dabei aber, dass Y und Z getauscht werden müssen.), oder aber mit der Seite quaternions.online easy aus Euler errechnen lassen.
Für Partikel oder ähnliches lässt sich das genauso machen, einfach einen kleinen Cube an die gewünschte Position ziehen und dort die Position rauskopieren.
Trigger - Einleitung
<Asset>
<Template>Trigger</Template>
<Values>
<Standard />
<Trigger>
<TriggerCondition>
//hier wird eine condition erwartet
</TriggerCondition>
<TriggerActions>
<Item>
//hier wird eine Action erwartet
</Item>
</TriggerAction>
</Trigger>
<TriggerSetup />
</Values>
</Asset>
Ein sehr simples, aber mächtiges Konzept, was seit Anno 2205 existiert, sind die Trigger.
Von Fishboss im Sommer 2019 entdeckt, basiert das Unlocksystem auf Triggern, basiert auf Triggern, die ausgelöst werden, sobald ihre Bedingung erfüllt ist. Werden sie einmal ausgelöst, dann schalten sie alles frei was sie freischalten sollen und bleiben dann ausgelöst.
Ein Trigger implementiert also folgendes Konzept: Wenn Vorraussetzung (Condition) erfüllt - ein oder mehrere Aktionen (Action) ausführen.
Dabei gibt es in der templates.xml eine Reihe vordefinierter Conditions und Actions. Auf diese ist man zwar beschränkt (gibt auch noch Codemüll aus Anno 2205) - aber sie können extrem viel und sind vielseitig einsetzbar. Diese Conditions und Actions können beliebig miteinander kombiniert werden. So kann man bspw die Freischaltung der schweren KI in der KI-Schiffswerft an den Bau eines Gebäudes (z.B. einer Botschaft) anstatt einer erreichten Bevölkerungszahl knüpfen. Die Actions eines Triggers werden in ihrer Reihenfolge ausgelöst.
Registrierung von Triggern
Folgendes Problem: Ich habe einen Trigger zum freischalten der Bank bei einem Ingeneur geschrieben und ihn ausgelöst, Bank freigeschaltet und Bewohner happy. Im Nachhinein fällt mir dann aber auf, dass ich doch auch das Clubhaus gerne ab einem Ingeneur haben will, also füge ich es in meinen Trigger ein. Lade ich meinen Spielstand erneut, fällt mir aber auf, dass es gar nicht freigeschaltet ist, obwohl ich doch einen Ingeneur habe?
Warum ist das so?
Anno verwaltet die Trigger in einer internen Eventliste, in der ein Trigger erst registriert werden muss. Beim Registrieren des Triggers wird das Asset geparst und auf dessen Basis für Anno ein Event erstellt. In unserem Fall: 1 Ingeneur erreicht -> Bank freischalten. Dieses Event erhält die GUID des Triggers als ID. Befindet sich Event 42 bereits in der Eventliste, werden keine Änderungen an diesem Event mehr vorgenommen, egal, ob wir den Trigger ändern. Wird Event 42 in der Eventliste ausgelöst, merkt sich das Spiel das. Egal, was wir jetzt am Asset mit GUID = 42 ändern, das Spiel wird es nicht von selbst neu registrieren.
Im Normalfall werden Assets mit der Template Trigger beim ersten Mal automatisch registriert, außer, wir stellen das ab. (siehe unten)Danach müssen wir manuell neu registrieren. Zum Testen ist es meistens ganz hilfreich, einfach die GUID des Assets zu ändern, weil Anno den Trigger dann als neues Event sieht und entsprechend neu einliest. Zur manuellen Registrierung kommen jetzt ein paar Handgriffe:
Ein paar Handgriffe für Trigger
Einen Trigger nicht automatisch registrieren: unter TriggerSetup kann man das Automatische Registrieren des Triggers abschalten
<TriggerSetup>
<AutoRegisterTrigger>0</AutoRegisterTrigger>
</TriggerSetup>
Einen Trigger durch einen anderen Trigger registrieren lassen: Über die ActionRegisterTrigger - Das geht auch, wenn der Trigger bereits ausgelöst wurde! Nach der Neuregistrierung kann der dann wieder ausgelöst werden.
<Template>ActionRegisterTrigger</Template>
<Values>
<Action />
<ActionRegisterTrigger>
<TriggerAsset>1443041</TriggerAsset>
</ActionRegisterTrigger>
</Values>
Einen Trigger sich selbst zurücksetzen lassen: Über die ActionResetTrigger
<Template>ActionResetTrigger</Template>
<Values>
<Action/>
<ActionResetTrigger/>
</Values>
Einen Trigger erst bei Erfüllung einer bestimmten Bedingung registrieren lassen: Das ganze geht über einen SubTrigger im SubTriggers Container. Dieser muss unter Values/Trigger.
<SubTriggers>
<Item>
<SubTrigger>
<Template>AutoCreateTrigger</Template>
<Values>
<Trigger>
<TriggerCondition>
//Hier wird eine Condition erwartet
</TriggerCondition>
</Trigger>
</Values>
</SubTrigger>
</Item>
</SubTriggers>
<SubTriggers>
<Item>
<SubTrigger>
<Template>AutoCreateTrigger</Template>
<Values>
<Trigger>
<TriggerCondition>
//Hier wird eine Condition erwartet
</TriggerCondition>
</Trigger>
</Values>
</SubTrigger>
</Item>
</SubTriggers>
<SubTriggers>
<Item>
<SubTrigger>
<Template>AutoCreateTrigger</Template>
<Values>
<Trigger>
<TriggerCondition>
//Hier wird eine Condition erwartet
</TriggerCondition>
</Trigger>
</Values>
</SubTrigger>
</Item>
</SubTriggers>
<SubTriggers>
<Item>
<SubTrigger>
<Template>AutoCreateTrigger</Template>
<Values>
<Trigger>
<TriggerCondition>
//Hier wird eine Condition erwartet
</TriggerCondition>
</Trigger>
</Values>
</SubTrigger>
</Item>
</SubTriggers>
Code ausklappen
Einem SubTrigger können auch weitere SubTrigger hinzugefügt werden, wer Lust hat, kann sich mal die uPlay-Club Herausforderung "Silocon Valley" anschauen, da ist es aufs Extremste getrieben worden.
Trigger - Einleitung
<Asset>
<Template>Trigger</Template>
<Values>
<Standard />
<Trigger>
<TriggerCondition>
//hier wird eine condition erwartet
</TriggerCondition>
<TriggerActions>
<Item>
//hier wird eine Action erwartet
</Item>
</TriggerAction>
</Trigger>
<TriggerSetup />
</Values>
</Asset>
Ein sehr simples, aber mächtiges Konzept, was seit Anno 2205 existiert, sind die Trigger.
Von Fishboss im Sommer 2019 entdeckt, basiert das Unlocksystem auf Triggern, basiert auf Triggern, die ausgelöst werden, sobald ihre Bedingung erfüllt ist. Werden sie einmal ausgelöst, dann schalten sie alles frei was sie freischalten sollen und bleiben dann ausgelöst.
Ein Trigger implementiert also folgendes Konzept: Wenn Vorraussetzung (Condition) erfüllt - ein oder mehrere Aktionen (Action) ausführen.
Dabei gibt es in der templates.xml eine Reihe vordefinierter Conditions und Actions. Auf diese ist man zwar beschränkt (gibt auch noch Codemüll aus Anno 2205) - aber sie können extrem viel und sind vielseitig einsetzbar. Diese Conditions und Actions können beliebig miteinander kombiniert werden. So kann man bspw die Freischaltung der schweren KI in der KI-Schiffswerft an den Bau eines Gebäudes (z.B. einer Botschaft) anstatt einer erreichten Bevölkerungszahl knüpfen. Die Actions eines Triggers werden in ihrer Reihenfolge ausgelöst.
Registrierung von Triggern
Folgendes Problem: Ich habe einen Trigger zum freischalten der Bank bei einem Ingeneur geschrieben und ihn ausgelöst, Bank freigeschaltet und Bewohner happy. Im Nachhinein fällt mir dann aber auf, dass ich doch auch das Clubhaus gerne ab einem Ingeneur haben will, also füge ich es in meinen Trigger ein. Lade ich meinen Spielstand erneut, fällt mir aber auf, dass es gar nicht freigeschaltet ist, obwohl ich doch einen Ingeneur habe?
Warum ist das so?
Anno verwaltet die Trigger in einer internen Eventliste, in der ein Trigger erst registriert werden muss. Beim Registrieren des Triggers wird das Asset geparst und auf dessen Basis für Anno ein Event erstellt. In unserem Fall: 1 Ingeneur erreicht -> Bank freischalten. Dieses Event erhält die GUID des Triggers als ID. Befindet sich Event 42 bereits in der Eventliste, werden keine Änderungen an diesem Event mehr vorgenommen, egal, ob wir den Trigger ändern. Wird Event 42 in der Eventliste ausgelöst, merkt sich das Spiel das. Egal, was wir jetzt am Asset mit GUID = 42 ändern, das Spiel wird es nicht von selbst neu registrieren.
Im Normalfall werden Assets mit der Template Trigger beim ersten Mal automatisch registriert, außer, wir stellen das ab. (siehe unten)Danach müssen wir manuell neu registrieren. Zum Testen ist es meistens ganz hilfreich, einfach die GUID des Assets zu ändern, weil Anno den Trigger dann als neues Event sieht und entsprechend neu einliest. Zur manuellen Registrierung kommen jetzt ein paar Handgriffe:
Ein paar Handgriffe für Trigger
Einen Trigger nicht automatisch registrieren: unter TriggerSetup kann man das Automatische Registrieren des Triggers abschalten
<TriggerSetup>
<AutoRegisterTrigger>0</AutoRegisterTrigger>
</TriggerSetup>
Einen Trigger durch einen anderen Trigger registrieren lassen: Über die ActionRegisterTrigger - Das geht auch, wenn der Trigger bereits ausgelöst wurde! Nach der Neuregistrierung kann der dann wieder ausgelöst werden.
<Template>ActionRegisterTrigger</Template>
<Values>
<Action />
<ActionRegisterTrigger>
<TriggerAsset>1443041</TriggerAsset>
</ActionRegisterTrigger>
</Values>
Einen Trigger sich selbst zurücksetzen lassen: Über die ActionResetTrigger
<Template>ActionResetTrigger</Template>
<Values>
<Action/>
<ActionResetTrigger/>
</Values>
Einen Trigger erst bei Erfüllung einer bestimmten Bedingung registrieren lassen: Das ganze geht über einen SubTrigger im SubTriggers Container. Dieser muss unter Values/Trigger.
<SubTriggers>
<Item>
<SubTrigger>
<Template>AutoCreateTrigger</Template>
<Values>
<Trigger>
<TriggerCondition>
//Hier wird eine Condition erwartet
</TriggerCondition>
</Trigger>
</Values>
</SubTrigger>
</Item>
</SubTriggers>
<SubTriggers>
<Item>
<SubTrigger>
<Template>AutoCreateTrigger</Template>
<Values>
<Trigger>
<TriggerCondition>
//Hier wird eine Condition erwartet
</TriggerCondition>
</Trigger>
</Values>
</SubTrigger>
</Item>
</SubTriggers>
<SubTriggers>
<Item>
<SubTrigger>
<Template>AutoCreateTrigger</Template>
<Values>
<Trigger>
<TriggerCondition>
//Hier wird eine Condition erwartet
</TriggerCondition>
</Trigger>
</Values>
</SubTrigger>
</Item>
</SubTriggers>
<SubTriggers>
<Item>
<SubTrigger>
<Template>AutoCreateTrigger</Template>
<Values>
<Trigger>
<TriggerCondition>
//Hier wird eine Condition erwartet
</TriggerCondition>
</Trigger>
</Values>
</SubTrigger>
</Item>
</SubTriggers>
Code ausklappen
Einem SubTrigger können auch weitere SubTrigger hinzugefügt werden, wer Lust hat, kann sich mal die uPlay-Club Herausforderung "Silocon Valley" anschauen, da ist es aufs Extremste getrieben worden.
Eigene Modelle aus Blender & Substance Painter für Anno 1800/2205
Vorwort
Zuersteinmal Danke an Judekw der sich komplett auf sich allein gestellt die Grafikengine der 3D-Annos erschlossen hat, und an kskudlik und Cyborg für die RDM-OBJ Conversion. Ohne diese Arbeit wäre das hier gar nicht möglich.
Ich gehe jetzt davon aus, dass ich mein Modell bereits erstellt, UV Unwrapped ( beides mit Blender) habe, es in Substance Painter texturieren möchte, und es dann nach Anno bringen will. Zum Modellieren, UV-Unwrappen und Texturieren gibt es auf Youtube bessere Tutorials, als ich das geben könnte. Außerdem setzt diese Anleitung ein höheres Level an Modding-Erfahrung mit Anno 1800 voraus, wie man ein Gebäude in der .cfg richtig zusammensetzt, in der .ifo Blocker schreibt oder wie man aus einer Grafik ein baubares Gebäude macht sollten bereits bekannt sein. Darauf wird hier nicht weiter eingegangen, es geht nur darum, wie man sein Gebäude aus Blender und Substance Painter in Anno reinbekommt.
Benötigte Programme
Ich benutze für die Erstellung des Modells und das UV Unwrapping Blender, für die Texturierung Substance Painter und für die rdm conversion den rdm-obj converter von kskudlik: https://github.com/kskudlik/Anno-1800-Model-Converter/releases
Grundlagen
Ein Modell in Anno ist folgendermaßen aufgebaut:
- Ordner "maps" (von jeder Map gibt es drei Variationen, diff_0 ist doppelt so groß wie diff_1, diff_1 ist doppelt so groß wie diff_2.
- diffuse maps - Farbinformationen in RGB, Transparenzinfo in Alpha.
- normal maps - Normalmap in RG, Glowmap in B, Glossiness in Alpha.
- metallic maps - RGB Metallic, einfärben möglich. Klassisch weiß bedeutet, dass die Farbinformation der diffuse übernommen wird. Alphakanal speichert eine Ambient Occlusion Map.
- mask maps - RGB, Leuchtende Stellen bei Nacht. Weiß bedeutet, dass dieser Teil bei Nacht beleuchtet wird.
- dye maps - RGB, Weiß bedeutet, dass dieser Teil mit der Spielerfarbe eingefärbt wird (Achtung: diffuse und dye werden vermittelt!)
- height maps: Graustufen, damit lässt sich Höhenunterschied ganz gut faken. Eignet sich daher gut für Bodentexturen.
- Ordner "rdm" (hierbei steht das lod_0 für das lodlevel der Grafik (0-4, je nach Distanz, sind weniger vorhanden wird automatisch das letzte bis zum Ende genommen, man kann also auch nur eine lod0 ablegen)
- Alle benötigten Modelle im .rdm Format
- Ordner "anim" (Für uns nicht benötigt, da kein bisher geschriebener Konverter diese unterstützt)
- Alle benötigten Animationen im .rdm Format
- <model>.fc (Zuständig für die Loops des visuellen Feedbacks. Eine Art xml-Dokument, die allerdings einige Teile in Binary hat, die entweder im Hexeditor bearbeitet oder erst konvertiert werden müssen, siehe Maug-Forum ).
- <model>.cfg (Setzt Modelle zusammen, eine Art xml-Dokument, kann mit einem xml-Formatter wesentlich einfacher lesbarer gemacht werden.)
- <model>.ifo (Legt Blocking der Grafik fest, eine Art xml-Dokument, kann ebenso mit xml-Formatter lesbarer gemacht werden.)
- <model>.bfg (Global Illumination, der Unterschied, wenn diese Datei fehlt, ist marginal, daher braucht man diese nicht. Man könnte sie auch eh nicht selbst schreiben.)
Aufsetzen eines Projektes mit Substance
In Substance Painter lassen sich die Eigenschaften der Anno-Engine relativ einfach reproduzieren. Euer Projekt benötigt folgende Kanäle:
- Base Color
- Metallic
- Normal
- Glossiness
- Opacity
- Reflection (Weiß in diesem Kanal bedeutet, dass dieser Teil der Textur bei Nacht leuchtet.)
Mithilfe von Templates können in Substance Painter Projekte direkt mit diesen Kanälen erstellt werden. Dafür kann beim Erstellen eines Projektes die entsprechende Template ausgewählt werden:
Anno 1800-spezifischer Shader
Anno 1800 ist ein wenig besonders in der Hinsicht, dass es PBR mit Metal/Gloss anstatt Metal/Rough umsetzt. Der Unterschied ist nicht allzu groß, es gibt in Substance Painter aber keinen Shader, der das von Haus aus unterstützt. Insofern hier eine kleine Bearbeitung des Pbr-metal-rough-with-alpha-blending Shaders, die statt Roughness eben Glossiness benutzt. Das sollte ausreichend sein, um nicht jedes Mal erst das Spiel zu starten, um zu schauen, ob die Texturen, die in Substance perfekt wirken, denn in 1800 auch gut aussehen. Der Unterschied ist hauptsächlich das Lighting, die Materials sind aber im wesentlichen identisch.
Hier ein Vergleich:
Den Shader könnt ihr rechts einstellen, ein mit der Template aufgesetztes Projekt wird diesen automatisch verwenden.
Export der Texturen aus Substance
Substance ermöglicht nicht nur das Aufsetzen von Projekten mit den Eigenschaften wie Anno 1800, sondern auch das exportieren. Anno 1800 fasst dabei mehrere Informationen in eine Textur zusammen, im Alphakanal der Diffuse Textur steckt bspw. die Opacity Map. Für Anno 1800 sieht das ganze dann so aus:
So und jetzt zum exportieren hier die Template einstellen:
Mein Projekt ist ein ganz normales Gebäude, was keine Heightmap benötigt, daher ist dies im Export abgestellt.
Jetzt exportieren und mit der Template Medium und halber Auflösung sowie der Template Low in Viertel-Auflösung wiederholen.
Downloads
Project-Template, Export-Templates und Shader können hier heruntergeladen werden: Google Drive
Alle drei Ordner müssen unter Documents/Allegorithmic/Substance Painter/shelf abgelegt werden und suchen sich ihren Ort dann selbst.
Der letzte Schritt: Texturen nach dds konvertieren
Die Texturen müssen für Anno in .dds, BC7_UNORM Format vorliegen. Substance Painter unterstützt jedoch kein dds, weshalb man die Texturen noch konvertieren muss. Dazu kann man sie zwar einfach alle in GIMP öffnen und nach dds exportieren, am besten mit automatisiertem Texture Tool, nach dieser Anleitung.
Schneller geht manuelles Konvertieren mit texconv.exe mit folgenden Argumenten, dann wird direkt der ganze Ordner konvertiert.
-f BC7_UNORM -ft DDS -sepalpha *.png
Damit hat man dann alles was man an Texturen benötigt.
Tipps zum schreiben der cfg und ifo
Damit sind die Maps erstellt, bleiben die .cfg und die .ifo. Diese müssen vielleicht nicht von Grund auf neu erstellt werden, aber Partikel und Props, etc. müssen von Hand hinzugefügt werden.
Dabei besonders hilfreich ist, dass man für ORIENTATION_TRANSFORMS auch einfach Blender benutzen kann um bspw Props und Particles dort zu positionieren wo man das möchte. Beachtet dabei allerdings bitte, dass Blender ein Z-Up Koordinatensystem benutzt, während Anno Y-Up arbeitet. Daher müssen sämtliche y- und z- Positionswerte immer vertauscht werden beim copy+pasten.
Rotationen lassen sich sogar aus Blender direkt ablesen (Anno speichert diese im Quaternion-Format, dieses kann man in Blender auch zur Anzeige auswählen. Beachtet dabei aber, dass Y und Z getauscht werden müssen.), oder aber mit der Seite quaternions.online easy aus Euler errechnen lassen.
Für Partikel oder ähnliches lässt sich das genauso machen, einfach einen kleinen Cube an die gewünschte Position ziehen und dort die Position rauskopieren.
Eigene Modelle aus Blender & Substance Painter für Anno 1800/2205
Vorwort
Zuersteinmal Danke an Judekw der sich komplett auf sich allein gestellt die Grafikengine der 3D-Annos erschlossen hat, und an kskudlik und Cyborg für die RDM-OBJ Conversion. Ohne diese Arbeit wäre das hier gar nicht möglich.
Ich gehe jetzt davon aus, dass ich mein Modell bereits erstellt, UV Unwrapped ( beides mit Blender) habe, es in Substance Painter texturieren möchte, und es dann nach Anno bringen will. Zum Modellieren, UV-Unwrappen und Texturieren gibt es auf Youtube bessere Tutorials, als ich das geben könnte. Außerdem setzt diese Anleitung ein höheres Level an Modding-Erfahrung mit Anno 1800 voraus, wie man ein Gebäude in der .cfg richtig zusammensetzt, in der .ifo Blocker schreibt oder wie man aus einer Grafik ein baubares Gebäude macht sollten bereits bekannt sein. Darauf wird hier nicht weiter eingegangen, es geht nur darum, wie man sein Gebäude aus Blender und Substance Painter in Anno reinbekommt.
Benötigte Programme
Ich benutze für die Erstellung des Modells und das UV Unwrapping Blender, für die Texturierung Substance Painter und für die rdm conversion den rdm-obj converter von kskudlik: https://github.com/kskudlik/Anno-1800-Model-Converter/releases
Grundlagen
Ein Modell in Anno ist folgendermaßen aufgebaut:
- Ordner "maps" (von jeder Map gibt es drei Variationen, diff_0 ist doppelt so groß wie diff_1, diff_1 ist doppelt so groß wie diff_2.
- diffuse maps - Farbinformationen in RGB, Transparenzinfo in Alpha.
- normal maps - Normalmap in RG, Glowmap in B, Glossiness in Alpha.
- metallic maps - RGB Metallic, einfärben möglich. Klassisch weiß bedeutet, dass die Farbinformation der diffuse übernommen wird. Alphakanal speichert eine Ambient Occlusion Map.
- mask maps - RGB, Leuchtende Stellen bei Nacht. Weiß bedeutet, dass dieser Teil bei Nacht beleuchtet wird.
- dye maps - RGB, Weiß bedeutet, dass dieser Teil mit der Spielerfarbe eingefärbt wird (Achtung: diffuse und dye werden vermittelt!)
- height maps: Graustufen, damit lässt sich Höhenunterschied ganz gut faken. Eignet sich daher gut für Bodentexturen.
- Ordner "rdm" (hierbei steht das lod_0 für das lodlevel der Grafik (0-4, je nach Distanz, sind weniger vorhanden wird automatisch das letzte bis zum Ende genommen, man kann also auch nur eine lod0 ablegen)
- Alle benötigten Modelle im .rdm Format
- Ordner "anim" (Für uns nicht benötigt, da kein bisher geschriebener Konverter diese unterstützt)
- Alle benötigten Animationen im .rdm Format
- <model>.fc (Zuständig für die Loops des visuellen Feedbacks. Eine Art xml-Dokument, die allerdings einige Teile in Binary hat, die entweder im Hexeditor bearbeitet oder erst konvertiert werden müssen, siehe Maug-Forum ).
- <model>.cfg (Setzt Modelle zusammen, eine Art xml-Dokument, kann mit einem xml-Formatter wesentlich einfacher lesbarer gemacht werden.)
- <model>.ifo (Legt Blocking der Grafik fest, eine Art xml-Dokument, kann ebenso mit xml-Formatter lesbarer gemacht werden.)
- <model>.bfg (Global Illumination, der Unterschied, wenn diese Datei fehlt, ist marginal, daher braucht man diese nicht. Man könnte sie auch eh nicht selbst schreiben.)
Aufsetzen eines Projektes mit Substance
In Substance Painter lassen sich die Eigenschaften der Anno-Engine relativ einfach reproduzieren. Euer Projekt benötigt folgende Kanäle:
- Base Color
- Metallic
- Normal
- Glossiness
- Opacity
- Reflection (Weiß in diesem Kanal bedeutet, dass dieser Teil der Textur bei Nacht leuchtet.)
Mithilfe von Templates können in Substance Painter Projekte direkt mit diesen Kanälen erstellt werden. Dafür kann beim Erstellen eines Projektes die entsprechende Template ausgewählt werden:
Anno 1800-spezifischer Shader
Anno 1800 ist ein wenig besonders in der Hinsicht, dass es PBR mit Metal/Gloss anstatt Metal/Rough umsetzt. Der Unterschied ist nicht allzu groß, es gibt in Substance Painter aber keinen Shader, der das von Haus aus unterstützt. Insofern hier eine kleine Bearbeitung des Pbr-metal-rough-with-alpha-blending Shaders, die statt Roughness eben Glossiness benutzt. Das sollte ausreichend sein, um nicht jedes Mal erst das Spiel zu starten, um zu schauen, ob die Texturen, die in Substance perfekt wirken, denn in 1800 auch gut aussehen. Der Unterschied ist hauptsächlich das Lighting, die Materials sind aber im wesentlichen identisch.
Hier ein Vergleich:
Den Shader könnt ihr rechts einstellen, ein mit der Template aufgesetztes Projekt wird diesen automatisch verwenden.
Export der Texturen aus Substance
Substance ermöglicht nicht nur das Aufsetzen von Projekten mit den Eigenschaften wie Anno 1800, sondern auch das exportieren. Anno 1800 fasst dabei mehrere Informationen in eine Textur zusammen, im Alphakanal der Diffuse Textur steckt bspw. die Opacity Map. Für Anno 1800 sieht das ganze dann so aus:
So und jetzt zum exportieren hier die Template einstellen:
Mein Projekt ist ein ganz normales Gebäude, was keine Heightmap benötigt, daher ist dies im Export abgestellt.
Jetzt exportieren und mit der Template Medium und halber Auflösung sowie der Template Low in Viertel-Auflösung wiederholen.
Downloads
Project-Template, Export-Templates und Shader können hier heruntergeladen werden: Google Drive
Alle drei Ordner müssen unter Documents/Allegorithmic/Substance Painter/shelf abgelegt werden und suchen sich ihren Ort dann selbst.
Der letzte Schritt: Texturen nach dds konvertieren
Die Texturen müssen für Anno in .dds, BC7_UNORM Format vorliegen. Substance Painter unterstützt jedoch kein dds, weshalb man die Texturen noch konvertieren muss. Dazu kann man sie zwar einfach alle in GIMP öffnen und nach dds exportieren, am besten mit automatisiertem Texture Tool, nach dieser Anleitung.
Schneller geht manuelles Konvertieren mit texconv.exe mit folgenden Argumenten, dann wird direkt der ganze Ordner konvertiert.
-f BC7_UNORM -ft DDS -sepalpha *.png
Damit hat man dann alles was man an Texturen benötigt.
Tipps zum schreiben der cfg und ifo
Damit sind die Maps erstellt, bleiben die .cfg und die .ifo. Diese müssen vielleicht nicht von Grund auf neu erstellt werden, aber Partikel und Props, etc. müssen von Hand hinzugefügt werden.
Dabei besonders hilfreich ist, dass man für ORIENTATION_TRANSFORMS auch einfach Blender benutzen kann um bspw Props und Particles dort zu positionieren wo man das möchte. Beachtet dabei allerdings bitte, dass Blender ein Z-Up Koordinatensystem benutzt, während Anno Y-Up arbeitet. Daher müssen sämtliche y- und z- Positionswerte immer vertauscht werden beim copy+pasten.
Rotationen lassen sich sogar aus Blender direkt ablesen (Anno speichert diese im Quaternion-Format, dieses kann man in Blender auch zur Anzeige auswählen. Beachtet dabei aber, dass Y und Z getauscht werden müssen.), oder aber mit der Seite quaternions.online easy aus Euler errechnen lassen.
Für Partikel oder ähnliches lässt sich das genauso machen, einfach einen kleinen Cube an die gewünschte Position ziehen und dort die Position rauskopieren.
Die Asset-Bibliothek - Einleitung
Stell Dir dein Spiel am besten so vor (sehr vereinfacht):
Assets + Grafiken → Binary (Anno1800.exe) → Anzeige auf dem Bildschirm
An der Binary können wir nichts verändern. Was wir allerdings tun können, ist der Binary veränderte oder neue Assets und Grafiken zu füttern, die das Spiel interpretieren kann.
Das bedeutet, wir müssen uns an dessen Regeln halten – Nur wenn man Code schreibt, den Anno versteht, wird es auch tun, was man will.
(Vielleicht ein kleines Update) Was sollte man können, um loszulegen?
Zuerst, nein, man muss nicht unbedingt programmieren können, obwohl das immer ein Pluspunkt ist.
Wenn Du nicht programmieren kannst, solltest du dich aber auf jeden Fall mit den Konzepten Datentyp, Variable, Arrays, Vererbung und (generischen) Dictionaries und Vectors vertraut machen.
Code musst du damit nicht schreiben, aber diese Konzepte zu verstehen ist eine große Hilfe beim Modding.
Die meisten Dateien in Anno benutzen die XML-Scriptsprache. Dessen Syntax werde ich nicht erklären, und ein Basisverständnis wird vorausgesetzt.
Wenn Du noch nie XML gesehen hast, solltest Du dich zuerst dort etwas einarbeiten. Du solltest ebenfalls ein bisschen Xpath können, da dies für den Modloader gebraucht wird.
Die Assets-Bibliothek verstehen
Zuerst, entschuldigt bitte den trockenen Start, es wird leider ein wenig theoretisch, aber für die interessanten Teile wird es später sehr wertvoll sein, besonders,
wenn Du neue Funktionen in das Spiel einbauen möchtest.
Für die Assets-Bibliothek verwendet Anno ein paar Konzepte:
- Assets sind Objekte im Spiel – Ein Gebäude, ein Schiff, oder das Handwerker-Baumenü
- Templates sind Blaupausen für Assets, die Standardwerte für jedes Asset mit dieser Template festlegen. Außerdem gibt die Template an, welche Properties geladen werden sollen.
- Properties sind Container, die direkt in einem Asset enthalten sind, Properties können Container enthalten, aber nicht umgekehrt!
- Container sind Sammlungen an Values, Vectors oder anderen Containern.
- Vectors sind Listen aus Variablen oder Containern
- Dictionaries sind wie Vectors, nur dass jedes Element einen einzigartigen Namen bekommt, die vom Spiel vordefiniert sind. Deswegen ist die Größe eines Dictionaries limitiert auf die Anzahl vordefinierter Hardcodes.
- Values sind simple Variablen oder Arrays
Im folgenden werden Values als Variablen bezeichnet, um Verwirrung zwischen Variablenname und dem Wert zu vermeiden.
Properties
Die Properties.xml ist wahrscheinlich am schwersten zu verstehen, aber sobald man diese versteht, wird man wenige Probleme haben, sich in jeder Template und jedem Asset zurechtzufinden.
Die Datei besteht aus zwei Teilen: DefaultContainerValues und DefaultValues. Für bessere Übersicht in den Entwicklertools sind sie gruppiert, aber generell könnte man es auf die beiden runterbrechen.
DefaultContainerValues gibt an, welche Container eine Property besitzt und definiert Standardwerte für Variablen in diesen Containern. Im Fall, dass der Container einen Vector oder ein Dictionary enthält,
ist der Datentyp des Inhalts angegeben.
DefaultValues definiert Standardwerte für Variablen, die direkt in der Property selbst enthalten sind, und erbt die Container aus DefaultContainerValues.
Die Standardwerte der Variablen können geändert werden, aber wir können dem Spiel nicht sagen, dass es andere Container oder Variablen laden soll. Die Properties ist ein direkter Export, was das Spiel von uns erwartet.Wenn Du Informationen brauchst, was man in einer Property so alles eintragen kann, solltest Du hier als erstes gucken. Das folgende Beispiel zeigt die Property „IrrigationConfig“
In seltenen Fällen ist dieser Export nicht komplett, in diesem Fall ist die Information aber auf jeden Fall in der assets.xml zu finden.
<Properties>
<DefaultContainerValues>
<Building>
<IrrigationConfig>
<IrrigationSpreading>
<!-- Variable in Container in Property: Standardwert für Variable am selben Ort definiert -->
<IrrigationSpreadRule>MaxAbsDist</IrrigationSpreadRule>
</IrrigationSpreading>
<!-- IrrigationIslandSeeds ist ein Container, der einen Vector enthält -->
<IrrigationIslandSeeds>
<!-- Island ist der ContentType dieses Vectors-->
<Island>0</Island>
</IrrigationIslandSeeds>
</IrrigationConfig>
</Building>
</DefaultContainerValues>
<DefaultValues>
<IrrigationConfig>
<!-- Container werden vererbt -->
<IrrigationSpreading />
<IrrigationIslandSeeds />
<!-- Variable direkt in der Property: Standardwert für Variable wird in DefaultValues definiert-->
<StaticIrrigationRandomizerFrequency>2</StaticIrrigationRandomizerFrequency>
</IrrigationConfig>
</DefaultValues>
</Properties>Code ausklappen
Im Fall, dass Container A einen anderen Container B enthält, wird B in „ContainerValues“ definiert und dann an A vererbt. Wenn B Variablen enthält, werden diese an derselben Stelle definiert.
Das folgende Beispiel zeigt einen anderen Teil der „IrrigationConfig“ Property
<Properties>
<DefaultContainerValues>
<IrrigationConfig>
<VisualsAndEffects>
<!-- Vegetation-Container wird definiert-->
<ContainerValues>
<Vegetation>
<!-- Variablen in Container in Container in Property: Standartwerte für Variablen werden am selben Ort definiert -->
<FadingSpeed>0.05</FadingSpeed>
<BlurRadius>1</BlurRadius>
</Vegetation>
</ContainerValues>
<!-- Der Vegetation Container wird an VisualsAndEffects vererbt. -->
<!-- Wichtig: In unserem Fall ist Vegetation nur ein einfacher Container -->
<!-- es könnte aber auch der ContentType eines Vectors oder Dictionaries sein. -->
<Vegetation />
</VisualsAndEffects>
</IrrigationConfig>
</DefaultContainerValues>
<DefaultValues>
<IrrigationConfig />
</DefaultValues>
</Properties>Code ausklappen
Die volle Definition der Property „IrrigationConfig“ sieht dann ungefähr so aus:
<Properties>
<DefaultContainerValues>
<Building>
<IrrigationConfig>
<IrrigationSpreading>
<IrrigationSpreadRule>MaxAbsDist</IrrigationSpreadRule>
</IrrigationSpreading>
<IrrigationIslandSeeds>
<Island>0</Island>
</IrrigationIslandSeeds>
<VisualsAndEffects>
<ContainerValues>
<Vegetation>
<FadingSpeed>0.05</FadingSpeed>
<BlurRadius>1</BlurRadius>
</Vegetation>
</ContainerValues>
<Vegetation />
</VisualsAndEffects>
</IrrigationConfig>
</Building>
</DefaultContainerValues>
<DefaultValues>
<IrrigationConfig>
<IrrigationSpreading />
<IrrigationIslandSeeds />
<VisualsAndEffects />
<StaticIrrigationRandomizerFrequency>2</StaticIrrigationRandomizerFrequency>
</IrrigationConfig>
</DefaultValues>
</Properties>Code ausklappen
Datentypen
Die folgenden Datentypen werden für Variablen benutzt:
- int → meist signed 16 oder 32 bit integers
- float → amerikanische schreibart 0.1305
- boolean → notiert als 0 oder 1
- String → ohne Anführungszeichen
- Hardcode → benutzt wie String, nur vorgegebene Namen werden akzeptiert.
Der Datentyp jeder Variable kann leider nur mit Datamining und Probieren herausgefunden werden. Es gibt leider keine Liste davon. Vorherige Spiele wie 2205 hatten dafür eine datasets.xml, die diese Information enthielt, in 1800 fehlt sie aber, und es wäre toll, wenn das von Ubisoft veröffentlicht werden könnte.
Templates
Eine Template besteht aus einem einzigartigen Namen sowie einer Reihe an Properties die Objekte mit dieser Template haben.
Wie mit der Properties.xml auch, sind Gruppen nur für Übersicht in den Devtools zuständig.
Sehen wir uns nun die Template „UnlockableAsset“ an:
<Templates>
<Template>
<Name>UnlockableAsset</Name>
<Properties>
<Standard />
<Locked />
</Properties>
</Template>
</Templates>
Diese hat die Properties „Standard“ und „Locked“.
Templates können Standardwerte für jede Variable in einer Property geben, die dann für alle Assets, die diese Template benutzen, geladen werden, und das ist auch eigentlich schon alles.
Wie können wir die Werte aber überschreiben? An dem Punkt sollten wir uns der Vererbung zuwenden.
Vererbung
Dies ist meine eigens erstellte Template „CustomIrrigationConfig“.
<Templates>
<Template>
<Name>CustomIrrigationConfig</Name>
<Properties>
<Standard />
<IrrigationConfig />
</Properties>
</Template>
</Templates>
In der Property „IrrigationConfig“ von oben, wurde StaticIrrigationRandomizerFrequency mit einem Standardwert von 2 versehen.
Wir wollen das jetzt auf 5 ändern, aber den Rest so lassen, wie er in der Property ist.
Nein, wir müssen nicht die ganze Property nochmal abschreiben, wir können einfach nur diese eine Variable abändern, und den Rest weglassen.
In der templates.xml und assets.xml werden Container automatisch vererbt, Für properties gilt das hingegen nicht.
Wir können Container auch manuell vererben, aber müssen das nicht und können sie weglassen, solange wir nichts darin ändern wollen.
<Templates>
<Template>
<Name>CustomIrrigationConfig</Name>
<Properties>
<Standard />
<IrrigationConfig>
<StaticIrrigationRandomizerFrequency>5</StaticIrrigationRandomizerFrequency>
</IrrigationConfig>
</Properties>
</Template>
</Templates>
Wir wollen jetzt noch den FadingSpeed in VisualsAndEffects ändern. Dafür vererben wir zuerst VisualsAndEffects manuell, darin dann Vegetation, wieder manuell, und darin überschreiben wir den FadingSpeed.
<Templates>
<Template>
<Name>CustomIrrigationConfig</Name>
<Properties>
<Standard />
<IrrigationConfig>
<VisualsAndEffects>
<Vegetation>
<FadingSpeed>0.05</FadingSpeed>
</Vegetation>
</VisualsAndEffects>
<StaticIrrigationRandomizerFrequency>5</StaticIrrigationRandomizerFrequency>
</IrrigationConfig>
</Properties>
</Template>
</Templates>
Für Dictionaries werden die einzigartigen Namen wie Container behandelt. Für Vectors muss man manuell vererben, was so nervig ist, dass das ganze ein ausgelagertes Tutorial ergibt. Dieses findet ihr im Maug-Forum
Assets
Okay, genug Trockenfleisch für heute, wir erstellen jetzt eine Fabrik.
Ein Asset besteht aus Template und Values. Unser Asset beginnen wir mit der Template, in unserem Fall „FactoryBuilding7“. Für die Values, schreiben wir die Properties auf, die diese Template besitzt, erstmal ohne Variablen oder Container zu ändern.
Asset>
<Template>FactoryBuilding</Template>
<Values>
<Standard />
<Building />
<Blocking />
<Cost />
<Object />
<Mesh />
<Selection />
<Text />
<Constructable />
<Locked />
<SoundEmitter />
<FeedbackController />
<Infolayer />
<UpgradeList />
<Factory7 />
<FactoryBase />
<LogisticNode />
<AmbientMoodProvider />
<Maintenance />
<Attackable />
<Pausable />
<IncidentInfectable />
<Industrializable />
<Culture />
<QuestObject />
<Electrifiable />
</Values>
</Asset>Code ausklappen
Dann machen wir die Values.
Ich habe für die Fabrik die GUID 1337733142 ausgesucht. Die wird in der „Standard“-Property als Variable angegeben.
Außerden können wir einen Namen (Name) vergeben, der aber nur für unsere Übersicht ist, das Spiel macht damit nichts.
Weitere Informationen zu GUIDs findet ihr hier.
Theoretisch haben wir damit ein valides Asset auf Basis der Template FactoryBuilding7.
Praktisch produziert es nichts aus nichts, kostet keine Arbeitskraft oder Credits, und Baumaterial braucht es auch nicht.
Also rein damit.
Wir machen eine Fabrik, die Dosenfutter aus Zink und Kartoffeln herstellt, und nur weil wir können, lassen wir in der Neuen Welt Obreros dafür arbeiten.
<Asset>
<Template>FactoryBuilding</Template>
<Values>
<Standard>
<GUID>1337733142</GUID>
<Name>Canned Food Factory 02 - Variant uses Potatoes and Zinc</Name>
</Standard>
<Building>
<!-- Gebäude für Colony01 (Südamerika) -->
<AssociatedRegions>Colony01</AssociatedRegions>
<!-- Zufällige Rotation beim bauen -->
<BuildModeRandomRotation>90</BuildModeRandomRotation>
</Building>
<Blocking>
<!-- Radius in Feldern, in dem Gras versteckt wird, wenn das Gebäude gebaut wird-->
<HidePropsRadius>0</HidePropsRadius>
<DeletePropsRadius>0</DeletePropsRadius>
</Blocking>
<Cost>
<Costs>
<Item>
<!-- Referenziert Geld -->
<Ingredient>1010017</Ingredient>
<Amount>10000</Amount>
</Item>
<Item>
<!-- Referenziert Holz -->
<Ingredient>1010196</Ingredient>
<Amount>50</Amount>
</Item>
<Item>
<!-- Referenziert Ziegel -->
<Ingredient>1010205</Ingredient>
<Amount>30</Amount>
</Item>
<Item>
<Ingredient>1010218</Ingredient>
</Item>
<Item>
<Ingredient>1010207</Ingredient>
</Item>
<Item>
<Ingredient>1010202</Ingredient>
</Item>
</Costs>
</Cost>
<Object />
<Mesh />
<Selection>
<!-- Hardcode, der den Participant (Charakter) im Gebäudemenü angibt. -->
<!-- in unserem Fall ist es die Obrera -->
<ParticipantMessageArcheType>SA_Resident_tier02_atWork</ParticipantMessageArcheType>
</Selection>
<Text />
<Constructable />
<Locked />
<SoundEmitter />
<FeedbackController />
<Infolayer />
<UpgradeList />
<Factory7 />
<FactoryBase>
<FactoryInputs>
<Item>
<!-- Product referenziert die GUID von Kartoffeln -->
<Product>1010195</Product>
<Amount>1</Amount>
<!-- Internes Kartoffel-Lager -->
<StorageAmount>6</StorageAmount>
</Item>
<Item>
<!-- Product referenziert die GUID von Zink-->
<Product>1010229</Product>
<Amount>1</Amount>
<!-- Internes Zink-Lager -->
<StorageAmount>3</StorageAmount>
</Item>
</FactoryInputs>
<FactoryOutputs>
<Item>
<!-- Product referenziert GUID von Dosenfutter-->
<Product>1010217</Product>
<Amount>1</Amount>
<!-- Internes Dosenfutter-Lager -->
<StorageAmount>4</StorageAmount>
</Item>
</FactoryOutputs>
<!-- Zeit in sekunden, in der 1t produziert wird -->
<CycleTime>45</CycleTime>
</FactoryBase>
<LogisticNode />
<AmbientMoodProvider />
<Maintenance>
<!-- Unterhaltskosten -->
<Maintenances>
<Item>
<!-- Geld -->
<Product>1010017</Product>
<Amount>40</Amount>
<InactiveAmount>20</InactiveAmount>
</Item>
<Item>
<!-- Obrero Arbeitskraft -->
<Product>1010367</Product>
<Amount>25</Amount>
</Item>
</Maintenances>
</Maintenance>
<Attackable />
<Pausable />
<IncidentInfectable />
<Industrializable>
<!-- Boost durch Strom unmöglich machen, da es in der neuen Welt keine Kraftwerke gibt. -->
<BoostedByIndustrialization>0</BoostedByIndustrialization>
</Industrializable>
<Culture />
<QuestObject />
<Electrifiable />
</Values>
</Asset>Code ausklappen
Nur eines fehlt noch – das visuelle. Anno trennt mehr oder weniger Grafik von Logik. Alles mit Grafiken wird separat gebastelt, wir geben nur den Dateipfad vom Ordner „data“ zur .cfg an (Der Hauptdatei einer Grafik, die alles zusammensetzt), bzw. zum Icon.
<Asset>
<Template>FactoryBuilding7</Template>
<Values>
<Standard>
<GUID>1337733142</GUID>
<Name>Canned Food Factory 02 - Variant uses Potatoes and Zinc</Name>
<IconFilename>data/ui/2kimages/main/3dicons/icon_canned_goulash.png</IconFilename>
</Standard>
<Building>
<AssociatedRegions>Colony01</AssociatedRegions>
<BuildModeRandomRotation>90</BuildModeRandomRotation>
</Building>
<Blocking>
<HidePropsRadius>0</HidePropsRadius>
<DeletePropsRadius>0</DeletePropsRadius>
</Blocking>
<Cost>
<Costs>
<Item>
<Ingredient>1010017</Ingredient>
<Amount>10000</Amount>
</Item>
<Item>
<Ingredient>1010196</Ingredient>
<Amount>50</Amount>
</Item>
<Item>
<Ingredient>1010205</Ingredient>
<Amount>30</Amount>
</Item>
<Item>
<Ingredient>1010218</Ingredient>
</Item>
<Item>
<Ingredient>1010207</Ingredient>
</Item>
<Item>
<Ingredient>1010202</Ingredient>
</Item>
</Costs>
</Cost>
<Object>
<Variations>
<!-- Mehrere .cfg bedeuten, dass man mit Shift+V durchschalten kann -->
<Item>
<Filename>data/graphics/buildings/production/workshop_colony01_01/workshop_colony01_01.cfg</Filename>
</Item>
</Variations>
</Object>
<Mesh />
<Selection>
<ParticipantMessageArcheType>SA_Resident_tier02_atWork</ParticipantMessageArcheType>
</Selection>
<Text />
<Constructable />
<Locked />
<SoundEmitter />
<FeedbackController />
<Infolayer />
<UpgradeList />
<Factory7 />
<FactoryBase>
<FactoryInputs>
<Item>
<Product>1010195</Product>
<Amount>1</Amount>
<StorageAmount>6</StorageAmount>
</Item>
<Item>
<Product>1010229</Product>
<Amount>1</Amount>
<StorageAmount>3</StorageAmount>
</Item>
</FactoryInputs>
<FactoryOutputs>
<Item>
<Product>1010217</Product>
<Amount>1</Amount>
<StorageAmount>4</StorageAmount>
</Item>
</FactoryOutputs>
<CycleTime>45</CycleTime>
</FactoryBase>
<LogisticNode />
<AmbientMoodProvider />
<Maintenance>
<Maintenances>
<Item>
<Product>1010017</Product>
<Amount>40</Amount>
<InactiveAmount>20</InactiveAmount>
</Item>
<Item>
<Product>1010367</Product>
<Amount>25</Amount>
</Item>
</Maintenances>
</Maintenance>
<Attackable />
<Pausable />
<IncidentInfectable />
<Industrializable>
<BoostedByIndustrialization>0</BoostedByIndustrialization>
</Industrializable>
<Culture />
<QuestObject />
<Electrifiable />
</Values>
</Asset>Code ausklappen
Schau dir die letzten beiden Beispiele nochmal genau an. Besonders achten solltest du auf Building, Maintenance, Object und FactoryBase, diese Container wirst du oft brauchen.
Jetzt haben wir auch ein voll funktionstüchtiges Asset, das Anno korrekt interpretieren und darstellen kann.
Beim nächsten Mal basteln wir unser Asset in das Baumenü, setzen dafür einen Text und schalten es frei.
Die Asset-Bibliothek - Einleitung
Stell Dir dein Spiel am besten so vor (sehr vereinfacht):
Assets + Grafiken → Binary (Anno1800.exe) → Anzeige auf dem Bildschirm
An der Binary können wir nichts verändern. Was wir allerdings tun können, ist der Binary veränderte oder neue Assets und Grafiken zu füttern, die das Spiel interpretieren kann.
Das bedeutet, wir müssen uns an dessen Regeln halten – Nur wenn man Code schreibt, den Anno versteht, wird es auch tun, was man will.
(Vielleicht ein kleines Update) Was sollte man können, um loszulegen?
Zuerst, nein, man muss nicht unbedingt programmieren können, obwohl das immer ein Pluspunkt ist.
Wenn Du nicht programmieren kannst, solltest du dich aber auf jeden Fall mit den Konzepten Datentyp, Variable, Arrays, Vererbung und (generischen) Dictionaries und Vectors vertraut machen.
Code musst du damit nicht schreiben, aber diese Konzepte zu verstehen ist eine große Hilfe beim Modding.
Die meisten Dateien in Anno benutzen die XML-Scriptsprache. Dessen Syntax werde ich nicht erklären, und ein Basisverständnis wird vorausgesetzt.
Wenn Du noch nie XML gesehen hast, solltest Du dich zuerst dort etwas einarbeiten. Du solltest ebenfalls ein bisschen Xpath können, da dies für den Modloader gebraucht wird.
Die Assets-Bibliothek verstehen
Zuerst, entschuldigt bitte den trockenen Start, es wird leider ein wenig theoretisch, aber für die interessanten Teile wird es später sehr wertvoll sein, besonders,
wenn Du neue Funktionen in das Spiel einbauen möchtest.
Für die Assets-Bibliothek verwendet Anno ein paar Konzepte:
- Assets sind Objekte im Spiel – Ein Gebäude, ein Schiff, oder das Handwerker-Baumenü
- Templates sind Blaupausen für Assets, die Standardwerte für jedes Asset mit dieser Template festlegen. Außerdem gibt die Template an, welche Properties geladen werden sollen.
- Properties sind Container, die direkt in einem Asset enthalten sind, Properties können Container enthalten, aber nicht umgekehrt!
- Container sind Sammlungen an Values, Vectors oder anderen Containern.
- Vectors sind Listen aus Variablen oder Containern
- Dictionaries sind wie Vectors, nur dass jedes Element einen einzigartigen Namen bekommt, die vom Spiel vordefiniert sind. Deswegen ist die Größe eines Dictionaries limitiert auf die Anzahl vordefinierter Hardcodes.
- Values sind simple Variablen oder Arrays
Im folgenden werden Values als Variablen bezeichnet, um Verwirrung zwischen Variablenname und dem Wert zu vermeiden.
Properties
Die Properties.xml ist wahrscheinlich am schwersten zu verstehen, aber sobald man diese versteht, wird man wenige Probleme haben, sich in jeder Template und jedem Asset zurechtzufinden.
Die Datei besteht aus zwei Teilen: DefaultContainerValues und DefaultValues. Für bessere Übersicht in den Entwicklertools sind sie gruppiert, aber generell könnte man es auf die beiden runterbrechen.
DefaultContainerValues gibt an, welche Container eine Property besitzt und definiert Standardwerte für Variablen in diesen Containern. Im Fall, dass der Container einen Vector oder ein Dictionary enthält,
ist der Datentyp des Inhalts angegeben.
DefaultValues definiert Standardwerte für Variablen, die direkt in der Property selbst enthalten sind, und erbt die Container aus DefaultContainerValues.
Die Standardwerte der Variablen können geändert werden, aber wir können dem Spiel nicht sagen, dass es andere Container oder Variablen laden soll. Die Properties ist ein direkter Export, was das Spiel von uns erwartet.Wenn Du Informationen brauchst, was man in einer Property so alles eintragen kann, solltest Du hier als erstes gucken. Das folgende Beispiel zeigt die Property „IrrigationConfig“
In seltenen Fällen ist dieser Export nicht komplett, in diesem Fall ist die Information aber auf jeden Fall in der assets.xml zu finden.
<Properties>
<DefaultContainerValues>
<Building>
<IrrigationConfig>
<IrrigationSpreading>
<!-- Variable in Container in Property: Standardwert für Variable am selben Ort definiert -->
<IrrigationSpreadRule>MaxAbsDist</IrrigationSpreadRule>
</IrrigationSpreading>
<!-- IrrigationIslandSeeds ist ein Container, der einen Vector enthält -->
<IrrigationIslandSeeds>
<!-- Island ist der ContentType dieses Vectors-->
<Island>0</Island>
</IrrigationIslandSeeds>
</IrrigationConfig>
</Building>
</DefaultContainerValues>
<DefaultValues>
<IrrigationConfig>
<!-- Container werden vererbt -->
<IrrigationSpreading />
<IrrigationIslandSeeds />
<!-- Variable direkt in der Property: Standardwert für Variable wird in DefaultValues definiert-->
<StaticIrrigationRandomizerFrequency>2</StaticIrrigationRandomizerFrequency>
</IrrigationConfig>
</DefaultValues>
</Properties>Code ausklappen
Im Fall, dass Container A einen anderen Container B enthält, wird B in „ContainerValues“ definiert und dann an A vererbt. Wenn B Variablen enthält, werden diese an derselben Stelle definiert.
Das folgende Beispiel zeigt einen anderen Teil der „IrrigationConfig“ Property
<Properties>
<DefaultContainerValues>
<IrrigationConfig>
<VisualsAndEffects>
<!-- Vegetation-Container wird definiert-->
<ContainerValues>
<Vegetation>
<!-- Variablen in Container in Container in Property: Standartwerte für Variablen werden am selben Ort definiert -->
<FadingSpeed>0.05</FadingSpeed>
<BlurRadius>1</BlurRadius>
</Vegetation>
</ContainerValues>
<!-- Der Vegetation Container wird an VisualsAndEffects vererbt. -->
<!-- Wichtig: In unserem Fall ist Vegetation nur ein einfacher Container -->
<!-- es könnte aber auch der ContentType eines Vectors oder Dictionaries sein. -->
<Vegetation />
</VisualsAndEffects>
</IrrigationConfig>
</DefaultContainerValues>
<DefaultValues>
<IrrigationConfig />
</DefaultValues>
</Properties>Code ausklappen
Die volle Definition der Property „IrrigationConfig“ sieht dann ungefähr so aus:
<Properties>
<DefaultContainerValues>
<Building>
<IrrigationConfig>
<IrrigationSpreading>
<IrrigationSpreadRule>MaxAbsDist</IrrigationSpreadRule>
</IrrigationSpreading>
<IrrigationIslandSeeds>
<Island>0</Island>
</IrrigationIslandSeeds>
<VisualsAndEffects>
<ContainerValues>
<Vegetation>
<FadingSpeed>0.05</FadingSpeed>
<BlurRadius>1</BlurRadius>
</Vegetation>
</ContainerValues>
<Vegetation />
</VisualsAndEffects>
</IrrigationConfig>
</Building>
</DefaultContainerValues>
<DefaultValues>
<IrrigationConfig>
<IrrigationSpreading />
<IrrigationIslandSeeds />
<VisualsAndEffects />
<StaticIrrigationRandomizerFrequency>2</StaticIrrigationRandomizerFrequency>
</IrrigationConfig>
</DefaultValues>
</Properties>Code ausklappen
Datentypen
Die folgenden Datentypen werden für Variablen benutzt:
- int → meist signed 16 oder 32 bit integers
- float → amerikanische schreibart 0.1305
- boolean → notiert als 0 oder 1
- String → ohne Anführungszeichen
- Hardcode → benutzt wie String, nur vorgegebene Namen werden akzeptiert.
Der Datentyp jeder Variable kann leider nur mit Datamining und Probieren herausgefunden werden. Es gibt leider keine Liste davon. Vorherige Spiele wie 2205 hatten dafür eine datasets.xml, die diese Information enthielt, in 1800 fehlt sie aber, und es wäre toll, wenn das von Ubisoft veröffentlicht werden könnte.
Templates
Eine Template besteht aus einem einzigartigen Namen sowie einer Reihe an Properties die Objekte mit dieser Template haben.
Wie mit der Properties.xml auch, sind Gruppen nur für Übersicht in den Devtools zuständig.
Sehen wir uns nun die Template „UnlockableAsset“ an:
<Templates>
<Template>
<Name>UnlockableAsset</Name>
<Properties>
<Standard />
<Locked />
</Properties>
</Template>
</Templates>
Diese hat die Properties „Standard“ und „Locked“.
Templates können Standardwerte für jede Variable in einer Property geben, die dann für alle Assets, die diese Template benutzen, geladen werden, und das ist auch eigentlich schon alles.
Wie können wir die Werte aber überschreiben? An dem Punkt sollten wir uns der Vererbung zuwenden.
Vererbung
Dies ist meine eigens erstellte Template „CustomIrrigationConfig“.
<Templates>
<Template>
<Name>CustomIrrigationConfig</Name>
<Properties>
<Standard />
<IrrigationConfig />
</Properties>
</Template>
</Templates>
In der Property „IrrigationConfig“ von oben, wurde StaticIrrigationRandomizerFrequency mit einem Standardwert von 2 versehen.
Wir wollen das jetzt auf 5 ändern, aber den Rest so lassen, wie er in der Property ist.
Nein, wir müssen nicht die ganze Property nochmal abschreiben, wir können einfach nur diese eine Variable abändern, und den Rest weglassen.
In der templates.xml und assets.xml werden Container automatisch vererbt, Für properties gilt das hingegen nicht.
Wir können Container auch manuell vererben, aber müssen das nicht und können sie weglassen, solange wir nichts darin ändern wollen.
<Templates>
<Template>
<Name>CustomIrrigationConfig</Name>
<Properties>
<Standard />
<IrrigationConfig>
<StaticIrrigationRandomizerFrequency>5</StaticIrrigationRandomizerFrequency>
</IrrigationConfig>
</Properties>
</Template>
</Templates>
Wir wollen jetzt noch den FadingSpeed in VisualsAndEffects ändern. Dafür vererben wir zuerst VisualsAndEffects manuell, darin dann Vegetation, wieder manuell, und darin überschreiben wir den FadingSpeed.
<Templates>
<Template>
<Name>CustomIrrigationConfig</Name>
<Properties>
<Standard />
<IrrigationConfig>
<VisualsAndEffects>
<Vegetation>
<FadingSpeed>0.05</FadingSpeed>
</Vegetation>
</VisualsAndEffects>
<StaticIrrigationRandomizerFrequency>5</StaticIrrigationRandomizerFrequency>
</IrrigationConfig>
</Properties>
</Template>
</Templates>
Für Dictionaries werden die einzigartigen Namen wie Container behandelt. Für Vectors muss man manuell vererben, was so nervig ist, dass das ganze ein ausgelagertes Tutorial ergibt. Dieses findet ihr im Maug-Forum
Assets
Okay, genug Trockenfleisch für heute, wir erstellen jetzt eine Fabrik.
Ein Asset besteht aus Template und Values. Unser Asset beginnen wir mit der Template, in unserem Fall „FactoryBuilding7“. Für die Values, schreiben wir die Properties auf, die diese Template besitzt, erstmal ohne Variablen oder Container zu ändern.
Asset>
<Template>FactoryBuilding</Template>
<Values>
<Standard />
<Building />
<Blocking />
<Cost />
<Object />
<Mesh />
<Selection />
<Text />
<Constructable />
<Locked />
<SoundEmitter />
<FeedbackController />
<Infolayer />
<UpgradeList />
<Factory7 />
<FactoryBase />
<LogisticNode />
<AmbientMoodProvider />
<Maintenance />
<Attackable />
<Pausable />
<IncidentInfectable />
<Industrializable />
<Culture />
<QuestObject />
<Electrifiable />
</Values>
</Asset>Code ausklappen
Dann machen wir die Values.
Ich habe für die Fabrik die GUID 1337733142 ausgesucht. Die wird in der „Standard“-Property als Variable angegeben.
Außerden können wir einen Namen (Name) vergeben, der aber nur für unsere Übersicht ist, das Spiel macht damit nichts.
Weitere Informationen zu GUIDs findet ihr hier.
Theoretisch haben wir damit ein valides Asset auf Basis der Template FactoryBuilding7.
Praktisch produziert es nichts aus nichts, kostet keine Arbeitskraft oder Credits, und Baumaterial braucht es auch nicht.
Also rein damit.
Wir machen eine Fabrik, die Dosenfutter aus Zink und Kartoffeln herstellt, und nur weil wir können, lassen wir in der Neuen Welt Obreros dafür arbeiten.
<Asset>
<Template>FactoryBuilding</Template>
<Values>
<Standard>
<GUID>1337733142</GUID>
<Name>Canned Food Factory 02 - Variant uses Potatoes and Zinc</Name>
</Standard>
<Building>
<!-- Gebäude für Colony01 (Südamerika) -->
<AssociatedRegions>Colony01</AssociatedRegions>
<!-- Zufällige Rotation beim bauen -->
<BuildModeRandomRotation>90</BuildModeRandomRotation>
</Building>
<Blocking>
<!-- Radius in Feldern, in dem Gras versteckt wird, wenn das Gebäude gebaut wird-->
<HidePropsRadius>0</HidePropsRadius>
<DeletePropsRadius>0</DeletePropsRadius>
</Blocking>
<Cost>
<Costs>
<Item>
<!-- Referenziert Geld -->
<Ingredient>1010017</Ingredient>
<Amount>10000</Amount>
</Item>
<Item>
<!-- Referenziert Holz -->
<Ingredient>1010196</Ingredient>
<Amount>50</Amount>
</Item>
<Item>
<!-- Referenziert Ziegel -->
<Ingredient>1010205</Ingredient>
<Amount>30</Amount>
</Item>
<Item>
<Ingredient>1010218</Ingredient>
</Item>
<Item>
<Ingredient>1010207</Ingredient>
</Item>
<Item>
<Ingredient>1010202</Ingredient>
</Item>
</Costs>
</Cost>
<Object />
<Mesh />
<Selection>
<!-- Hardcode, der den Participant (Charakter) im Gebäudemenü angibt. -->
<!-- in unserem Fall ist es die Obrera -->
<ParticipantMessageArcheType>SA_Resident_tier02_atWork</ParticipantMessageArcheType>
</Selection>
<Text />
<Constructable />
<Locked />
<SoundEmitter />
<FeedbackController />
<Infolayer />
<UpgradeList />
<Factory7 />
<FactoryBase>
<FactoryInputs>
<Item>
<!-- Product referenziert die GUID von Kartoffeln -->
<Product>1010195</Product>
<Amount>1</Amount>
<!-- Internes Kartoffel-Lager -->
<StorageAmount>6</StorageAmount>
</Item>
<Item>
<!-- Product referenziert die GUID von Zink-->
<Product>1010229</Product>
<Amount>1</Amount>
<!-- Internes Zink-Lager -->
<StorageAmount>3</StorageAmount>
</Item>
</FactoryInputs>
<FactoryOutputs>
<Item>
<!-- Product referenziert GUID von Dosenfutter-->
<Product>1010217</Product>
<Amount>1</Amount>
<!-- Internes Dosenfutter-Lager -->
<StorageAmount>4</StorageAmount>
</Item>
</FactoryOutputs>
<!-- Zeit in sekunden, in der 1t produziert wird -->
<CycleTime>45</CycleTime>
</FactoryBase>
<LogisticNode />
<AmbientMoodProvider />
<Maintenance>
<!-- Unterhaltskosten -->
<Maintenances>
<Item>
<!-- Geld -->
<Product>1010017</Product>
<Amount>40</Amount>
<InactiveAmount>20</InactiveAmount>
</Item>
<Item>
<!-- Obrero Arbeitskraft -->
<Product>1010367</Product>
<Amount>25</Amount>
</Item>
</Maintenances>
</Maintenance>
<Attackable />
<Pausable />
<IncidentInfectable />
<Industrializable>
<!-- Boost durch Strom unmöglich machen, da es in der neuen Welt keine Kraftwerke gibt. -->
<BoostedByIndustrialization>0</BoostedByIndustrialization>
</Industrializable>
<Culture />
<QuestObject />
<Electrifiable />
</Values>
</Asset>Code ausklappen
Nur eines fehlt noch – das visuelle. Anno trennt mehr oder weniger Grafik von Logik. Alles mit Grafiken wird separat gebastelt, wir geben nur den Dateipfad vom Ordner „data“ zur .cfg an (Der Hauptdatei einer Grafik, die alles zusammensetzt), bzw. zum Icon.
<Asset>
<Template>FactoryBuilding7</Template>
<Values>
<Standard>
<GUID>1337733142</GUID>
<Name>Canned Food Factory 02 - Variant uses Potatoes and Zinc</Name>
<IconFilename>data/ui/2kimages/main/3dicons/icon_canned_goulash.png</IconFilename>
</Standard>
<Building>
<AssociatedRegions>Colony01</AssociatedRegions>
<BuildModeRandomRotation>90</BuildModeRandomRotation>
</Building>
<Blocking>
<HidePropsRadius>0</HidePropsRadius>
<DeletePropsRadius>0</DeletePropsRadius>
</Blocking>
<Cost>
<Costs>
<Item>
<Ingredient>1010017</Ingredient>
<Amount>10000</Amount>
</Item>
<Item>
<Ingredient>1010196</Ingredient>
<Amount>50</Amount>
</Item>
<Item>
<Ingredient>1010205</Ingredient>
<Amount>30</Amount>
</Item>
<Item>
<Ingredient>1010218</Ingredient>
</Item>
<Item>
<Ingredient>1010207</Ingredient>
</Item>
<Item>
<Ingredient>1010202</Ingredient>
</Item>
</Costs>
</Cost>
<Object>
<Variations>
<!-- Mehrere .cfg bedeuten, dass man mit Shift+V durchschalten kann -->
<Item>
<Filename>data/graphics/buildings/production/workshop_colony01_01/workshop_colony01_01.cfg</Filename>
</Item>
</Variations>
</Object>
<Mesh />
<Selection>
<ParticipantMessageArcheType>SA_Resident_tier02_atWork</ParticipantMessageArcheType>
</Selection>
<Text />
<Constructable />
<Locked />
<SoundEmitter />
<FeedbackController />
<Infolayer />
<UpgradeList />
<Factory7 />
<FactoryBase>
<FactoryInputs>
<Item>
<Product>1010195</Product>
<Amount>1</Amount>
<StorageAmount>6</StorageAmount>
</Item>
<Item>
<Product>1010229</Product>
<Amount>1</Amount>
<StorageAmount>3</StorageAmount>
</Item>
</FactoryInputs>
<FactoryOutputs>
<Item>
<Product>1010217</Product>
<Amount>1</Amount>
<StorageAmount>4</StorageAmount>
</Item>
</FactoryOutputs>
<CycleTime>45</CycleTime>
</FactoryBase>
<LogisticNode />
<AmbientMoodProvider />
<Maintenance>
<Maintenances>
<Item>
<Product>1010017</Product>
<Amount>40</Amount>
<InactiveAmount>20</InactiveAmount>
</Item>
<Item>
<Product>1010367</Product>
<Amount>25</Amount>
</Item>
</Maintenances>
</Maintenance>
<Attackable />
<Pausable />
<IncidentInfectable />
<Industrializable>
<BoostedByIndustrialization>0</BoostedByIndustrialization>
</Industrializable>
<Culture />
<QuestObject />
<Electrifiable />
</Values>
</Asset>Code ausklappen
Schau dir die letzten beiden Beispiele nochmal genau an. Besonders achten solltest du auf Building, Maintenance, Object und FactoryBase, diese Container wirst du oft brauchen.
Jetzt haben wir auch ein voll funktionstüchtiges Asset, das Anno korrekt interpretieren und darstellen kann.
Beim nächsten Mal basteln wir unser Asset in das Baumenü, setzen dafür einen Text und schalten es frei.