Tutorial #01: Bitmap Fonts

Bitmap Fonts bieten die Möglichkeit vorgefertigte, mehrfärbige Zeichen darzustellen.

Diese Methodik ermöglicht eine genaue Abstimmung des Designs, hat aber den Nachteil, dass alle Zeichen als Bild-Ressource zur Verfügung gestellt werden müssen.

Font Converter

Um vorgezeichnete Zeichen in ein textur-taugliches Format umwandeln zu können, müssen ein grafischer Streifen aller Zeichen und eine Beschreibung der Zuordnung erstellt werden.

  • data/orig/arial_color_24.png
  • data/orig/arial_color_48.png
  • data/orig/arial_color.txt

Die beiden PNG-Dateien enthalten jeweils die gezeichneten Zeichen in unterschiedlicher Größe. Die Textdatei beinhaltet die Zeichenfolge aus den Bildern in UTF-8 Kodierung.

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

  • scripts/convert_fonts.sh
  • scripts/convert_fonts.cmd

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

  • data/packages/main.murlres/arial_color_24_glyphs.murl
  • data/packages/main.murlres/arial_color_24_map.png
  • data/packages/main.murlres/arial_color_48_glyphs.murl
  • data/packages/main.murlres/arial_color_48_map.png

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

Folgende zusätzliche Zeicheninformationen können dem font_converter als Parameter übergeben werden:

  • --space_width 10 (oder –sw) Die Breite des Leerzeichen in Pixel.
  • --digit_width 10 (oder –dw) Die feste Breite aller Zahlen (0 – 9) in Pixel.
  • --spacing -5 (oder –s) Der horizontale Abstand der Zeichen zueinander in Pixel.
  • --leading -5 (oder –l) Der vertikale Abstand der Zeichen zueinander in Pixel.

Es gibt noch weitere Parameter, welche in diesem Beispiel unerwähnt bleiben.

Zu beachten ist, dass die Zeichen im Streifen ausreichend Abstand zueinander haben, um als einzelne Zeichen erkannt zu werden. Als Trennung zwischen zwei Zeichen gilt mindestens eine Spalte von Pixel mit Alphawert 0. Um "geteilte" Zeichen miteinander zu verbinden, muss eine "unsichtbare" Verbindung mit einem Alphawert von >= 1 gezeichnet werden.

Szenengraph

Als erstes müssen die Bitmap-Font Ressourcen in das Paket aufgenommen werden.

<?xml version="1.0" ?>
<!-- Copyright 2013 Spraylight GmbH -->
<Package id="package_main">
    
    <!-- Font resources -->
    <Resource id="arial_color_24_glyphs" fileName="arial_color_24_glyphs.murl"/>
    <Resource id="arial_color_24_map"    fileName="arial_color_24_map.png"/>
    <Resource id="arial_color_48_glyphs" fileName="arial_color_48_glyphs.murl"/>
    <Resource id="arial_color_48_map"    fileName="arial_color_48_map.png"/>
    
    <!-- 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>

Die Instanzen für den Textur, den Material und den Main Sub-Graphen werden direkt in der package.xml Datei mittels <Instance> Knoten instanziert.

Definition der Bitmap Font Textur

Zur mehrmaligen Verwendung der Bitmap-Font Texturen empfehlen sich Knoten zum Referenzieren.

<?xml version="1.0" ?>
<!-- Copyright 2013 Spraylight GmbH -->
<Graph>
    <Namespace id="textures" activeAndVisible="no">
        
        <Node id="arial_color_24">
            <FlatTexture id="arial_color_24_map"
            imageResourceId="package_main:arial_color_24_map"
            pixelFormat="R8_G8_B8_A8"
            useMipMaps="no"/>
            <TextureState textureId="arial_color_24_map" slot="0" unit="0"/>
        </Node>
        
        <Node id="arial_color_48">
            <FlatTexture id="arial_color_48_map"
            imageResourceId="package_main:arial_color_48_map"
            pixelFormat="R8_G8_B8_A8"
            useMipMaps="no"/>
            <TextureState textureId="arial_color_48_map" slot="0" unit="0"/>
        </Node>
        
    </Namespace>
