Tutorial #02: Atlas Demo

Um Grafiken unterschiedlicher Größe in ein textur-taugliches Format zu konvertieren, steht der atlas_generator zur Verfügung.

Der atlas_generator platziert alle vorgegebenen Grafiken in ein Texturbild und erzeugt die entsprechenden Graphen-Informationen.

Für unser Beispiel haben wir einige Grafiken von https://opengameart.org geladen. Die Herkunftsadressen der einzelnen Bilder befinden sich in der Datei data/orig/AUTHOR.txt.

  • data/orig/back_1.png
  • data/orig/nightraiderfixed.png
  • data/orig/warrior1_0.png
  • data/orig/warrior2_0.png
  • data/orig/sz/2k_anim1.png
  • data/orig/sz/2k_anim2.png
  • data/orig/sz/2k_anim3.png
  • data/orig/sz/2k_anim4.png

Wir haben ein Hintergrundbild, drei Raumschiffe und ein Verzeichnis mit vier Einzelbildern einer Alien-Animation.

Für die Alien-Animation soll eine Textur und die dazugehörigen <Rectange> Ressourcen erzeugt werden. Für den Hintergrund und die Raumschiffe soll eine Textur und die dazugehörigen <PlaneGeometry> Knoten erstellt werden.

Der atlas_generator liest die Konfiguration aus XML Dateien.

  • scripts/atlas_config_alien.xml
  • scripts/atlas_config_misc.xml

Für jede der beiden Aufgaben gibt es eine entsprechende Konfigurationsdatei.

<?xml version="1.0"?>
<!-- Copyright 2013 Spraylight GmbH -->

<AtlasGenerator xmlns="https://murlengine.com">
    
    <Input path="../orig/sz">
        
        <Crop cropThreshold="8i" cropCenterX="10" cropCenterY="0"/>
        
        <Matte color="0i, 0i, 0i"/>
        
        <Image scanAll="yes"/>
        
    </Input>
    
    <Output path="../packages/main.murlres">
        
        <Atlas sizeRaster="2"/>
        
        <Image name="gfx_alien.png" margin="1"/>
        
        <AtlasXML name="atlas_alien.xml"/>
        
    </Output>
</AtlasGenerator>

Hier wird die Textur für die Alien-Animation erstellt.

Der atlas_generator benötigt immer eine <AtlasGenerator> Root-Tag Sektion mit einer oder mehreren <Input> Tag Sektionen und einer <Output> Tag Sektion.

Das <Input> Tag kann einen Pfad für alle Eingabe-Dateien mit dem Attribut path="../orig/sz" festlegen. In unserem Beispiel werden die Pfade relativ zum scripts Verzeichnis angegeben, von wo aus der atlas_generator mittels Shell-Script gestartet wird.

Das <Image scanAll="yes"/> Tag legt die Eingabe-Bild-Dateien fest. Mit dem Attribut scanAll="yes" werden alle Dateien im Pfad für die Eingabe-Dateien gelesen. Dateien mit vorangestellten Punkt oder Verzeichnisse werden ignoriert. In unserem Beispiel werden somit alle vier 2k_anim… Bilder geladen.

Das <Matte color="0i, 0i, 0i"/> Tag legt die Farbe der Pixel mit Alphawert 0 fest. Einige sehr populäre Grafikprogramme speichern in den Pixeln mit Alphawert 0 einfach zufällige RGB Farbwerte. Dies führt bei der Interpolation von Pixeln (wie sie beispielsweise der 3D-Grafik Chip verwendet) zu falschen Farben an den Rändern solcher Bereiche. Um diesen Umstand zu beheben, werden mit dieser Anweisung vorsichtshalber alle Pixel mit Alpha 0 mit den RGB-Werten des color="0i, 0i, 0i" Attributs ersetzt.

Das <Crop cropThreshold="8i" cropCenterX="10" cropCenterY="0"/>Tag ermöglicht das Zuschneiden von großzügig platzierten Grafiken mit leeren Rändern. Dies kann den Platzbedarf auf der Textur erheblich verringern.

Unsere Alien Grafik ist ein besonders heimtückisches Beispiel. Hier sind nahezu "unsichtbare" Alpha Verläufe (von Alpha 0 – 15) an den Rändern der Figur gespeichert. Zudem sitzt die Figur als Ganzes in der Mitte eines viel zu großen Bildes.

Die Ränder mit einem Alphawert kleiner oder gleich 8 werden mittels cropThreshold="8i" Attribut entfernt.

