Tools zur Spieleprogrammierung

Nach meiner Recherche ist folgendes Setup die ideale Ausgangsbedingung um komplexere Computerspiele zu programmieren. Als IDE empfielt sich der Texteditor Geany. Gegenüber veralteten Systemen wie Emacs und VIM ist er sehr viel leichter zu benutzen, und gegenüber modernen Entwicklungsumgebungen wie Eclipse oder Visual C++ kann er durch seine Schlankheit punkten. Als Programmiersprache für den Einstieg und zur Prototypenentwicklung kommt nur Python in Frage. Keine andere Sprache kann man leichter einsetzen und keine andere Sprache macht in der täglichen Arbeit weniger Stress. Besonders in Kombination mit Box2D und pygame lassen sich damit erste Prototypen in extrem wenig Lines of Code erzeugen.

Wenn das Spiel dann einer größeren Community zugänglich gemacht werden soll, es performancemäßig Optimierungsbedarf gibt sowie das Programm sich insgesamt runder anfühlten soll, empfielt sich ein Wechsel auf CSFML. Damit ist das C-Binding der bekannten SFML Library gemeint. Die Anleitung dafür findet sich etwas versteckt wenn man bei Google nach „example “ sucht. Darüber findet sich Beispielcode, der explizit das C-Binding thematisiert und zeigt wie das mit den Pointern geht um ein Fenster zu initialisieren. Mit CSFML hat man eine gute Basis um leistungsfähigen Code zu schreiben, der auch professionellen Ansprüchen genügt. Also die Hardware gut ausgelastet und die maximale Performance ermöglicht.

Nicht vergessen sollte man die Erstellen einer Dokumentation. Am besten lädt man diese auf Academia.edu hoch weil es aktuell noch einen Mangel gibt, an wissenschaftlich aufbereiteten Texten in Sachen Spieleentwicklung. Gleichzeitig erhält man eine verlinkbare URL die mit etwas Glück irgendwann von anderen Autoren sogar zitiert wird.

Viel Erfolg.

Charakter-Animation mit Behavior Trees

Obwohl die Robotik-Wissenschaft und die Spieleentwicklung vordergründig mit den selben Problemen beschäftigt sind (Motion Planning, Animation, Künstliche Intelligenz) findet ein Austausch nur selten statt. Dies hat etwas mit dem Selbstverständnis der jeweiligen Disziplinen zu tun: die Robotik-Wissenschaft ist traditionell an den Hochschulen in den Fächern Informatik und Ingineurwissenschaften angesiedelt. Dementsprechend Mathematiklastig geht man dort auch die Problemlösung heran. Währenddessen die Spieleindustrie eher im Bereich Kreativität / Entertainment / Rummelplatz angesiedelt ist und zu Mathematik dementsprechend auf Abstand geht.

Nur, will man Roboter bauen, so kommt man mit Schubladendenken nicht weiter. Denn interessanterweise hat die Spieleindustrie schon seit längerem das entwickelt, wonach die Robotik-Wissenschaft so angestrengt sucht: eine Methode wie man Roboter steuert. Bei der Spieleindustrie geht es zwar weniger um Robotik, sondern dafür um Charakter-Animation, das Problem ist jedoch dasselbe. Es geht darum, innerhalb einer Physik-Engine die Bewegungsplanung von humanoiden Charakteren durchzuführen. Und die Lösung darauf ist simpel: es wird ein Behavior Tree verwendet, der die unterschiedlichen Motion-Capture Patterns ausführt. Wenn also der Spieler auf „Springen“ drückt, wird die Sequenz A und wenn er auf Laufen drückt, die Sequenz B abgespielt. Fortgeschrittene Behavior based Animation kann zusätzlich auch noch die Charakterbewegung an den Untergrund anpassen. Im Grunde benötigt auch die Robotik-Wissenschaft diese Technologie. Die Lösung für das Motion Planning Problem besteht darin, eine Datei mit rund 100 kb Sourcecode zu erstellen, was verschiedene Verhaltensweisen das Charakters mitsamt den auszuführenden Motion Patterns definiert.

