Spieleentwicklung

2D-Zeichnungstechniken und Bibliotheken für Onlinespiele

Michael Oneppo

Lange Zeit gab es eigentlich nur eine Möglichkeit, interaktive Onlinespiele zu entwickeln: Flash. Und ob's einem gefällt oder nicht, Flash hatte ein schnelles Zeichensystem. Jeder verwendete es zum Erstellen von Animationen, Point-and-Click-Abenteuern und aller möglichen anderen Erfahrungen.

Als sich Browser mit HTML5 an Webstandards anpassten, gab es eine regelrechte Explosion von Optionen für die Entwicklung schneller, qualitativ hochwertiger Grafiken – ohne erforderliche Plug-Ins. In diesem Artikel werden ein kleiner Teil der Zeichenmethoden und die zugrunde liegenden Technologien sowie einige Bibliotheken zur Vereinfachung ihrer Verwendung vorgestellt. Ich werde nicht auf Bibliotheken eingehen, die spezifisch für Spiele gedacht sind. Es gibt so viele davon, dass ich mir diese Diskussion für einen anderen Artikel aufhebe.

Zeichenstandards

Mit dem Einsatz von HTML5 haben sich drei Möglichkeiten zum Zeichnen in 2D ergeben: das Document Object Model (DOM)-, Canvas- und Scalable Vector Graphics (SVG)-Format. Bevor die Bibliotheken näher untersucht werden, die diese Technologien verwenden, werde ich darauf eingehen, wie jede einzelne funktioniert, um die Vor- und Nachteile jeder Methode besser verständlich zu machen.

Es überrascht nicht, dass die einfachste Möglichkeit zum Zeichnen von Grafiken in HTML tatsächlich HTML ist. Durch Erstellen einer Reihe von Bild- oder Hintergrundelementen und Verwenden einer Bibliothek wie jQuery können Sie schnell Sprites erstellen, die Sie ohne Neuzeichnen der Szene verschieben können. Der Browser übernimmt das für Sie. Diese Art von Struktur wird häufig als Szenengraph bezeichnet. Im Fall von HTML ist DOM der Szenengraph. Da Sie CSS zum Formatieren Ihrer Sprites verwenden, können Sie auch CSS-Übergänge und Animationen verwenden, um Ihrer Szene gleichmäßige Bewegungen hinzufügen.

Das Hauptproblem bei dieser Methode ist, dass sie vom DOM-Renderer abhängig ist. Dieser kann die Dinge verlangsamen, wenn Sie eine komplexe Szene haben. Ich empfehle, nicht mehr als ein paar Hundert Elemente zu verwenden. Daher können bei etwas komplexeren Spielen als bei Match-3- oder Plattformspielen Leistungsprobleme auftreten. Und eine plötzliche Zunahme der Anzahl von Elementen, z. B. in einem Partikelsystem, kann zu Unterbrechungen in der Animation führen.

Ein weiteres Problem bei dieser Methode ist, dass Sie CSS zum Formatieren von Elementen verwenden müssen. Je nachdem, wie Sie CSS schreiben, kann es recht schnell oder ziemlich langsam sein. Schlussendlich kann es schwierig sein, für HTML geschriebenen Code in ein anderes System, z. B. systemeigenes C++, zu verschieben. Dies ist wichtig, wenn Sie Ihr Spiel z. B. zu einer Konsole portieren möchten. Hier eine Übersicht der Vorteile:

  • Baut auf der grundlegenden Struktur einer Webseite auf.
  • jQuery und andere Bibliotheken erleichtern das Verschieben von Elementen.
  • Sprites können relativ einfach eingerichtet werden.
  • Integriertes Animationssystem mit CSS-Übergängen und Animationen.

Und hier eine Übersicht der Nachteile:

  • Viele kleine Elemente können Sie ausbremsen.
  • CSS muss zum Formatieren von Elementen verwendet werden.
  • Keine Vektorgrafiken.
  • Portierung zu anderen Plattformen schwierig.

HTML5 Canvas