Da es sich um eine Bildsequenz handelt, ist es besonders wichtig, dass die Bilder deckungsgleich sind und bleiben. Dies wird mit dem cropCenter="0" Attribut erreicht, welches sichergestellt, dass an allen vier Rändern immer gleich viel abgeschnitten wird. Da das Objekt sich nicht in der Bildmitte befindet, wird das Zentrum mit den Attributen cropCenterX="10" und cropCenterY="0" zusätzlich noch relativ verschoben.

Das <Output> Tag kann einen Pfad für alle Ausgabe-Dateien mit dem Attribut path="../packages/main.murlres" festlegen. In unserem Beispiel werden die Pfade relativ zum scripts Verzeichnis angegeben, von wo aus der atlas_generator mittels Shell-Script gestartet wird.

Das <Atlas sizeRaster="2"/> Tag legt die Eigenschaften der zu erzeugenden Textur fest. Das Attribut sizeRaster="2" definiert den Größenraster der einzulegenden Bilder. In unserem Fall sind alle Dimensionen der einzelnen Bilder durch 2 teilbar. Die einzelnen Bilder werden bei diesem Vorgang nicht modifiziert, sondern lediglich in den Raster zentriert eingelegt. Dies hat den Vorteil, dass alle Bilder immer auf ganzzahligen Pixeln am Bildschirm platziert werden können.

Wenn ein Bild mit einer Breite von z.B. 17 Pixeln einfach auf Position (0/0) am Bildschirm platziert wird, dann wird das Bild verwaschen dargestellt, weil der Grafik-Chip links und rechts von der Koordinate 8,5 Pixel interpoliert zeichnet. Dies lässt sich zwar umgehen in dem man die Koordinate auf z.B. (0.5/0) verschiebt, was jedoch bei vielen unterschiedlichen Grafiken sehr schnell unübersichtlich werden kann.

Wesentlich einfacher hingegen ist die Verwendung des Rasters, da man sich keine Gedanken über die Abmessungen der Grafiken machen muss, solange diese immer auf ganzzahligen Koordinaten angezeigt werden.

Natürlich werden bewegte Grafiken grundsätzlich auf gerechneten nicht-ganzzahligen Koordinaten dargestellt. In diesem Fall ist das Verwaschen auch nicht vermeidbar, es ist jedoch nicht sonderlich störend. Statische Bilder hingegen, z.B. in Menüs und dergleichen, hinterlassen sehr wohl einen verwaschenen Eindruck.

Der Begriff ganzzahlige Koordinate bezieht sich in unserem Beispiel auf den Umstand, dass die Kamera für eine 1:1 Abbildung des virtuellen Koordinatensystems zur Bildschirmausgabe eingerichtet ist.

Das <Image name="gfx_alien.png" margin="1"/> Tag gibt den Namen der Textur-Bilddatei an. Zusätzlich wird hier noch ein Rand von 1 Pixel für alle Eingangsbilder mit dem Attribut margin="1" eingestellt. Ein zusätzlicher Rand um die Bilder ist notwendig, damit bei der Interpolation von Pixeln nicht die Pixel der benachbarten Grafiken herangezogen werden. Der Randbereich wird mit der Farbe des Matte-Attributes des jeweiligen Eingangsbildes gefüllt- wenn dieses angegeben ist. Andernfalls bleibt der Rand unberührt.

Das <AtlasXML name="atlas_alien.xml"/> Tag gibt den Namen der XML Datei an, in welche die Texturkoordinaten in Form einer Atlas Ressource geschrieben werden.

<?xml version="1.0"?>
<!-- Copyright 2013 Spraylight GmbH -->

<AtlasGenerator xmlns="https://murlengine.com">
    
    <Input path="../orig">
        
        <Matte color="0i, 0i, 0i"/>
        
        <Image name="back_1.png" sizeX="800" sizeY="640"/>
        <Image name="nightraiderfixed.png"/>
        <Image name="warrior1_0.png"/>
        <Image name="warrior2_0.png"/>
        
    </Input>
    
    <Output path="../packages/main.murlres">
        
        <Atlas sizeRaster="2"/>
        
        <Image name="gfx_misc.png" margin="1"/>
        
        <PlaneGraphXML name="planes_misc.xml"/>
        
    </Output>
    
</AtlasGenerator>

Hier wird die Textur für den Hintergrund und die Raumschiff-Bilder erstellt. Es werden alle Eingabe-Bilddateien mit einem <Image name="..."/> Tag explizit angegeben.

Das <Image name="back_1.png" sizeX="800" sizeY="640"/> Tag gibt zusätzlich die gewünschte Pixelgröße der Bilddatei in der Ausgabe Textur an.