</Graph>

Main Graph

<?xml version="1.0" ?>
<!-- Copyright 2013 Spraylight GmbH -->
<Graph>
    <Namespace id="main">
        
        <View id="view"/>
        
        <PerspectiveCamera 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"/>
        
        <Transform id="position">
            
            <Reference targetId="/materials/state_plain_color"/>
            
            <FixedParameters diffuseColor="200i, 200i, 200i, 255i">
                <PlaneGeometry posX="0" posY="80"
                frameSizeX="500" frameSizeY="250"/>
            </FixedParameters>
            
            <FixedParameters diffuseColor="200i, 200i, 200i, 255i">
                <PlaneGeometry posX="0" posY="-150"
                frameSizeX="400" frameSizeY="130"/>
            </FixedParameters>
            
            <Reference targetId="/materials/state_plain_tex_color"/>
            
            <Reference targetId="/textures/arial_color_24"/>
            <TextGeometry id="edit_text"
            fontResourceId="package_main:arial_color_24_glyphs"
            containerSizeX="500" containerSizeY="250"
            posX="0" posY="80"
            text="Spraylight"/>
            
            <Reference targetId="/textures/arial_color_48"/>
            <TextGeometry id="info_text"
            fontResourceId="package_main:arial_color_48_glyphs"
            containerSizeX="400" containerSizeY="130"
            textAlignmentX="LEFT" textAlignmentY="CENTER"
            posX="0" posY="-150"/>
            
        </Transform>
        
    </Namespace>
</Graph>

TextGeometry Knoten

Der TextGeometry Knoten setzt voraus, dass die entsprechende Textur mit den Zeichen aktiv ist. Dies wird mit <Reference targetId="/textures/arial_color_24"/> erreicht.

Die Resource mit den der Textur zugehörigen Zeichendefinitionen wird mit dem Attribut fontResourceId="package_main:arial_color_24_glyphs" festgelegt.

Ein virtueller Container zur Textausrichtung kann mit den Attributen containerSizeX="500" und containerSizeY="250" definiert werden.

Zur Textausrichtung in horizontaler Richtung (textAlignmentX) stehen folgende Werte zur Verfügung:

  • "CENTER" In der Mitte der Textur zentriert (Voreinstellung).
  • "LEFT" Am linken Rand der Textur ausgerichtet.
  • "RIGHT" Am rechten Rand der Textur ausgerichtet.

Zur Textausrichtung in vertikaler Richtung (textAlignmentY) stehen folgende Werte zur Verfügung:

  • "CENTER" In der Mitte der Textur zentriert (Voreinstellung).
  • "TOP" Am oberen Rand der Textur ausgerichtet.
  • "BOTTOM" Am unteren Rand der Textur ausgerichtet.

Der anzuzeigende Text kann mit dem Attribut text="…" direkt in der Graphen-Definition gesetzt oder aber auch nachträglich im Programmcode verändert werden.

Applikation

Die Bitmap-Font Applikation implementiert eine simple Funktionalität zum Editieren eines Texts und einer Informationsanzeige.

Initialisierung der Logik

Bool App::BitmapFontLogic::OnInit(const Logic::IState* state)
{
    Graph::IRoot* root = state->GetGraphRoot();
    
    if (state->GetPlatformConfiguration()->IsTargetClassMatching(IEnums::TARGET_CLASS_HANDHELD))
    {
        Logic::TransformNode position;
        position.GetReference(root, "/main/position");
        if (position.IsValid())
        {
            position->SetPositionY(200);
        }
    }
    
    AddGraphNode(mEditTextNode.GetReference(root, "/main/edit_text"));
    AddGraphNode(mInfoTextNode.GetReference(root, "/main/info_text"));
    
    if (!AreGraphNodesValid())
    {
        return false;
    }
    
    mEditText = mEditTextNode->GetText();
    UpdateText(true);
    
    return true;
}

Im ersten Code-Block wird nur auf Mobilgeräten die Position aller Text-Texturen nach oben verschoben, um Platz für die On-Screen Tastatur zu schaffen.