Und obwohl eigentlich alle ahnen, dass genau das die Zukunft ist, wird dieses Konzept in der Praxis nur sehr rudimentär umgesetzt. Beispielsweise wurde bei den Robotern Nao oder AIBO jeweils ein solcher Behavior Tree einprogrammiert der bestimmte Patterns ausführen konnte. Das ganze wurde nicht als echte Künstliche Intelligenz verstanden, sondern nur als Behelfslösung, weil man eben keine bessere Methode zur Verfügung hatte, wie man das Spielzeug lebendiger erscheinen lassen kann. Nur, das Rezept stimmt eigentlich, lediglich die Dosierung war zu gering. Wenn man lediglich eine 10 kb Datei mit einem Behavior Tree definiert um darin dann 5-6 Verhaltensmuster vorzugeben, dann ist es wenig verwunderlich, dass der Spielzeugroboter anschließend sehr unbeholfen wirkt. Aber daraus ableiten, das läge an der Programmiertechnik ist falsch. Vielmehr muss man das Konzept nur nach oben skalieren, also den Behavior Tree komplexer gestalten und mehr Patterns einbauen.

Natürlich kann man für die simpelste als Motion Probleme (das Gehen geradeaus) eine sehr preiswerte Lösung ersinnen, und dafür exakt einen Motion Pattern definieren der abgespielt wird. Sowas ist schnell implementiert und verbraucht 5 kb an Speicher (wenn überhaupt). Nur, jemanden beeindrucken kann man damit niemanden. Wenn der Roboter an ein Hinderniss gelangt, wird ihn dieses Motion Pattern nicht darüberhinweg befördern. Sobald man sich jedoch etwas mehr anstrengt und das Problem „Gehen geradeaus“ aufmerksamer analysiert wird man in aller Regel eine Lösung finden, die auch für komplexe Szenarien ausreichend ist. Damit ist nicht automatisch gleich eine komplette kognitive Architektur gemeint, wo man Modelle erstellt, sondern damit ist im einfachsten Fall lediglich gemeint, dass man umfangreicheren Sourcecode erstellt, der so vielfältig ist, dass es dem Betrachter gar nicht auffällt, dass es sich dabei um precomputed Scripts handeln könnte.

Zur Verdeutlichung vielleicht ein konkretes Beispiel: der Roboter Nao besitzt im Auslieferungszustand exakt einen Pattern um aufzustehen. Dieser führt dazu, dass Arme und Beine einen bestimmten vordefinierten Ablauf ausführen der dazu führt, dass Nao aufsteht. Das ist schonmal besser als nichts, aber noch stark verbesserungswürdig. Was spricht beispielsweise dagegen, rein von der Quanität her nachzubessern und die Anzahl möglicher „Steh-Auf-Patterns“ auf 10 zu erhöhen? Auch wenn man sich einmal überlegt, dass es durchaus Sinn macht (je nachdem wie Nao gerade hingefallen ist) unterschiedliche Aufstehmuster einzusetzen.

Natürlich bedeutet die simple Erhöhung der Anzahl nicht, dass Nao dadurch klüger wird. Denn es wird ja eben keine bessere Software eingesetzt sondern das selbe Prinzip wird lediglich von der Quanität her erweitert. Aber, exakt diese Strategie ist zumindest in der Spieleindustrie erfolgreich, wo ebenfalls kein besseres Verfahren bekannt, aber durch simplen Fleiß die Charaktere dennoch realistisch animiert wirken.