In unserem Beispiel hat der Bildschirmbereich eine Größe von 800x600 640? Pixel. Das Hintergrundbild hat aber größere Abmessungen und wird dementsprechend skaliert. Die Höhe von 640 ist beabsichtigt, um das Seitenverhältnis des Bildes beizubehalten, da oben und unten 20 Pixel weniger angezeigt werden. Grundsätzlich empfiehlt es sich nur für schnell erstellte Prototypen den atlas_generator zum Skalieren von Bildern heranzuziehen. Um eine bestmögliche Qualität der Grafiken zu gewährleisten, sollten Bilder für die jeweilige Anwendung immer von Grund auf richtig vorbereitet werden.

Das <PlaneGraphXML name="planes_misc.xml"/> Tag gibt den Namen der XML Datei an, in welche die Texturkoordinaten in Form einer Graphen-Ressource geschrieben wird.

Eine vollständige Beschreibung aller Tags und Attribute befindet sich in der Tools Dokumentation.

Folgende Skript-Dateien (Unix/Windows) beinhalten die Aufrufe, welche den atlas_generator anweisen, die entsprechenden Texturen und Metadaten zu erstellen.

  • scripts/atlas_generator.sh
  • scripts/atlas_generator.cmd

Beim Aufruf der entsprechenden Skript-Datei werden folgende Dateien erzeugt:

  • data/packages/main.murlres/atlas_alien.xml
  • data/packages/main.murlres/gfx_alien.png
  • data/packages/main.murlres/gfx_misc.png
  • data/packages/main.murlres/planes_misc.xml

Die erzeugten Dateien können direkt in Graph-Knoten verwendet werden.

Szenengraph

Als erstes müssen die erzeugten Ressourcen in das Paket aufgenommen werden.

<?xml version="1.0" ?>
<!-- Copyright 2013 Spraylight GmbH -->
<Package id="package_main">
    
    <!-- Animation resources -->
    <Resource id="anim_alien"       fileName="anim_alien.xml"/>
    
    <!-- Atlas resources -->
    <Resource id="atlas_alien"      fileName="atlas_alien.xml"/>
    
    <!-- Bitmap resources -->
    <Resource id="gfx_alien"        fileName="gfx_alien.png"/>
    <Resource id="gfx_misc"         fileName="gfx_misc.png"/>
    <Resource id="planes_misc"      fileName="planes_misc.xml"/>
    
    <!-- Graph resources -->
    <Resource id="graph_main"       fileName="graph_main.xml"/>
    <Resource id="graph_materials"  fileName="graph_materials.xml"/>
    <Resource id="graph_textures"   fileName="graph_textures.xml"/>
    
    <!-- Graph instances -->
    <Instance graphResourceId="graph_materials"/>
    <Instance graphResourceId="graph_textures"/>
    <Instance graphResourceId="graph_main"/>
    
</Package>

Definition der Texturen

Zur mehrmaligen Verwendung der Texturen empfehlen sich Knoten zum Referenzieren.

<?xml version="1.0" ?>
<!-- Copyright 2013 Spraylight GmbH -->
<Graph>
    <Namespace id="textures" activeAndVisible="no">
        
        <FlatTexture id="tex_alien"
        imageResourceId="package_main:gfx_alien"
        pixelFormat="R8_G8_B8_A8"
        useMipMaps="no"/>
        
        <Instance graphResourceId="package_main:planes_misc"/>
        <FlatTexture id="tex_misc"
        imageResourceId="package_main:gfx_misc"
        pixelFormat="R8_G8_B8_A8"
        useMipMaps="no"/>
        
    </Namespace>
</Graph>

Hier ist zu beachten, dass die vom atlas_generator erzeugten <PlaneGeomentry> Knoten mittels <Instance graphResourceId="package_main:planes_misc"/> instanziert werden.

Main Graph

<?xml version="1.0" ?>
<!-- Copyright 2013 Spraylight GmbH -->
<Graph>
    <Namespace id="main">
        
        <View id="view"/>
        
        <Camera id="camera"
        viewId="view"
        fieldOfViewX="400"
        nearPlane="400" farPlane="2500"
        clearColorBuffer="yes"/>
        <CameraTransform cameraId="camera"
        posX="0" posY="0" posZ="800"/>
        <CameraState cameraId="camera"/>
        
        <FixedParameters id="screen_parameters"/>
        <ParametersState parametersId="screen_parameters"/>
        
        <Reference targetId="/materials/state_plain_tex_color"/>
        
        <TextureState textureId="/textures/tex_misc"/>
        <Reference targetId="/textures/back_1"/>
        
        <Transform id="ship_position" depthOrder="2">
            <Scale id="ship_scale">
                <Switch id="ship_switch">
                    <Reference targetId="/textures/nightraiderfixed"/>
                    <Reference targetId="/textures/warrior1_0"/>
                    <Reference targetId="/textures/warrior2_0"/>
                </Switch>
            </Scale>
        </Transform>
        
        <Timeline id="alien_timeline" startOnActivate="yes"
            startTime="0.0" endTime="3.0" numberOfLoops="-1">
            <TextureState textureId="/textures/tex_alien"/>
            <PlaneSequenceGeometry atlasResourceId="package_main:atlas_alien"
            posX="100" posY="-100" depthOrder="1"
            controller.animationResourceId="package_main:anim_alien"
            controller.animationKeys="INDEX"/>
        </Timeline>
        
    </Namespace>