Das Canvas-Element beseitigt viele Nachteile. Es bietet eine Renderingumgebung im unmittelbaren Modus – einen flachen Streifen von Pixeln. Sie weisen es in JavaScript an, was zu zeichnen ist, und es wird sofort gezeichnet. Da es Ihre Zeichenbefehle in Pixel konvertiert, können Sie schnell eine lange Liste von Zeichenbefehlen zusammenstellen, ohne dass das System in die Knie geht. Sie können Geometrie, Text, Bilder, Farbverläufe und andere Elemente zeichnen. Weitere Informationen zur Verwendung des Canvas-Elements für Spiele finden Sie im Artikel von David Catuhe unter bit.ly/1fquBuo.

Worin bestehen die Nachteile? Da das Canvas-Element vergisst, was es gezeichnet hat, wenn der Vorgang abgeschlossen ist, müssen Sie die Szene jedes Mal selbst neu zeichnen, wenn Sie sie ändern möchten. Und wenn Sie eine Form auf komplexe Weise ändern möchten, z. B. biegen oder animieren, müssen Sie die Berechnungen durchführen und das Element neu zeichnen. Dies bedeutet, dass Sie eine große Datenmenge zu Ihrer Szene in Ihren eigenen Datenstrukturen verwalten müssen. Dies ist jedoch nicht wirklich eine große Sache, da es Bibliotheken gibt, die diese Aufgabe erleichtern. Wenn Sie wirklich etwas angepasst ausführen möchten, sollten Sie bedenken, dass Canvas keine Informationen für Sie speichert. Darüber hinaus bietet Canvas keine Animationen. Sie müssen Ihre Szene in aufeinander folgenden Schritten zeichnen, um eine flüssige Animation zu erzeugen. Hier eine Übersicht der Vorteile:

  • Zeichnet direkt – Szenen können komplexer sein.
  • Unterstützt viele unterschiedliche visuelle Elemente.

Und hier eine Übersicht der Nachteile:

  • Kein inhärenter Speicher der Szene. Sie müssen ihn selbst erstellen.
  • Komplexe Transformationen und Animationen müssen manuell erfolgen.
  • Kein Animationssystem.

SVG: Scalable Vector Graphics

Als XML-basiertes Markup zum Beschreiben von visuellen 2D-Elementen ist SVG mit HTML vergleichbar. Der Hauptunterschied ist, dass SVG für das Zeichnen vorgesehen ist, während HTML in erster Linie für Text und Layout gedacht ist. SVG weist einige leistungsfähige Zeichenfunktionen auf, wie z. B. glatte Formen, komplexe Animationen, Verformungen und sogar Bildfilter wie Weichzeichner. Wie HTML hat SVG eine Szenengraphstruktur, sodass Sie SVG-Elemente durchgehen, Formen hinzufügen und deren Eigenschaften ändern können, ohne sich Gedanken über das Neuzeichnen machen zu müssen. Der Browser übernimmt das für Sie. Im Video "Working with SVG in HTML5" von Channel 9 (bit.ly/1DEAWmh) finden Sie weitere Erläuterungen.

Wie bei HTML können komplexe Szenen SVG ausbremsen. SVG kommt mit einem gewissen Grad an Komplexität zurecht, aber es erreicht nicht ganz die Komplexität, die Canvas zu bieten hat. Darüber hinaus können die Tools für die Bearbeitung von SVG komplex sein, wenngleich es andere Tools zur Vereinfachung des Prozesses gibt. Hier eine Übersicht der Vorteile:

  • Viele Zeichenoptionen wie gekrümmte Oberflächen und komplexe Formen.
  • Strukturiert, ohne dass neu gezeichnet werden muss.

Und hier eine Übersicht der Nachteile:

  • Komplexität kann das System ausbremsen.
  • Schwierig zu bearbeiten.

2D-Zeichenbibliotheken