In der Robotik ist es geradezu eine Manie geworden, die üblichen Motion Capture Techniken zu kritisieren und vermeintlich bessere Lösungen zu verwenden die auf maschinellem Lernen, stochastischen Algorithmen oder neuronalen Netzen basieren. Das wird dann mit der Notwendigkeit begründet, man bräuchte allgemeinere Verfahren die eine Art von Bootstrapping erlauben hin zu denkenden Robotern. Leider funktioniert davon nichts. Besser ist es, ganz simpel Motion Capture zu betreiben, die Daten in den Roboter zu übertragen und dann entsprechend der Umgebung einfach den passenden Pattern abzuspielen. Und zwar wirklich so simpel, dass bei Hinderniss 1, das Pattern 1 eingesetzt wird, bei Hinderniss 2 das Pattern 2 usw. Vorgesetzt man hat rund 100 unterschiedliche Motion Patterns um ein Hinderniss zu überwinden, so kann man damit bereits einen sehr lebensechten Nao Roboter kreieren. Im Grunde kann man hier auf eine Abstrahierung hin zu Modellen oder ähnliches verzichten, sondern sich einfach darauf beschränken einen möglichst umfangreichen Behavior Tree zu entwickeln.

Ein klein wenig wird dieses Konzept im Bereich der kognitiven Robotik aufgegriffen, die sich nicht zu schade ist, sogenannte Grasp-Databases zu erstellen, was im Grunde nichts anderes ist als eine Sammlung von 100 Möglichkeiten einen Ball zu greifen. Derartige Ansätze zeichnen sich dadurch aus, dass hier mathematische Verfahren keine Rolle spielen, es wird also nicht der Versuch unternommen das Problem über Formeln zu beschreiben .

3D Engine für Python

Bisher habe ich unter Python mit pygame+Box2d gearbeitet und war damit im Großen und Ganzen zufrieden. Mit relativ wenig Code konnte man eine Szene erzeugen, die sogar über eine Physik verfügte. Leider häuften sich in letzter Zeit die Mängel. Zum einen reagiert Box2D bei mehreren Ground-Objekten etwas hackelig in der Steuerung und zum zweiten ist sowohl pygame als auch Box2D nur in 2d erhältlich.

Nur zur Probe habe ich mal über den Tellerrand geblickt und panda3d entdeckt. Installiert wird das über „sudo pip install panda3d“ und ein Demoprogramm gibt es auf https://www.panda3d.org/manual/index.php/Loading_and_Animating_the_Panda_Model Wenn man das Demoprogramm startet sieht man eine Bären in 3d laufen und die Kamera bewegt sich um ihn herum. Für die wenigen Lines of Code eine beachtliche Leistung. Und ruckelfrei ist das ganze auch. Ob ich wirklich zu panda3d wechseln werde ist noch unklar, aber ein wenig rumspielen kann nicht schaden.

UPDATE
Leider hat sich herausgestellt dass Panda3D nicht praxixtauglich ist. Die Game-Engine ist überwiegend auf das Anzeigen von schicken Texturen und Lichtverhältnissen spezialisiert. Dazu gibt es die meisten Tutorials. Wenn man so simple Dinge tun will wie einfach eine Linie zeichnen oder zwei Boxen mit einem Joint zu koppeln muss man schon das Forum fragen. Und dort entdeckt man dann dass in Panda3D gleich mehrere Physik-Engines (Panda-original, ODE, Bullet) integriert sind die sich alle unterschiedlich konfigurieren lassen. Kurz gesagt, Panda3D ist einerseits an Funktionen überladen auf der andere Seite gibt es weniger als bei Box2D. Es hat schon seine Gründe warum sich Box2D so großer Beliebtheit erfreut obwohl die Library viele Schwächen besitzt.

UPDATE2
Was ist die Alternative zu Panda3D? Diese lautet, dass man sich selber eine Physik-Engine hernimmt wie pybullet oder pyode und ontop dann eine Graphic-Engine drauflegt. Leider gibt es das Problem, dass sich pyode unter Ubuntu nicht installieren lässt. Es gibt eine Compiler-Fehlermeldung, und zu pybullet gibt es keine gute Doku. Aber was ist gibt, ist eine Ode-Doku unter Panda3d https://www.panda3d.org/manual/index.php/Collision_Detection_with_ODE#Example Das abgedruckte Beispielprogramm hat zwar nichts mehr mit Panda3D zu tun ist aber dafür erfrischend simpel aufgebaut. Und das beste ist, es läuft reibungslos durch, man sieht nach dem Starten eine Fläche auf die würfel hinabpurzeln. Ist also ODE unter panda3D eine Art von Geheimtipp wie man leicht und ähnlich wie in Box2D komplexe Animationen erstellen kann? Derzeit kann ich das nicht sagen, dafür ist es noch zu früh.