</Graph>

Um die Grafiken der erzeugten Textur darzustellen, wird als erstes der dazugehörige Textur-State <TextureState textureId="/textures/tex_misc"/> gesetzt.

Anschließend wird der Hintergrund durch Referenzieren der erzeugten <PlaneGeometry> mit <Reference targetId="/textures/back_1"/> angezeigt.

Im nächsten Block befindet sich ein <Transform> Knoten zum Bewegen der Raumschiffe, ein <Scale> Knoten zum Verändern der Größe und ein <Switch> Knoten zur Auswahl eines bestimmten Raumschiffes.

Der <Switch> Knoten hat als Eigenschaft einen Index, welcher den Kinder-Knoten mit dem entsprechenden Index aktiviert (activeAndVisible="true") und alle anderen deaktiviert (activeAndVisible="false"). Wenn der Index ungültig ist, werden alle Kinder-Knoten deaktiviert. Der Voreingestellte Index ist "-1" was immer einen ungültigen Index darstellt und somit alle Kinder-Knoten deaktiviert. In unserem Beispiel ist der gültige Index-Bereich 0 – 2, welcher weiter unten programmatisch gesetzt wird.

Im letzten Block wird das Alien animiert dargestellt. Der <Timeline> Knoten ist so konfiguriert, dass die Animation automatisch startet und endlos läuft. Um die Grafiken des Aliens darzustellen, wird der dazugehörige Textur State <TextureState textureId="/textures/tex_alien"/> gesetzt. Der <PlaneSequenceGeometry atlasResourceId="package_main:atlas_alien"> Knoten verwendet die erzeugte Atlas-Ressource.

Applikation

Die Atlas Demo Applikation implementiert eine simple Animation, um die Raumschiffe zufällig zu bewegen.

Aufbau der Logik

        class AtlasDemoLogic : public Logic::BaseProcessor
        {
        public:
            AtlasDemoLogic(Logic::IFactory* factory);
            virtual ~AtlasDemoLogic();
            
        protected:
            virtual Bool OnInit(const Logic::IState* state);
            virtual Bool OnDeInit(const Logic::IState* state);
            virtual void OnProcessTick(const Logic::IState* state);
            
            Logic::TransformNode mShipPosition;
            Logic::ScaleNode mShipScale;
            Logic::SwitchNode mShipSwitch;
            
            Util::TT800 mRng;
            Logic::AnimationVector mShipAnimation;
        };

Zum Erzeugen von Zufallszahlen wird ein RandomNumberGenerator (RNG) instanziert. In unserem Beispiel ist das die Util::TT800 Klasse. Weitere RNG Klassen befinden sich im Header murl_util_rng.h.

Um eine Animation programmatisch zu steuern, reichen die Animation Ressourcen, wie sie z.B. für die Alien-Animation verwendet werden, nicht aus, da diese zur Programmausführungszeit nicht mehr veränderbar sind. Zu diesem Zweck gibt es für die Logik-Implementierung Animationsklassen, welche während der Programmausführung veränderbar sind.

In unserem Beispiel benötigen wir für die Raumschiff-Position eine Vektor-Animation, welche mit Logic::AnimationVector mShipAnimation instanziert wird.

Initialisierung der Logik

Bool App::AtlasDemoLogic::OnInit(const Logic::IState* state)
{
    Graph::IRoot* root = state->GetGraphRoot();
    
    AddGraphNode(mShipPosition.GetReference(root, "/main/ship_position"));
    AddGraphNode(mShipScale.GetReference(root, "/main/ship_scale"));
    AddGraphNode(mShipSwitch.GetReference(root, "/main/ship_switch"));
    
    mShipAnimation.AddKey(Real(0.0), Vector(-600, 0, 0, 1), IEnums::INTERPOLATION_LINEAR);
    mShipAnimation.AddKey(Real(1.0), Vector( 600, 0, 0, 1));
    AddStepable(mShipAnimation);
    
    return true;
}