Anschließend werden die Referenzen zu den beiden Text-Knoten hergestellt und der Prozessor Basisklasse hinzugefügt.

Die Sicherheitsabfrage if (!AreGraphNodesValid()) empfiehlt sich für den Fall, dass während der Initialisierung bereits auf Knoten zugegriffen wird. Ansonsten kommt es zu einem unschönen Absturz, wenn ein Knoten nicht gefunden werden kann, aber trotzdem verwendet wird.

Abschließend wird der anfängliche Editier-Text aus dem Text-Knoten ausgelesen und die Textanzeige aktualisiert.

Abarbeiten der Logik

void App::BitmapFontLogic::OnProcessTick(const Logic::IState* state)
{
    Logic::IDeviceHandler* deviceHandler = state->GetDeviceHandler();
    
    if (!deviceHandler->IsKeyboardShowing())
    {
        state->GetDeviceHandler()->ShowKeyboard();
    }
    
    const StringArray& keys = deviceHandler->GetKeys();
    for (UInt32 i = 0; i < keys.GetCount(); i++)
    {
        Bool isPrintable = true;
        const String& utf8key = keys[i];
        
        IEnums::KeyCode keyCode = IEnums::KEYCODE_NONE;
        if (utf8key.GetLength() == 1)
        {
            keyCode = static_cast<IEnums::KeyCode>(utf8key[0]);
        }
        if (keyCode == IEnums::KEYCODE_BACKSPACE)
        {
            mEditText.RemoveLastUTF8();
        }
        else if (keyCode == IEnums::KEYCODE_RETURN)
        {
            mEditText += "\n";
        }
        else
        {
            if ((keyCode >= IEnums::KEYCODE_SPACE) || (keyCode == IEnums::KEYCODE_NONE))
            {
                if (mEditTextNode->IsCharacterPrintable(utf8key))
                {
                    mEditText += utf8key;
                }
                else
                {
                    isPrintable = false;
                }
            }
        }
        UpdateText(isPrintable);
    }
}

Im ersten Code-Block wird sichergestellt, dass die On-Screen Tastatur sichtbar ist. Falls die Tastatur ausgeblendet ist, wird sie einfach wieder angezeigt.

Der zweite Block implementiert eine simple Editier-Funktion. Es funktioniert lediglich die Eingabe von neuen Zeichen am Textende und das Löschen des letzten Zeichens.

Eine wichtige Rolle übernimmt die Überprüfung mittels if (mEditTextNode->IsCharacterPrintable(utf8key)), um festzustellen, ob das anzuhängende Zeichen überhaupt dargestellt werden kann. Wenn das Zeichen nicht dargestellt werden kann, wird es nicht angehängt und isPrintable auf false gesetzt, um den Grund für das Ignorieren des Zeichens anzuzeigen.

Anzeige des Texts

void App::BitmapFontLogic::UpdateText(Bool isPrintable)
{
    if (isPrintable)
    {
        mEditTextNode->SetText(mEditText + "_");
    }
    else
    {
        mEditTextNode->SetText(mEditText + "_(non printable)");
    }
    
    String infoText;
    infoText += " " + Util::UInt32ToString(mEditText.GetLengthUTF8());
    infoText += " Characters\n";
    infoText += " " + Util::UInt32ToString(mEditText.GetLength());
    infoText += " Bytes";
    mInfoTextNode->SetText(infoText);
}

Diese Hilfsfunktion zeigt den editierten Text mit angehängtem Unterstrich oder dem Kommentar "non printable" an, wenn ein Zeichen nicht darstellbar ist.

Der letzte Block zeigt Informationen über den editierten Text an. Hier wird ersichtlich, dass alle Zeichen in UTF-8 Codierung gespeichert sind und nicht genau der Bytelänge des Texts entsprechen müssen z.B. durch Eingabe von Umlauten oder bestimmten Sonderzeichen.

tut0201_bitmap_font.png
Bitmap Font Ausgabe Fenster


Copyright © 2011-2018 Spraylight GmbH.