Nachdem ich Ihnen die Standards für das Zeichnen im Web vorgestellt habe, werfen wir nun einen Blick auf einige Bibliotheken, die Zeichnung und Animation leichter machen können. Es ist erwähnenswert, dass nur Sie selten zeichnen, ohne etwas anderes mit der Zeichnung zu tun. Beispielsweise benötigen Sie häufig Grafiken, um auf eine Eingabe zu reagieren. Bibliotheken können allgemeine Aufgaben im Zusammenhang mit dem Zeichnen vereinfachen.

KineticJS Hätten Sie gern einen Szenengraph für Canvas? KineticJS ist eine äußerst leistungsfähige Canvas-Bibliothek, die mit einem Szenengraph beginnt und weitere Funktionen hinzugefügt. Zunächst einmal ermöglicht Ihnen KineticJS das Definieren von Ebenen auf der Canvas, die zu zeichnende Formen enthalten. Beispielsweise wird in Abbildung 1 gezeigt, wie mit KineticJS ein einfacher roter Kreis gezeichnet wird.

Abbildung 1: Zeichnen eines Kreises mit KineticJS

// Points to a canvas element in your HTML with id "myCanvas"
var myCanvas = $('#myCanvas'); 
var stage = new Kinetic.Stage({
  // get(0) returns the first element found by jQuery,
  // which should be the only canvas element
  container: myCanvas.get(0),
    width: 800,
    height: 500
  });
   
var myLayer = new Kinetic.Layer({id: “myLayer”});
stage.add(myLayer);
var circle = new Kinetic.Ellipse({
  // Set the position of the circle
  x: 100,                                            
  y: 100,
   
  // Set the size of the circle
  radius: {x: 200, y: 200},
   
  // Set the color to red
  fill: '#FF0000'  
});
 
myLayer.add(circle);
stage.draw();

Sie müssen die letzte Zeile von Abbildung 1 jedes Mal aufrufen, wenn die Szene neu gezeichnet werden soll. KineticJS erledigt den Rest, indem es das Layout der Szene in Erinnerung behält und gewährleistet, dass alles richtig gezeichnet wird.

Es gibt einige interessante Elemente in KineticJS, durch die es recht leistungsstark wird. Die "Fill"-Eigenschaft eines Objekts kann z. B. viele verschiedene Dinge abbilden, so auch einen Farbverlauf:

fill: {
  start: {x: 0, y: 0},
  end: {x: 0, y: 200},
  colorStops: [0, '#FF0000', 1, '#00FF00']
},

Oder ein Bild:

// The "Image" object is built into JavaScript and
// Kinetic knows how to use it
fillPatternImage: new Image('path/to/an/awesome/image.png'),

KineticJS hat auch ein Animationssystem, mit dessen Hilfe Sie durch Erstellen eines "Animation"-Objekts Elemente verschieben können. Oder Sie können mit einem "Tween"-Objekt Übergänge für Eigenschaften für die Formen in Ihrer Szene einrichten. Abbildung 2 zeigt beide Arten von Animationen.

Abbildung 2: Animationen mithilfe von KineticJS

// Slowly move the circle to the right forever
var myAnimation = new Kinetic.Animation(
  function(frame) {
    circle.setX(myCircle.getX() + 1);
  },
  myLayer);
 
// The animation can be started and stopped whenever
myAnimation.start();
// Increase the size of the circle by 3x over 3 seconds
var myTween = new Kinetic.Tween({
  node: circle,
  duration: 3,
  scaleX: 3.0,
  scaleY: 3.0
});
 
// You also have to initiate tweens
myTween.play();

KineticJS ist leistungsfähig und weit verbreitet, insbesondere für Spiele. Sehen Sie sich Code, Beispiele und Dokumentation unter kineticjs.com an.

Paper.js Paper.js bietet mehr als bloß eine Bibliothek zum Vereinfachen des Zeichnens auf der Canvas. Es bietet eine leicht abgeänderte Version von JavaScript namens PaperScript zur Vereinfachung allgemeiner Zeichenaufgaben. Wenn Sie PaperScript Ihrem Projekt hinzufügen, verknüpfen Sie es wie ein normales Skript, jedoch mit einem anderen Typ von Code:

