Juni 2017

Orbit Asteroids - Neue Spieletechnologien im Web

Benutzte Technologien

Bei der Umsetzung kamen insgesamt drei verschiedene JavaScript-Frameworks zum Einsatz:

  • Google Closure
  • THREE.JS
  • Greensock


Google Closure ist das von Google entwickelte JavaScript-Framework, welches uns als Grundlage des Spiels diente. Dieses Framework stellt uns durch eigens entwickelte Funktionen ein Grundgerüst zur Spieleentwicklung bereit.

THREE.JS ist das Framework zur Entwicklung mittels WebGL - also hardware-unterstützten Web-Applikationen. Mit diesem Framework konnten wir das Spiel an sich aufbauen und die Funktionen und Interaktionen bauen.

Mit Greensock war es uns möglich, diverse Animationen im Spiel selbst einfach umzusetzen, wie beispielsweise die Rotationen und Bewegungen der Kamera und des Spielcharakters an sich.

Probleme und deren Lösungen

Natürlich war die Entwicklung des Demo-Spiels kein Einfaches. Es gab auch hier wieder einige Probleme zu lösen. 

Eines davon war die Performance. Natürlich geht - unabhängig von der benutzten Hardware - die Performance einer Website immer weiter in den Keller, wenn viele sich bewegende Elemente platziert werden. Das war auch hier der Fall, denn wir haben mehrere Hundert Planeten und Asteroiden in der virtuellen Welt platziert. 

Wie also die Performance oben halten, und trotzdem genug Objekte platzieren, damit das Feeling stimmt?

Eine Lösung dafür ist die Sichtbarkeit der platzieren Elemente zu beeinflussen. Warum sollte man Elemente anzeigen, die sich gerade hinter einem befinden oder die durch eine gewisse Entfernung sowieso nicht mehr sichtbar sind? Und warum dann noch dafür sorgen, dass diese sich auch bewegen und rotieren?

Daher entwickelten wir einen Code, der die Position des Spielers und die Position der jeweiligen Hindernisse ausliest und miteinander vergleicht. Wenn das Hindernis hinter oder mehr als 20% der Gesamtstrecke vor dem Spieler liegt, so wird das Hindernis ausgeblendet und damit auch nicht mehr rotiert. Dies spart vor allem bei den Berechnungen von mehreren Hundert Objekten enorm viel Performance. 

                                    var obstacles = [...]; // Variable containing the obstacles
var obstacleRotation = [0.3, 0.2, 0.1]; // Variable for Rotation of the obstacles in x-, y- and z-direction

function updateObstacles(progress)
{
    for(var i = 0; i < obstacles.length; i++)
    {
        var isInVisibleArea = ((obstacles[i]['progress'] > progress - .01 && obstacles[i]['progress'] < progress + .2) || ( progress > .8 && obstacles[i]['progress'] < .2 ));

        obstacles[i]['visible'] = isInVisibleArea;

        if(isInVisibleArea)
        {
            obstacles[i]['rotation']['x'] += obstacleRotation[0];
            obstacles[i]['rotation']['y'] += obstacleRotation[1];
            obstacles[i]['rotation']['z'] += obstacleRotation[2];
        }
    }
}
                                

Ein weiteres Problem war die Kollisionserkennung. Wie konnte man performant herausfinden, ob ein Hindernis gerade mit dem Spieler kollidiert?

Auch hier kommt ein Teil des Codes der Performance-Optimierung wieder zum Tragen. Man muss ja schließlich nicht prüfen, ob sich ein Hindernis, das sich auf der anderen Seite der 3D-Welt befindet, gerade in der Nähe des Spielcharakters ist. 

In unserem Fall machen wir die Kollisionserkennung mit Hindernissen nur dann, wenn sich ein Element auf 1% der Gesamtstrecke nah an dem Spieler befindet. 

Dazu werden sogenannte "Bounding-Boxes" um die zu prüfenden Elemente erzeugt - also Würfel, die für den Endbenutzer unsichtbar sind und genau die Größe der Hindernisse und des Spielers haben. Wenn die Boxen sich dann überschneiden, so ist eine Kollision erfolgt.

Dabei ist zu beachten, dass es sich um dreidimensionale Elemente handelt. Hindernisse kollidieren daher in jedem Browsertick erneut, solange sich der Spielcharakter gerade durch sie hindurch bewegt. Um dies abzufangen haben wir einfach bei der ersten Kollision eine Variable im Hindernis gesetzt, welches uns zeigt, dass eine Kollision bereits erfolgt ist und daher keine weitere Kollision geprüft werden muss. 

                                    var obstacles = [...]; // Variable containing the obstacles

function checkCollision(player, progress)
{
    var firstBoundingBox = new THREE.Box3().setFromObject(player);

    for(var i = 0; i < obstacles.length; i++)
    {
        if(obstacles[i]['progress'] > progress - .01 && obstacles[i]['progress'] < progress + .01 && !obstacles[i]['obstacleAlreadyCollided'])
        {
            var secondBoundingBox = new THREE.Box3().setFromObject(obstacles[i]['children'][0]);
            var collision = firstBoundingBox.intersectsBox(secondBoundingBox);

            if(collision)
            {
                obstacles[i]['obstacleAlreadyCollided'] = true;
                // handle collision
            }
        }
    }
}
                                

Ein drittes großes Problem war das Feeling im 3D-Raum, das wir so echt wie möglich machen wollten. Einfach nur ein paar Hindernisse und Planeten zu positionieren war uns da zu wenig. Ein Partikelsystem musste her, denn mehrere Tausend kleine Elemente zu platzieren und während der Laufzeit immer zu aktualisieren wäre selbst für den besten Computer zu viel. Wir entwickelten eine Schleife, die 15.000 mal durchlaufen wird und jedes mal einen kleinen Kreis mittels einer Grafik zufällig in unsere Welt platziert.

Diese Punkte haben wir dann mittels "Vertices" in einem dreidimensionalen Objekt kombiniert und dargestellt. Wir mussten also nur eines an Stelle von 15.000 Objekten positionieren. Die Berechnungen des Browsers während der Laufzeit war damit auch ungemein schneller.

                                    var particles, geometry, materials = [], texture;

function generateParticles()
{
    geometry = new THREE.Geometry();

    texture = new THREE.TextureLoader().load("path/to/the/texture.png");

    for (var i = 0; i < 15000; i ++ ) 
    {
        //get a random position
        var vertex = new THREE.Vector3();
        vertex.x = Math.random() * 4000 - 2000;
        vertex.y = Math.random() * 4000 - 2000;
        vertex.z = Math.random() * 4000 - 2000;

        geometry['vertices'].push( vertex );
    }

    materials[i] = new THREE.PointsMaterial( { 'size': 4, 'transparent': true, 'map': texture } );

    particles = new THREE.Points( geometry, materials[i] );

    scene.add( particles );
}
                                

Browser-Support

WebGL ist eine relativ neue Technologie für manche Browser, jedoch unterstützen Andere wie dies schon lange.

Das Spiel wurde daher optimiert für folgende Browser:

  • Internet Explorer 11
  • Microsoft Edge 12+
  • iOS 8+
  • Android 5+
  • Mozilla Firefox 34+
  • Google Chrome 42+


WebGL ist allerdings als Technologie schon etwas länger verfügbar, vor allem was den Google Chrome und Firefox betrifft: