Version 1: Texturatlas
Animierte Grafiken bzw. Bildsequenzen können mit dem Objekt PlaneSequenceGeometry
erstellt werden. Dafür wird ein Texturatlas und eine Atlas-XML-Datei benötigt:
texture_explosion.png
atlas_explosion.xml
Der Texturatlas fasst viele Einzelbilder in einem großen Gesamtbild zusammen, wobei die Atlas-XML-Datei die Positionen der Einzelbilder speichert. Beide Dateien müssen in der Datei package.xml
bekannt gemacht werden.
<!-- Graphic resources --> <Resource id="gfx_explosion" fileName="gfx/gfx_explosion.png"/> <!-- Atlas resources --> <Resource id="atlas_explosion" fileName="atlas_explosion.xml"/>
Der Texturatlas zeigt eine Explosionsanimation, welche aus 32 Einzelbildern besteht. Jedes Einzelbild hat eine Größe von 64x64 Pixeln. Die einheitliche Größe der Einzelbilder wurde nur für dieses Beispiel gewählt und ist nicht zwingend vorgeschrieben.
Im Atlas-XML-File wird für jedes Einzelbild ein rechteckiger Ausschnitt definiert. Mit den Attributen texCoordX1
und texCoordX2
werden die X-Koordinaten und mit texCoordY1
und texCoordY2
die Y-Koordinaten des Rechtecks festgelegt. Die Angaben sind relativ zur später in der PlaneSequenceGeometry
angegebenen textureSizeX
bzw. textureSizeY
.
Für unser Beispiel verwenden wir die Pixel-Koordinaten und geben für textureSizeX
und textureSizeY
die Pixelauflösung des Gesamtbildes an. Alternativ werden oft normierte Werte im Bereich von 0 bis 1 für die Koordinaten sowie der Standardwert 1 für textureSizeX
bzw. textureSizeY
verwendet.
<?xml version="1.0" ?> <Atlas> <Rectangle texCoordX1="0" texCoordY1="0" texCoordX2="64" texCoordY2="64"/> <Rectangle texCoordX1="64" texCoordY1="0" texCoordX2="128" texCoordY2="64"/> <Rectangle texCoordX1="128" texCoordY1="0" texCoordX2="192" texCoordY2="64"/> <Rectangle texCoordX1="192" texCoordY1="0" texCoordX2="256" texCoordY2="64"/> <Rectangle texCoordX1="256" texCoordY1="0" texCoordX2="320" texCoordY2="64"/> <Rectangle texCoordX1="320" texCoordY1="0" texCoordX2="384" texCoordY2="64"/> <Rectangle texCoordX1="384" texCoordY1="0" texCoordX2="448" texCoordY2="64"/> <Rectangle texCoordX1="448" texCoordY1="0" texCoordX2="512" texCoordY2="64"/> <Rectangle texCoordX1="0" texCoordY1="64" texCoordX2="64" texCoordY2="128"/> <Rectangle texCoordX1="64" texCoordY1="64" texCoordX2="128" texCoordY2="128"/> <Rectangle texCoordX1="128" texCoordY1="64" texCoordX2="192" texCoordY2="128"/> <Rectangle texCoordX1="192" texCoordY1="64" texCoordX2="256" texCoordY2="128"/> <Rectangle texCoordX1="256" texCoordY1="64" texCoordX2="320" texCoordY2="128"/> <Rectangle texCoordX1="320" texCoordY1="64" texCoordX2="384" texCoordY2="128"/> <Rectangle texCoordX1="384" texCoordY1="64" texCoordX2="448" texCoordY2="128"/> <Rectangle texCoordX1="448" texCoordY1="64" texCoordX2="512" texCoordY2="128"/> <Rectangle texCoordX1="0" texCoordY1="128" texCoordX2="64" texCoordY2="192"/> <Rectangle texCoordX1="64" texCoordY1="128" texCoordX2="128" texCoordY2="192"/> <Rectangle texCoordX1="128" texCoordY1="128" texCoordX2="192" texCoordY2="192"/> <Rectangle texCoordX1="192" texCoordY1="128" texCoordX2="256" texCoordY2="192"/> <Rectangle texCoordX1="256" texCoordY1="128" texCoordX2="320" texCoordY2="192"/> <Rectangle texCoordX1="320" texCoordY1="128" texCoordX2="384" texCoordY2="192"/> <Rectangle texCoordX1="384" texCoordY1="128" texCoordX2="448" texCoordY2="192"/> <Rectangle texCoordX1="448" texCoordY1="128" texCoordX2="512" texCoordY2="192"/> <Rectangle texCoordX1="0" texCoordY1="192" texCoordX2="64" texCoordY2="256"/> <Rectangle texCoordX1="64" texCoordY1="192" texCoordX2="128" texCoordY2="256"/> <Rectangle texCoordX1="128" texCoordY1="192" texCoordX2="192" texCoordY2="256"/> <Rectangle texCoordX1="192" texCoordY1="192" texCoordX2="256" texCoordY2="256"/> <Rectangle texCoordX1="256" texCoordY1="192" texCoordX2="320" texCoordY2="256"/> <Rectangle texCoordX1="320" texCoordY1="192" texCoordX2="384" texCoordY2="256"/> <Rectangle texCoordX1="384" texCoordY1="192" texCoordX2="448" texCoordY2="256"/> <Rectangle texCoordX1="448" texCoordY1="192" texCoordX2="512" texCoordY2="256"/> </Atlas>
Plane Sequence Geometry
Im Graphen setzen wir den Texturatlas mit TextureState
und wählen ein geeignetes Material zum Zeichnen aus.
Danach definieren wir eine PlaneSequenceGeometry
, um die Einzelbilder anzuzeigen. Zum Vergleich erstellen wir auch einen PlaneGeometry
Knoten, der uns den gesamten Texturatlas anzeigt.
Mit dem Attribut atlasResourceId
definieren wir die zu verwendende Atlas-XML-Datei mit den Positionen für die Einzelbilder. Die Attribute textureSizeX
und textureSizeY
legen die Größe des gesamten Texturatlas-Bildes in Bezug auf die angegebenen Koordinatenwerte in der Atlas-XML-Datei fest.
Jedes Rechteck in der Atlas-XML-Datei definiert ein Einzelbild mit eindeutigem, fortlaufendem Index. Die Indizierung erfolgt aufsteigend und beginnt mit 0. Die Auswahl des Einzelbildes erfolgt mit dem Attribut index
.
<TextureState textureId="material/texture_explosion"/> <MaterialState materialId="material/mat_alpha_color_texture"/> <PlaneGeometry id="plane5" scaleFactorX="512" scaleFactorY="256" posX="0" posY="100" /> <PlaneSequenceGeometry id="myPlaneSequence" atlasResourceId="package_main:atlas_explosion" textureSizeX="512" textureSizeY="256" scaleFactorX="64" scaleFactorY="64" index="5" posX="0" posY="-100" />
Um die Indexnummer im Programm verändern zu können, erstellen wir noch ein PlaneSequenceGeometryNode
-Objekt
Logic::PlaneSequenceGeometryNode mPlaneSequence;
und verknüpfen es mit dem Graphenknoten:
AddGraphNode(mPlaneSequence.GetReference(root, "myPlaneSequence"));
Mit der Methode SetIndex()
kann ein Einzelbild ausgewählt werden. Wir verwenden für die Steuerung die linke bzw. rechte Maustaste sowie die Pfeiltasten links bzw. rechts.
void App::AnimatedImagesLogic::OnProcessTick(const Logic::IState* state) { Logic::IDeviceHandler* deviceHandler = state->GetDeviceHandler(); if (deviceHandler->WasMouseButtonPressed(IEnums::MOUSE_BUTTON_LEFT) || deviceHandler->WasRawKeyPressed(RAWKEY_RIGHT_ARROW)) { UInt32 index = mPlaneSequence->GetIndex() + 1; index = index < 32 ? index : 0; mPlaneSequence->SetIndex(index); state->SetUserDebugMessage("Index " + Util::UInt32ToString(index)); } if (deviceHandler->WasMouseButtonPressed(IEnums::MOUSE_BUTTON_RIGHT)|| deviceHandler->WasRawKeyPressed(RAWKEY_LEFT_ARROW)) { UInt32 index = mPlaneSequence->GetIndex() - 1; index = index > 31 ? 31 : index; mPlaneSequence->SetIndex(index); state->SetUserDebugMessage("Index " + Util::UInt32ToString(index)); } if (deviceHandler->WasRawKeyPressed(RAWKEY_ESCAPE) || deviceHandler->WasRawButtonPressed(RAWBUTTON_BACK)) { deviceHandler->TerminateApp(); } }
Version 2: Animation
Anstatt mit SetIndex()
gezielt ein Einzelbild auszuwählen, kann alternativ die Animation auch mit einer zusätzlichen Animationsdatei automatisiert und zeitgesteuert abgespielt werden.
Die neue Datei muss wiederum mit einer eindeutigen id
im Package bekannt gemacht werden.
<!-- Animation resources --> <Resource id="anim_explosion" fileName="anim_explosion.xml"/>
In der Animationsdatei erstellen wir eine Animation mit definierter Startzeit (0,0 Sekunden) und Endzeit (1,5 Sekunden). Mit dem Element IndexKey
erstellen wir eine Sequenz von Indexwerten mit zugehörigen Zeitwerten. In unserem Beispiel definieren wir diese Sequenz mit nur zwei Stützwerten – einem zum Zeitpunkt 0,0 Sekunden mit dem Indexwert 0 und einem zum Zeitpunkt 1,5 Sekunden mit dem Indexwert 31. Die Zwischenwerte werden automatisch gemäß einer vorgegebenen Interpolationsfunktion berechnet.
<?xml version="1.0" ?> <Animation startTime="0.0" endTime="1.5"> <IndexKey time="0.0" value="0"/> <IndexKey time="1.5" value="31"/> </Animation>
Im Graphen erzeugen wir einen neuen PlaneSequenceGeometry
-Knoten und verknüpfen diesen mit dem Attribut controller.animationResourceId
mit der Animationsdatei. Zusätzlich verpacken wir den Knoten in eine Graph::Timeline
, um die Animation steuern zu können.
<Timeline id="explosion_timeline" startTime="0.0" endTime="1.5" autoRewind="yes" > <PlaneSequenceGeometry id="myAnimationPlaneSequence" atlasResourceId="package_main:atlas_explosion" textureSizeX="512" textureSizeY="256" scaleFactorX="64" scaleFactorY="64" posX="100" posY="-100" controller.animationResourceId="package_main:anim_explosion" /> </Timeline>
In der Methode OnProcessTick()
können wir mit einem passenden Logic::TimelineNode
die Animation steuern. Mit der linken Maustaste kann die Animation neu gestartet und mit der rechten Maustaste pausiert werden.
void App::AnimatedImagesLogic::OnProcessTick(const Logic::IState* state) { Logic::IDeviceHandler* deviceHandler = state->GetDeviceHandler(); if (deviceHandler->WasMouseButtonPressed(IEnums::MOUSE_BUTTON_LEFT) || deviceHandler->WasRawKeyPressed(RAWKEY_RIGHT_ARROW)) { UInt32 index = mPlaneSequence->GetIndex() + 1; index = index < 32 ? index : 0; mPlaneSequence->SetIndex(index); state->SetUserDebugMessage("Index " + Util::UInt32ToString(index)); mTimeLine->Rewind(); mTimeLine->Start(); } if (deviceHandler->WasMouseButtonPressed(IEnums::MOUSE_BUTTON_RIGHT)|| deviceHandler->WasRawKeyPressed(RAWKEY_LEFT_ARROW)) { UInt32 index = mPlaneSequence->GetIndex() - 1; index = index > 31 ? 31 : index; mPlaneSequence->SetIndex(index); state->SetUserDebugMessage("Index " + Util::UInt32ToString(index)); if (mTimeLine->IsPaused()) mTimeLine->Start(); else mTimeLine->Pause(); } if (deviceHandler->WasRawKeyPressed(RAWKEY_ESCAPE) || deviceHandler->WasRawButtonPressed(RAWBUTTON_BACK)) { deviceHandler->TerminateApp(); } }
Version 3: Interpolation
Eine Animationsdatei kann nicht nur den IndexKey
steuern, sondern auch viele andere Parameter. Beispielsweise kann mit dem AlphaKey
ein Bild ein- oder ausgeblendet oder mit dem PositionKey
ein Bild bewegt werden. Folgende Key-Elemente stehen zur Verfügung:
VisibleKey
ActiveKey
IndexKey
AlphaKey
VolumeKey
TimeKey
AmbientColorKey
DiffuseColorKey
SpecularColorKey
EmissiveColorKey
PositionKey
ScalingKey
AxisAngleKey
QuaternionKey
Siehe auch Abschnitt Animationsressourcen in der User's Guide Dokumentation.
Mit dem Attribut interpolation
kann für jedes Key-Element die zu verwendende Interpolationsfunktion vorgegeben werden (Standardwert ist LINEAR
). Alle erlaubten Werte sind in der Enumeration IEnums::Interpolation
gelistet.
Interpolationsfunktionen
Im folgenden Beispiel werden die Sichtbarkeit und der Index über die Animation gesteuert.
<?xml version="1.0" ?> <Animation startTime="0.0" endTime="5"> <VisibleKey time="0.0" value="0" interpolation="CONSTANT"/> <VisibleKey time="0.01" value="1" interpolation="CONSTANT"/> <VisibleKey time="5" value="0"/> <IndexKey time="0.0" value="0" interpolation="LINEAR"/> <IndexKey time="5" value="31"/> </Animation>
Der VisibleKey
wird beim Starten der Animation auf sichtbar (1) gesetzt (Zeitpunkt 0,01 Sekunden). Nach dem Ende der Animation wird der VisibleKey
wieder auf nicht sichtbar geschaltet (Zeitpunkt 5 Sekunden).
Mit den Attributen controller.timeScale
und controller.timeShift
kann zusätzlich die Zeitangabe in der Animationsdatei skaliert und verschoben werden. Die tatsächliche Zeit in der Animationsdatei ergibt sich aus der Formel:
(time – animationTimeShift) * animationTimeScale
In folgendem Beispiel wird durch die Angabe von controller.timeScale="2"
die Animation doppelt so schnell abgespielt (also in 2,5 Sekunden anstatt der im Animation File definierten 5 Sekunden).
<Timeline id="explosion_timeline" startTime="0.0" endTime="3" autoRewind="yes" > <PlaneSequenceGeometry id="myAnimationPlaneSequence" atlasResourceId="package_main:atlas_explosion" textureSizeX="512" textureSizeY="256" scaleFactorX="64" scaleFactorY="64" posX="300" posY="-100" controller.animationResourceId="package_main:anim_explosion" controller.timeScale="2" />
Zum Vergleich der unterschiedlichen Interpolationsfunktionen, erstellen wir für einige Interpolationsfunktionen einen PlaneGeometry
-Graphenknoten mit eigener Animationsdatei.
<?xml version="1.0" ?> <Animation startTime="0.0" endTime="1"> <PositionKey time="0.0" posX="-50" posY="-250" interpolation="SMOOTHSTEP_IN"/> <PositionKey time="1" posX="-50" posY="250"/> </Animation>
<PlaneGeometry id="plane6" scaleFactorX="64" scaleFactorY="64" controller.animationResourceId="package_main:anim_smoothstep_in" depthOrder="1" /> <PlaneGeometry id="plane7" scaleFactorX="64" scaleFactorY="64" controller.animationResourceId="package_main:anim_smoothstep_out" depthOrder="1" /> <PlaneGeometry id="plane8" scaleFactorX="64" scaleFactorY="64" controller.animationResourceId="package_main:anim_smoothstep_in_out" depthOrder="1" />
Als Ergebnis erhalten wir eine Reihe von Smiley-Grafiken, die sich beim Drücken der linken Maustaste mit unterschiedlichen Geschwindigkeitsverläufen von unten nach oben bewegen.