Der eigentliche Grund dennoch bei Box2D zu bleiben hat weniger etwas mit panda3d zu tun als vielmehr mit dem Umstand dass Spieleprogrammierung in 2D um sehr vieles leichter ist. Man denke nur mal an einen Pathplanning Algorithmus wie PRM. Wollte man den in 3D realisieren muss man schon ziemlich fit sein. Kurz gesagt, fürs erste bietet 2D genug Herausforderung, auch wenn natürlich 3D sehr reizvoll ist.

3D Grafik mit Python

Schaut man einmal um, was laut Google Scholar eine gute Programmiersprache zur Spieleentwicklung ist, so wird dort von der überwältigenden Mehrheit der Autoren die Meinung vertreten, dass C++ das Nonplusultra ist. Die Anzahl der Lehrbücher ist groß, es gibt sogar welche die auf Deutsch verfasst sind und in den letzten 2 Jahren geschrieben wurden. Leider offenbart sich beim Blick in derartige Fachliteratur das blanke Grauen. Der Neueinsteiger wird mit dem vollen Sprachumfang von C++ traktiert. Polymorphe Vererbung, Pointer und copy-Konstruktoren werden bereits im Einleitungskapitel diskutiert auf die dann im weiteren Verlauf aufgebaut wird. Weiterhin heißt es, dass gute Spiele wenigstens aus 10000 Lines of Code in C++ bestehen und alles darunter ist die Amateurliga.

Ganz unrecht haben die Autoren nicht, und was in den Büchern erläutern ist durchweg korrekt. Aber, die Einstiegshürde ist viel zu hoch, auf diese Weise ein Spiel zu entwickeln ist für Leute außerhalb der Spielebranche nicht möglich. Die nach meiner Ansicht nach bessere Wahl ist die Programmiersprache Python. Es gibt dort zwar nicht soviele Bücher die speziell für Spieleentwicklung unter Linux geschrieben wurden, aber immerhin wird pygame noch relativ ausführlich erläutert. Solange man 2D Plattformer Games entwickeln will kommt man mit Linux+pygame+eine Anleitung von Google Scholar schon ziemlich weit. Die Programmierung geht deutlich leichter von der Hand als mit C++ und das Ergebnis kann sich durchaus sehen lassen. Schwieriger wird es jedoch, wenn man 3D Spiele entwickeln möchte. Hier gibt es leider keine Anleitung.

Ganz unmöglich ist das jedoch nicht, es ist nur weniger gut dokumentiert. Als Basis sollte man den Begriff OpenGL kennen, es handelt sich dabei um einen Grafikstandard der in Linux GUIs wie GTK+ und Qt eingesetzt wird um 3D Inhalte anzuzeigen und der Zugriff hat auf die Grafikkarte. Um in OpenGL zu programmieren würde man normalerweise eine Game/Grafik-Engine einsetzen wie z.B. Unity3d. Die ist zwar auch für Linux verfügbar ist jedoch kostenpflichtig. Und OpenSource Alternativen wie Ogre3d, oder Panda3D sind leider keine gute Wahl. Sie sind grottenschlecht dokumentiert und selbst einfachste Dinge wie das Zeichnen eines 3d Cube auf den Bildschirms erfordert bereits sehr tiefe Kenntnisse. Es ist nicht komplett unmöglich, aber es ist nicht geeignet für Einsteiger.

Einen halbwegs leichten Zugang zu der Thematik „3D Grafik mit Python und Linux“ bietet der Blogpost https://pythonprogramming.net/opengl-rotating-cube-example-pyopengl-tutorial/ Dort werden zwei Dinge verwendet: pygame und pyopengl. Beides muss man in Ubuntu zunächst über den Paketmanager installieren, dann aber kann man das Beispielprogramm starten und erhält die folgende Grafik:

opengl