<script type=“text/paperscript" src=“mypaperscript.js”>

Dadurch kann Paper.js den Code etwas anders interpretieren. Paper.js zeichnet sich durch nur zwei Merkmale aus. Erstens verfügt PaperScript über zwei integrierte Objekte, die "Point" und "Size" heißen. PaperScript enthält diese Objekte für die allgemeine Verwendung in seinen Funktionen und bietet die Möglichkeit, diese Typen direkt hinzuzufügen, zu subtrahieren und zu multiplizieren. Um beispielsweise ein Objekt in PaperScript umher zu schieben, können Sie dies angeben:

var offset = new Point(10, 10);
 
var myCircle = new Path.Circle({
  center: new Point(300, 300),
  radius: 60
});
 
// Direct addition of Point objects!
myCircle.position += offset;

Zweitens reagiert Paper.js anders auf Ereignisse. Angenommen, Sie schreiben den folgenden Code in JavaScript:

function onMouseDown(event) {
  alert("Hello!");
}

Es geschieht nichts, da die Funktion nicht an die Ereignisse von Elementen gebunden ist. Wenn sie allerdings denselben Code in PaperScript schreiben, erkennt Paper.js automatisch diese Funktion und bindet sie an das Maus-nach-unten-Ereignis. Unter paperjs.org finden Sie dazu weitere Informationen.

Fabric.js Fabric.js ist eine mit vielen Features ausgestattete Canvas-Bibliothek mit der Möglichkeit, verschiedene erweiterte Effekte und Formen ohne viel Code in eine Webseite zu integrieren. Einige wichtige Merkmale sind Bildfilter wie z. B. Freistellen, benutzerdefinierte Klassen für das Erstellen eigener zusammengesetzter Objekte und Unterstützung für das Freihandzeichnen, bei den Sie einfach in verschiedene Formaten auf der Leinwand zeichnen. Fabric.js ähnelt KineticJS dahingehend, dass ein Szenengraph geboten wird, der allerdings eine präzisere Struktur besitzt, was mitunter bevorzugt wird. Beispielsweise müssen Sie nie mehr die Szene neu zeichnen:

var canvas = new fabric.Canvas('myCanvas');
var circle = new fabric.Circle({
  radius: 200,
    fill: '#FF0000',
    left: 100,
    top: 100
});
 
// The circle will become immediately visible
canvas.add(circle);

Das ist kein großer Unterschied, doch Fabric.js bietet differenzierte Rendersteuerelemente, die automatisches Neuzeichnen und manuelles Neuzeichnen mischen. Das Skalieren eines Kreises in Fabric.js sieht beispielsweise so aus:

circle.animate(
  // Property to animate
  'scale',
  // Amount to change it to
  3,
  {
    // Time to animate in milliseconds
    duration: 3000,
    // What's this?
    onChange: canvas.renderAll.bind(canvas)
  });

Wenn Sie etwas in Fabric.js animieren, müssen Sie angeben, was geschehen soll, wenn ein Wert geändert wird. Meistens soll die Szene neu gezeichnet werden. Das ist, worauf "canvas.renderAll.bind(canvas)" verweist. Dieser Code gibt eine Funktion zurück, die die gesamte Szene rendert. Wenn Sie viele Objekte auf diese Weise animieren, würde die Szene jedoch unnötigerweise einmal für jedes Objekt neu gezeichnet werden. Sie können stattdessen das Neuzeichnen der gesamten Szene unterdrücken und die Animationen selbst neu zeichnen. Abbildung 3 veranschaulicht diesen Ansatz.

Abbildung 3: Strengere Steuerung des Neuzeichnens in Fabric.js

var needRedraw = true;
 
// Do things like this a lot, say hundreds of times
circle.animate(
  'scale',
  3,
  {
    duration: 3000,
       
    // This function will be called when the animation is complete
    onComplete: function() {
      needRedraw = false;
    }
  });
 