Als Erstes werden die Referenzen zu den Raumschiff Knoten hergestellt und der Prozessor Basisklasse hinzugefügt.

Logik Animation

Die Logik-Animation funktioniert grundsätzlich gleich wie die Graph-Timeline Knoten und Graph-Animation Ressourcen. Es können beliebig viele Stützpunkte, sogenannte Keys, angegeben werden. In unserem Beispiel werden jeweils ein Key für den Start und Endpunkt der Raumschiffposition mit mShipAnimation.AddKey(...) angegeben.

Der erste Parameter ist die Zeit des Stützpunktes, der zweite Parameter der Wert des Stützpunktes und der dritte Parameter ist optional die Interpolationsfunktion. Der Vorgabewert ist die lineare Interpolation.

Das Animationsobjekt ist eine Template-Klasse und kann grundsätzlich mit jedem Datentypen arbeiten, wenn in diesem die Operatoren für Addition, Subtraktion und Multiplikation mathematisch korrekt implementiert sind.

Für folgende Datentypen gibt es bereits vordefinierte Animations-Typen:

Stepable-Objekte

Als Stepable werden Objekte bezeichnet, welche das Logic::IStepable Interface implementieren. Stepable-Objekte sind leichtgewichtige Objekte, welche eine Zeit- bzw. Frame-Information benötigen. Ebenso wie die Prozessor Klasse besitzen Stepable-Objekte eine OnProcessTick() Methode, welche jeden Logik-Step abgearbeitet wird. Dies erledigt die Prozessor Basisklasse automatisch, wenn eine Stepable-Objekt Instanz mit der Methode AddStepable() hinzugefügt wird, da die Prozessor Klasse einen eingebauten StepableObserver besitzt.

In unserem Beispiel verwenden wir eine Logic::AnimationVector Instanz, welche zum zeitbasierten Berechnen der Animation die entsprechende OnProcessTick() Methode implementiert. Deshalb ist es notwendig, diese Instanz mit AddStepable(mShipAnimation) hinzuzufügen.

Abarbeiten der Logik

void App::AtlasDemoLogic::OnProcessTick(const Logic::IState* state)
{
    if (!mShipAnimation.IsOrWasRunning())
    {
        SInt32 shipIndex = mRng.RandUInt(0, 2);
        mShipSwitch->SetIndex(shipIndex);
        
        Real scaleFactor = mRng.RandReal(Real(0.5), Real(1.0));
        mShipScale->SetScaleFactor(scaleFactor);
        
        Real shipPositionY = mRng.RandReal(Real(100), Real(200));
        mShipAnimation.mKeys[0].mValue.y = shipPositionY;
        
        Real flightDuration = mRng.RandReal(Real(1.5), Real(3.5));
        mShipAnimation.mKeys[1].mTime = flightDuration;
        shipPositionY = mRng.RandReal(Real(100), Real(200));
        mShipAnimation.mKeys[1].mValue.y = shipPositionY;
        
        mShipAnimation.StartForward();
    }
    
    mShipPosition->SetPosition(mShipAnimation.GetCurrentValue());
}

Als erstes wird überprüft ob die Raumschiff-Animation gerade läuft. Wenn nicht, werden zufällige Animations-Parameter für die Raumschiff Bewegung bestimmt:

  • Zuerst wird mit dem RNG ein Index ermittelt, um eines der drei Raumschiffe zu aktivieren.
  • Anschließend wird ein Skalierungsfaktor mit dem RNG ermittelt, um die Größe des Raumschiffs zu variieren.
  • Zuletzt folgt die zufällige Bestimmung der vertikalen Start- und Endposition sowie der Flugdauer. Die horizontale Start- und Endposition wurden schon in der Initialisierung auf -600 und +600 gesetzt und bleiben unverändert.

Die Manipulation der Animation Keys erfolgt mittels Zugriff auf das mKeys Array. Jedes Key-Element besitzt die Zeit in mTime, den Wert in mValue und die Interpolationsfunktion in mInterpolation. Zuletzt wird die Animation mit mShipAnimation.StartForward() gestartet.

Wenn die Animation läuft, ist es nicht erlaubt, in dem Array mKeys Stützpunkte einzufügen oder zu entfernen. Ansonsten ist dies jederzeit möglich.

Am Ende der Methode wird in jedem Logik-Schritt der aktuelle Positionswert der Raumschiff Animation in den Graph-Knoten für die Raumschiff Position übertragen. Somit ist das Raumschiff in Bewegung.

tut0202_atlas_demo.png
Atlas Demo Ausgabe Fenster


Copyright © 2011-2018 Spraylight GmbH.