Der Vorteil bei diesem Ansatz ist, dass es sehr weit unten anfängt. Es werden Punkte im Raum definiert, zwischen diesen dann Edges (Linien) gezogen werden und zum Schluss wird in der pygame Gameloop das Objekt rotiert. Bemerkenswert ist vor allem, was in der Grafik alles nicht enthalten ist: es gibt keine animierten Charaktere, keine Physik-Engine, keine Mouse-Listener, keine Texturen und keine Lichtquellen. Stattdessen wird einem minimalistischen Ansatz bei dem wirklich nur ein 3d Cube angezeigt wird und sonst gar nichts. Gerade für Einsteiger ist das ein guter Zugang, weil klar ist wie man das System erweitern muss, wenn man ein Spiel daraus machen möchte.

Von der Performance gibt es keine Probleme. Obwohl Python als Programmiersprache genutzt wird, ist dank OpenGL eine hohe Framerate möglich. Auch die CPU Auslastung geht bei der 3D Darstellung nicht auf 100% hoch, sondern ist kaum wahrnehmbar.

Sourcecode three.js

<!doctype html>
<html> Splines, http://www.gingerleprechaun.com/javascript/threejs-tween-along-motion-path<br>
http://stackoverflow.com/questions/18578249/three-js-splinecurve3-without-round-edges-or-linecurve3-replacement
<head>
	<meta charset="utf-8">
  <title>Spline following</title>
</head>

<body>

<script src="three.min.js"></script> 
<script> // Our Javascript will go here. 

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500);
var renderer = new THREE.WebGLRenderer();
var geometry = new THREE.CubeGeometry(.5,.5,.5);
var material = new THREE.MeshBasicMaterial({
  color: 0x01ff00f, 
  wireframe: true, 
  wireframeLinewidth: 2
});
var materialCube = new THREE.MeshBasicMaterial({
  color: 0x00aa30, 
  wireframe: false, 
  wireframeLinewidth: 2
});



var cube = new THREE.Mesh(geometry, materialCube);
var cube2 = new THREE.Mesh(geometry, materialCube);

// Spline Definition
// ----------------------------------------
 
// spline = new THREE.ClosedSplineCurve3([
spline = new THREE.SplineCurve3([
   new THREE.Vector3(0, 10, 0),
   new THREE.Vector3(2, 9, 0),
   new THREE.Vector3(5, 11, 0),
   new THREE.Vector3(6, 10, 0),
   new THREE.Vector3(8, 9.5, 0),
]);
spline2 = new THREE.SplineCurve3([
   new THREE.Vector3(0, 5, 0),
   new THREE.Vector3(3, 3, 0),
   new THREE.Vector3(4, 6, 0),
   new THREE.Vector3(7, 2, 0),
   new THREE.Vector3(8, 5, 0),
]);

var numPoints = 100;
var splinePoints = spline.getPoints(numPoints);
var spline2Points = spline2.getPoints(numPoints);
var geometry3 = new THREE.Geometry(); 
var geometry4 = new THREE.Geometry();
 
for(var i = 0; i < splinePoints.length; i++){
    geometry3.vertices.push(splinePoints[i]);  
    geometry4.vertices.push(spline2Points[i]);  
}

var line2 = new THREE.Line(geometry3, material);
var line3 = new THREE.Line(geometry4, material);



// ----------------------------------------
var t = 0.0;

function render() {
  requestAnimationFrame(render);
//  cube.rotation.x += 0.01;
//  cube.rotation.y += 0.01;

  t = t + 0.001;
  cube.position = spline.getPointAt(t)
  cube2.position = spline2.getPointAt(t)
//  camera.position.z += 0.1;
  renderer.render(scene, camera);
};

// ----------------------------------------
// Animation
//var t = 0;
//function update(){
//    t = t + 0.05;
//    particle.position = spline.getPointAt(t)
//}



renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene.add(cube);
scene.add(cube2);
scene.add(line2);
scene.add(line3);

// camera.position.set(0, 0, 100);
camera.position.set(0, 0, 30);
camera.lookAt(new THREE.Vector3(10, 5, 0));

render();

</script>

</body>
</html>