// This function will redraw the whole scene, and schedule the
// next redraw only if there are animations going
function drawAnimations() {
  canvas.renderAll();
  if (needRedraw) {
    requestAnimationFrame(drawAnimations);
  }
}
 
// Now draw the scene to show the animations
requestAnimationFrame(drawAnimations);

Fabric.js bietet viele Anpassungsmöglichkeiten, sodass Sie Ihre Zeichnung nur bei Bedarf optimieren können. Einige können nur schwer damit umgehen. Doch bei vielen komplexen Spielen kann dies ein wichtiges Merkmal sein. Weitere Informationen finden Sie unter fabricjs.com.

Raphaël Raphaël ist eine nützliche SVG-Bibliothek, die einen Großteil der Komplexität beim Arbeiten mit SVG beseitigt. Raphaël verwendet SVG, sofern verfügbar. Falls nicht, wird SVG von Raphaël in JavaScript mithilfe von Technologien implementiert, die im Browser verfügbar sind. Jedes in Raphaël erstellte grafische Objekt ist auch ein DOM-Objekt mit allen Funktionsmöglichkeiten, durch die sich DOM-Objekte auszeichnen, z. B. Ereignishandler für Bindungen und jQuery-Zugriff. Raphaël bietet auch ein Animationssystem, mit dem Sie Animationen unabhängig von den gezeichneten Objekten definieren können, was eine umfassende Wiederverwendung ermöglicht:

var raphael = Raphael(0, 0, 800, 600);
 
var circle = raphael.circle(100, 100, 200);
circle.attr("fill", "red");
circle.animate({r: 600}, 3000);
 
// Or make a custom animation
var myAnimation = Raphael.animation(
  {r: 600},
  3000);
circle.animate(myAnimation);

In diesem Code platziert Raphaël, anstatt einen Kreis zu zeichnen, ein SVG-Dokument mit einem Kreiselement auf einer Seite. Seltsamerweise unterstützt Raphaël systemeigen nicht das Laden von SVG-Dateien. Für Raphaël gibt es eine große Community. Ein Plug-In finden Sie unter bit.ly/1AX9n7q.

Snap.svg Snap.svg sieht fast so aus wie Raphaël:

var snap = Snap("#myCanvas"); // Add an SVG area to the myCanvas element
var circle = snap.circle(100, 100, 200);
circle.attr("fill", "#FF0000");
circle.animate({r: 600}, 1000);

Einer der wichtigsten Unterschiede ist, dass Snap.svg nahtlosen SVG-Import bietet:

Snap.load("myAwesomeSVG.svg");

Der zweite wesentliche Unterschied ist, dass Snap.svg leistungsstarke integrierte Tools zum Durchsuchen und Bearbeiten der vorhandenen SVG-Struktur bietet, wenn Sie die SVG-Struktur kennen, mit der Sie arbeiten. Angenommen Sie, Sie möchten alle Gruppen ("g"-Tags) in SVG unsichtbar machen. Nachdem SVG geladen wurde, müssen Sie diese Funktionalität einem Rückruf der "Load"-Methode hinzufügen:

Snap.load("myAwesomeSVG.svg", function(mySVG) {
  mySVG.select("g").attr("opacity", 0);
});

Die "select"-Methode funktioniert nahezu wie der jQuery-Selektor "$" und ist recht leistungsstark. Weitere Informationen zu Snap.svg finden Sie unter snapsvg.io.

Ein wenig mehr: p5.js

Viele dieser Bibliotheken bieten Extras für allgemeine Aufgaben. Dadurch entsteht ein Spektrum an Technologien für eine Vielzahl von Anwendungen – von der einfachen Zeichnung über interaktive Medien bis zu komplexen Spielerlebnissen. Was gibt es sonst noch in der Mitte des Spektrums, das etwas mehr ist als eine einfache Zeichenlösung ist, aber noch keine komplette Spiel-Engine?

