09.05.2014

libGDX - den DebugRenderer ersetzen: Projektionsmatrizen

TL;DR die OrthographicCamera rechnet uns mit den project() und unproject() Methoden ganz von alleine die Welt-Koordinaten in Screen-Koordinaten um und zurück.

Um Animationen und Sprites an der korrekten Stelle zu zeichnen empfehle ich 2 SpriteBatches, von denen einer mit worldBatch.setProjectionMatrix(camera.combined); für die automatische Umrechnung von Welt auf Screen-Koordinaten genutzt wird.


Mit dem DebugRenderer von Box2D lassen sich schnell und leicht die Entities anzeigen, damit hat man zumindest einen guten Überblick.

Sobald man nun aber Texturen auf die rohen Skelette legen will, muss man feststellen, dass die SpriteBatches Pixel Werte verlangen, relativ zum Bildschirm, Box2D nutzt aber Meter als Einheit. Nun bin ich mir nicht sicher, ob ich einfach die Doku nicht genau gelesen hatte, aber die Tatsache ist: ich habe dann doch viel zuviel Zeit in die händische Umrechnung von Worldunits (also Meter) in Pixel-Koordinaten auf dem Screen vergeudet. Leider war Google auch wenig hilfreich, denn immer wieder liest man von der manuellen Umrechnung mit PIXEL_PER_METER Faktoren etc. etc.

Die erfreuliche Nachricht: libGDX will unser Freund sein und nimmt uns das alles ab!

Nachdem ich nochmal die Doku gewälzt hatte, fielen mir die project/unproject Methoden der orthographischen Kamera in die Hände. Der erste Satz aus den Java-Docs war vielversprechend: "Projects the Vector3 given in world space to screen coordinates."

Genau das nachdem ich so lange gesucht hatte. Also wurde kurzerhand das Renderable Interface um die Kamera und einen SpriteBatch erweitert, und es wurde ganz problemlos die Demo-Animation an der richtigen Stelle gerendert.

Da der coole Teil noch kommt, hier nur im Groben wie project() funktioniert.

//screenPosition = Vector3
screenPosition.set(body.getPosition().x, body.getPosition().y , 0);
//calculate screen coordinates
camera.project(screenPosition);
batch.begin();
batch.draw(currentFrame, 
        screenPosition.x, 
        screenPosition.y, 
        width, 
        height );
batch.end();

Nun haben wir aber in einem realen Fall mehrere Entities auf dem Screen. Die oben gezeigte Methode funktioniert auch damit, kam mir aber nicht sehr komfortabel vor (wenn auch sehr viel eleganter als die manuelle Berechnung zuvor), denn wir brauchen für jede Entity einen zusätzlichen Vektor in dem wir für jeden Frame in jeweils 2 Schritten die Pixel-Koordinaten berechnen müssen.

Schon zuvor habe ich den Code des DebugRenderers durchforstet, weil ich wissen wollte, wofür dieser dieses camera.combined benötigt.

//camera.combined = the combined projection and view matrix 
debugRenderer.render(world, camera.combined);

Nach genauerem Nachlesen und dem weiteren Durchsuchen der Doku und der Java-Docs habe ich dann den "korrekten" Weg gefunden. Das erwähnte camera.combined ist eine 4x4 Matrix die alle Transformations-Daten über unsere Kamera und Welt enthält. Wer sich mit dem Thema etwas genauer auseinandersetzen möchte, dieser Artikel hat mir sehr beim Verständnis geholfen.

Der SpriteBatch bietet die Funktion setProjectionMatrix an. Damit können alle unsere Welt-Objekte automatisch auf unsere Kamera projeziert werden. Da ich eine Möglichkeit gefunden habe die gesetzte Projektionsmatrix wieder zu entfernen, wurden alle UI Elemente wie z.B. die Meteranzeige ebenso projeziert, also irgendwo weit außerhalb des sichtbaren Bereichs gezeichnet. Um dieses Verhalten zu umgehen habe ich einen zweiten SpriteBatch in meinem GameScreen angelegt, also benutze ich einen screenBatch und einen worldBatch. Mit dem screenBatch werden UI Elemente und Hintergründe gezeichnet, und mit dem worldBatch lassen wir uns von libGDX alle Box2D Entities an die korrekte Screenposition zeichnen.

//gameScreen
@Override
public void render(float delta) {
	screenBatch.begin();
        // drawing backgrounds
        background.render(delta, screenBatch, camera);
        //fonts
        font.draw(screenBatch, highscore + " m", 20, hardwareHeight);
        screenBatch.end();

	worldBatch.setProjectionMatrix(camera.combined);
	//render all box2D Entities
}

Da der SpriteBatch eines der schwereren Objekte ist, empfiehlt es sich aus Sicht der Performance die beiden Objekte einmal zu erstellen und dannach in alle render Aufrufe zu übergeben. Dabei können innerhalb eines render-Loops beliebig oft begin() und end() aufgerufen werden.

Ich werde immer begeisterter von libGDX! Wohoooo!


error success