Ein erwähnenswertes Projekt ist p5.js, das auf der beliebten Programmiersprache Processing (siehe processing.org) basiert. Diese JavaScript-Bibliothek bietet eine interaktive Medienumgebung durch die Implementierung von Processing im Browser. p5.js fasst die häufigsten Aufgaben in einer Gruppe von Funktionen zusammen, um die Reaktion auf Ereignisse im System, wie z. B. Neuzeichnen der Szene oder Mauseingabe, zu definieren. Der Funktionsumfang entspricht dem von Paper.js, aber mit zusätzlichen Multimediabibliotheken. Hier ist ein Beispiel, in dem veranschaulicht wird, wie dieser Ansatz zu präziserem Grafikcode führt:

float size = 20;
function setup() {
  createCanvas(600, 600);
}
 
function draw() {
  ellipse(300, 300, size, size);
  size = size + .1;
}

Dieses Programm erstellt einen Kreis, der so lange wächst, bis er den Bildschirm ausfüllt. Weitere Informationen zu p5.js finden Sie unter p5js.org.

Was also verwenden?

Canvas und SVG haben eindeutige Vor- und Nachteile. Es gibt auch Bibliotheken, die viele der Nachteile beider Ansätze erheblich verringern. Was sollten Sie also wählen? Standard-HTML empfiehlt sich im Allgemeinen nicht unbedingt. Es ist wahrscheinlich, dass ein modernes Spiel die unterstützte grafische Komplexität überschreiten wird. Dadurch reduzieren sich unsere Optionen auf SVG und Canvas, was die Sache nicht einfacher macht.

Für anspruchsvollere Spielgattungen ist die Antwort etwas einfacher. Wenn Sie ein Spiel mit Hunderten oder Tausenden von Partikel entwickeln, sollten Sie die Canvas verwenden. Wenn Sie ein Point-and-Click-Abenteuerspiel im Comic-Stil erstellen, empfiehlt es sich, SVG in Betracht zu ziehen.

Bei den meisten Spielen spielt letztlich nicht Leistung die große Rolle, wie viele Sie glauben machen wollen. Sie können Stunden damit verbringen, die zu verwendende Bibliothek auszuwählen. Letzten Endes ist dies jedoch Zeit, die Sie mit dem Schreiben des Spiels verbringen könnten.

Meine Empfehlung ist, eine Auswahl basierend auf den verfügbaren Grafikressourcen zu treffen. Wenn Sie die Zeichenanimationen in Adobe Illustrator oder Inkscape erstellen, warum dann jedes Einzelbild Ihrer Animationen in Pixel umwandeln? Verwenden Sie die systemeigenen Vektorgrafiken. Verschwenden Sie nicht Ihre ganze Arbeit, indem Sie die Canvas mit Grafiken vollpacken.

Wenn jedoch Ihre Grafik größtenteils pixelbasiert ist oder Sie komplexe Effekte auf Pixel-für-Pixel-Basis generieren wollen, ist die Canvas die ideale Option.

Eine weitere Option

Wenn Sie die bestmögliche Leistung wünschen und bereit sind, dafür etwas mehr Komplexität in Kauf zu nehmen, empfehle ich ausdrücklich Pixi.js. Im Gegensatz zu allen anderen Lösungen, die ich in diesem Artikel vorgestellt habe, verwendet Pixi.js für das 2D-Rendering WebGL. WebGL bietet einige wesentliche Leistungsoptimierungen.

Die API ist nicht ganz so einfach, wie die anderen hier beschriebenen, doch der Unterschied ist nicht gewaltig. Darüber hinaus wird WebGL nicht von so vielen Browsern unterstützt wie die anderen Technologien. Auf älteren Systemen bringt Pixi.js also keinen Leistungsvorsprung. So oder so müssen Sie eine Wahl treffen. Viel Spaß dabei!


Michael Oneppo ist Kreativtechnologe und früherer Programmmanager im Direct3D-Team bei Microsoft. In den letzten Jahren engagierte er sich als technischer Direktor in der gemeinnützigen Einrichtung "Library For All" und arbeitete an einem Master-Abschluss im "Interactive Telecommunications Program" der Universität von New York (NYU).

Unser Dank gilt den folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: Shai Hinitz