Erhalten Sie Zugang zu diesem und mehr als 300000 Büchern ab EUR 5,99 monatlich.
Die größte Herausforderung bei der Entwicklung von Spielen ist die Komplexität der Software. Der Autor stellt in diesem Buch bewährte Patterns zusammen, die er über Jahre bei der Spieleprogrammierung zusammengestellt und eingesetzt hat. Die Patterns sind in Form unabhängiger Rezepte organisiert. Dabei werden die wichtigsten Probleme berücksichtigt wie z.B. das Schreiben einer stabilen Game Loop oder wie man den CPU-Chae nutzt, umd die Performance zu steigern. Außerdem erfahren Sie, wie man die klassischen Deisgn Patterns in Spielen einsetzen lassen.
Sie lesen das E-Book in den Legimi-Apps auf:
Seitenzahl: 509
Veröffentlichungsjahr: 2015
Das E-Book (TTS) können Sie hören im Abo „Legimi Premium” in Legimi-Apps auf:
Robert Nystrom
Übersetzung aus dem Amerikanischen von Knut Lorenzen
Bibliografische Information der Deutschen Nationalbibliothek
Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über <http://dnb.d-nb.de> abrufbar.
ISBN 978-3-95845-092-9
1. Auflage 2015
www.mitp.de
E-Mail: [email protected]
Telefon: +49 7953 / 7189 - 079
Telefax: +49 7953 / 7189 - 082
© 2015 mitp Verlags GmbH & Co. KG
Dieses Werk, einschließlich aller seiner Teile, ist urheberrechtlich geschützt. Jede Verwertung außerhalb der engen Grenzen des Urheberrechtsgesetzes ist ohne Zustimmung des Verlages unzulässig und strafbar. Dies gilt insbesondere für Vervielfältigungen, Übersetzungen, Mikroverfilmungen und die Einspeicherung und Verarbeitung in elektronischen Systemen.
Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften.
Authorized translation from the English language edition, entitled »Game Programming Patterns«, 978-0-9905829-0-8 by Nystrom, Robert, published by Genever Benning, Copyright © 2014 Robert Nystrom. All rights reserved.
Lektorat: Sabine Schulz
Sprachkorrektorat: Maren Feilen
Coverbild: Robert Nystrom
electronic publication: III-satz, Husby, www.drei-satz.de
Dieses Ebook verwendet das ePub-Format und ist optimiert für die Nutzung mit dem iBooks-reader auf dem iPad von Apple. Bei der Verwendung anderer Reader kann es zu Darstellungsproblemen kommen.
Der Verlag räumt Ihnen mit dem Kauf des ebooks das Recht ein, die Inhalte im Rahmen des geltenden Urheberrechts zu nutzen. Dieses Werk, einschließlich aller seiner Teile, ist urheberrechtlich geschützt. Jede Verwertung außerhalb der engen Grenzen des Urheherrechtsgesetzes ist ohne Zustimmung des Verlages unzulässig und strafbar. Dies gilt insbesondere für Vervielfältigungen, Übersetzungen, Mikroverfilmungen und Einspeicherung und Verarbeitung in elektronischen Systemen.
Der Verlag schützt seine ebooks vor Missbrauch des Urheberrechts durch ein digitales Rechtemanagement. Bei Kauf im Webshop des Verlages werden die ebooks mit einem nicht sichtbaren digitalen Wasserzeichen individuell pro Nutzer signiert.
Bei Kauf in anderen ebook-Webshops erfolgt die Signatur durch die Shopbetreiber. Angaben zu diesem DRM finden Sie auf den Seiten der jeweiligen Anbieter.
Für Megan, für Vertrauen und Zeit,
Ich habe mir sagen lassen, dass nur andere Autoren einschätzen können, was zum Schreiben eines Buches alles dazugehört. Allerdings gibt es da noch ein anderes Völkchen, das genau weiß, was für eine Belastung das ist – nämlich die Unglücklichen, die in einer Beziehung zum Autor stehen. Ich habe mir die Zeit zum Schreiben mühsam zusammenkratzen müssen, denn der Alltag ist für meine Frau Megan und mich eigentlich schon anstrengend genug. Abzuwaschen und die Kinder zu baden, ist zwar nicht gerade das, was man unter »Schreiben« versteht, aber ohne ihre Hilfe gäbe es dieses Buch nicht.
Als ich mit diesem Projekt begann, war ich bei Electronic Arts als Programmierer tätig. Ich denke, das Unternehmen wusste nicht so recht, was davon zu halten war, und so bin ich Michael Malone, Olivier Nallet und Richard Wifall für ihre Unterstützung und ihr detailliertes, aufschlussreiches Feedback zu den ersten paar Kapiteln umso dankbarer.
Nachdem etwa die Hälfte des Buches erledigt war, entschloss ich mich, auf die Veröffentlichung durch einen traditionellen Verlag zu verzichten. Mir war durchaus bewusst, dass mir damit auch die fachliche Unterstützung eines Lektorats fehlen würde, andererseits hatten mir jedoch schon Dutzende Leser gemailt, die keinen Zweifel daran ließen, in welche Richtung das Buch ihrer Meinung nach gehen sollte. Auch auf das Korrektorat musste ich verzichten, dafür hatte ich aber bereits mehr als 250 Fehlerberichte erhalten, die mir dabei halfen, den Text auf Vordermann zu bringen. Ich hatte es schnell aufgegeben, mich beim Schreiben an einen Zeitplan zu halten – doch wenn einem die Leser nach der Fertigstellung der einzelnen Kapitel auf die Schulter klopfen, ist das Ansporn genug!
Ich musste zwar auf ein verlagsseitiges Lektorat verzichten, nicht aber auf gute Korrekturleser. Lauren Briese war stets zur Stelle, wenn ich Hilfe brauchte, und hat ausgezeichnete Arbeitet geleistet.
Mein besonderer Dank gilt Colm Sloan, der jedes einzelne Kapitel zweimal überprüft hat und mir haufenweise fabelhaftes Feedback lieferte – nur weil er so ein großzügiger Mensch ist. Du hast ein Bier (oder zwanzig) bei mir gut!
Diese Art der Veröffentlichung wird als »Selbstverlag« oder »Self-Publishing« bezeichnet, obwohl der Begriff »Crowd-Publishing« der Wahrheit näher kommt. Schreiben kann man zwar auch alleine, ich selbst war allerdings niemals alleine. Sogar als ich die Arbeit am Buch zwei Jahre ruhen ließ, wurde ich weiterhin ermutigt, es fertigzustellen. Ohne all die Leute (Dutzende!), die mich immer wieder daran erinnerten, dass sie auf weitere Kapitel warteten, hätte ich das Buch wohl nicht zu Ende gebracht.
Ich möchte allen, die E-Mails geschrieben, Kommentare gesendet, getwittert, Fehler berichtet, Freunden von diesem Buch erzählt oder irgendwie Verbindung mit mir aufgenommen haben, sagen: Ich bin euch wirklich von ganzem Herzen dankbar. Dieses Buch fertigzustellen, gehörte zu meinen größten Wünschen. Und ihr habt dafür gesorgt, dass ich es geschafft habe. Danke!
Bob Nystrom, 6. September 2014
Kapitel 1
Architektur, Performance und Spiele
Im fünften Schuljahr standen in unserem Klassenzimmer ein paar ziemlich ramponierte TRS-80-Computer herum, die meine Freunde und ich benutzen durften. Ein Lehrer, der unsere Begeisterung wecken wollte, gab uns einen Stapel Ausdrucke einiger einfacher BASIC-Programme, mit denen wir experimentieren konnten.
Die Kassettenlaufwerke der Rechner waren defekt, d.h., wenn wir Code ausführen wollten, mussten wir ihn immer sehr sorgfältig komplett neu eingeben. Das führte dazu, dass wir bevorzugt auf Programme zurückgriffen, die nur einige wenige Zeilen lang waren:
10 PRINT "BOBBY IST EIN RADIKALER!!!" 20 GOTO 10Vielleicht wird es ja auf wundersame Weise wahr, wenn der Computer es nur oft genug ausgibt ...
Trotzdem war das Ganze Glückssache. Wir hatten keine Ahnung von Programmierung, und schon der kleinste Syntaxfehler war für uns unergründlich. Wenn ein Programm nicht funktionierte, und das kam des Öfteren vor, fingen wir einfach von vorne an.
Am unteren Ende des Papierstapels mit Ausdrucken fanden wir schließlich ein richtiges Ungeheuer: ein Programm, das aus mehreren eng mit Code bedruckten Seiten bestand. Es dauerte ein Weilchen, bis wir überhaupt den Mut aufbrachten, es einzugeben, aber es war einfach unwiderstehlich, denn der Titel am Anfang des Listings lautete »Tunnels & Trolls«. Wir hatten keinen blassen Schimmer, worum es bei dem Programm ging, aber es klang nach einem Spiel, und was könnte cooler sein als ein selbstprogrammiertes Computerspiel?
Leider haben wir es nie zum Laufen bekommen und im darauffolgenden Schuljahr zogen wir in ein anderes Klassenzimmer um. (Jahre später, nachdem ich etwas BASIC gelernt hatte, wurde mir klar, dass es sich seinerzeit gar nicht um ein Spiel gehandelt hatte. Das Programm erzeugte bloß verschiedene Persönlichkeiten für das eigentliche Rollenspiel.) Dessen ungeachtet waren die Würfel gefallen: Ich hatte mir in den Kopf gesetzt, Spieleprogrammierer zu werden.
Als ich ein Teenager war, schaffte sich meine Familie einen Macintosh mit QuickBASIC an, später kam dann THINK C dazu. Ich habe fast meine gesamten Sommerferien damit verbracht, Spiele zusammenzuhacken. Es war mühsam, auf mich allein gestellt zu lernen und es ging nur langsam vorwärts. Anfangs war es gar nicht so schwer und ich bekam schnell etwas zum Laufen – vielleicht eine Landkartendarstellung oder ein kleines Puzzle. Aber sobald die Programme etwas umfangreicher waren, wurde es immer schwieriger.
Die übrige Zeit verbrachte ich damit, in den Sümpfen des südlichen Louisianas Schlangen und Schildkröten einzufangen. Wenn es draußen nicht so glühend heiß wäre, könnte sich dieses Buch sehr wohl auch um Amphibien- und Reptilienforschung drehen, statt um Programmierung.
Zunächst bestand die Herausforderung nur darin, überhaupt etwas zum Funktionieren zu bekommen. Dann musste ich mir überlegen, wie ich Programme schreiben konnte, die aus mehr Zeilen bestanden, als ich mir gleichzeitig merken konnte. Anstatt Bücher wie »Programmierung in C« von Kernighan und Ritchie zu lesen, versuchte ich Fachliteratur zu finden, die sich mit dem Organisieren von Programmen befasste.
Zeitsprung. Mehrere Jahre später drückte mir ein Bekannter das Buch Design Patterns: Entwurfsmuster als Elemente wiederverwendbarer objektorientierter Software in die Hand. Endlich! Das war genau das Nachschlagewerk, nach dem ich seit meiner Teenagerzeit gesucht hatte. Ich las es in einem Rutsch von vorne bis hinten durch. Zwar hatte ich noch immer mit meinen eigenen Programmen zu kämpfen, aber mir fiel ein Stein vom Herzen, als ich begriff, dass andere Leute ähnliche Schwierigkeiten hatten und Lösungen dafür erarbeiteten. Ich hatte das Gefühl, dass ich statt »nur« meiner bloßen Hände nun endlich richtige Werkzeuge einsetzen konnte.
Den Bekannten, der mir das Buch gab, lernte ich bei dieser Gelegenheit übrigens gerade erst kennen. Fünf Minuten nachdem wir einander vorgestellt wurden, saß ich auf seinem Sofa und verbrachte die nächsten Stunden mit Lesen, wobei ich ihn vollkommen ignorierte. Ich hoffe allerdings, dass sich meine Sozialkompetenz seitdem doch ein wenig verbessert hat.
2001 trat ich meinen Traumjob an: Programmierer bei Electronic Arts. Ich konnte es kaum erwarten, mir die richtigen Spiele anzusehen und herauszufinden, wie die Profis sie entwickeln. Wie sieht wohl die Architektur eines so gigantischen Spiels wie Madden Football aus? Wie arbeiten die verschiedenen Systemkomponenten zusammen? Wie schaffen sie es, eine einzige Codebasis auf mehreren Plattformen zum Laufen zu bringen?
Als ich den Quellcode sah, war das eine demutsvolle und überraschende Erfahrung. Der Code zur Grafikerzeugung, die künstliche Intelligenz (KI), die Animationen und visuellen Effekte – all das war einfach brillant. Hier gab es Leute, die das letzte bisschen Leistung aus der CPU herauskitzeln und nutzen konnten. Sie erledigten Dinge, von denen ich gar nicht wusste, dass sie überhaupt möglich sind, schon vor der Mittagspause.
Aber der Architektur, in der sich dieser brillante Code befand, wurde kaum Beachtung geschenkt. Die Programmierer waren so sehr auf Features konzentriert, dass sie dabei die Organisation übersahen. Es wimmelte nur so von eng gekoppelten Modulen. Neue Features wurden der Codebasis da aufgepfropft, wo es gerade passte. Ernüchtert musste ich feststellen, dass es viele der Programmierer offenbar nicht weiter als bis zum Singleton-Pattern geschafft hatten – wenn sie das Buch Design Patterns denn überhaupt mal aufgeschlagen haben sollten.
Na ja, ganz so schlimm war es natürlich auch wieder nicht. Ich hatte mir allerdings ausgemalt, dass Spieleprogrammierer mithilfe von Wandtafelen wochenlang in aller Seelenruhe in einem Elfenbeinturm die architektonischen Einzelheiten ausdiskutierten – tatsächlich war der Code, den ich mir ansah, jedoch von Leuten geschrieben worden, die sich mit engen Abgabefristen konfrontiert sahen. Sie gaben wirklich ihr Bestes und das war, so dämmerte mir allmählich, oft sehr gut. Je länger ich mich mit der Arbeit am Gamecode beschäftigte, desto mehr brillante Codeschnipsel entdeckte ich, die sich unter der Oberfläche versteckten.
Leider war »versteckt« häufig eine allzu treffende Beschreibung. Es waren echte Juwelen im Code verborgen, die viele völlig übersahen. Ich habe mehr als nur einmal erlebt, dass andere Programmierer sich damit abmühten, Dinge neu zu erfinden, obwohl in der Codebasis, mit der sie arbeiteten, bereits gute Lösungen für genau die betreffende Fragestellung vorhanden waren.
Dieses Problem möchte das vorliegende Buch lösen. Ich habe die besten in der Spieleprogrammierung vorkommenden Patterns ausgewählt, geordnet und zusammengestellt, damit wir unsere Zeit damit verbringen können, Dinge tatsächlich neu zu erfinden, anstatt sie wieder zu erfinden.
Es gibt bereits Dutzende Bücher über Spieleprogrammierung – warum also ein weiteres schreiben? Die meisten mir bekannten Bücher zum Thema Spieleprogrammierung gehören einer der beiden folgenden Kategorien an:
Themenspezifische Bücher Diese Bücher sind weitestgehend auf einen bestimmten Aspekt der Spieleprogrammierung fokussiert, wie etwa 3D-Grafik, Rendern in Echtzeit, Physiksimulation, künstliche Intelligenz oder Audio. Das sind die Gebiete, auf die sich viele Spieleprogrammierer im Lauf ihrer Karriere spezialisieren.
Bücher über komplette Spiel-Engines Im Gegensatz zu der vorgenannten Kategorie versuchen diese Bücher, alle Bestandteile einer Engine abzuhandeln. Sie sind auf die Entwicklung einer kompletten Engine für ein bestimmtes Spielgenre ausgerichtet, meist 3D-Ego-Shooter.
Mir sagen die beiden Kategorien durchaus zu, aber ich denke, dass sie einige Lücken lassen. Themenspezifische Bücher erklären selten, wie der Code mit dem Rest des Spiels zusammenwirkt. Sie mögen vielleicht beim Rendern und der Physiksimulation ein alter Hase sein, aber wissen Sie auch, wie man beides vernünftig miteinander verbindet?
Dieser Bereich wird von der zweiten Kategorie zwar abgedeckt, ich finde die Bücher über komplette Spiel-Engines allerdings oft zu einseitig und genrebezogen. Mit dem Aufkommen von Smartphones und der damit einhergehenden Verbreitung mobiler Spiele und sogenannter Casual Games (einfache Spiele, die man bei Gelegenheit spielt) sind wir in einem Zeitalter angelangt, in dem viele verschiedene Spielegenres bedient werden. Wir klonen nicht mehr nur Quake. Bücher über Spiel-Engines helfen Ihnen nicht weiter, wenn Ihr Spiel nicht zur Engine passt.
Ich bemühe mich hier darum, die Themen eher wie auf einer Speisekarte zu präsentieren. Die einzelnen Kapitel im Buch beruhen auf jeweils unabhängigen Konzepten, die auf Ihren Code anwendbar sind. Auf diese Weise können Sie sie so zusammenstellen, wie es am besten zu dem Spiel passt, das Sie entwickeln möchten.
Ein weiteres Beispiel für diesen »Speisekarten-Ansatz« ist die hochgelobte Buchreihe Game Programming Gems.
Jedes Buch über Programmierung, dessen Titel das Wort »Patterns« enthält, steht in einer gewissen Beziehung zu dem Klassiker Design Patterns: Entwurfsmuster als Elemente wiederverwendbarer objektorientierter Software von Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides (die merkwürdigerweise als die »Gang of Four« bezeichnet werden).
Das Buch Design Patterns wurde seinerseits von einem anderen Buch inspiriert. Die Idee, Patterns zur Beschreibung ergebnisoffener Problemlösungen zu verwenden, entstammt dem Buch A Pattern Language von Christopher Alexander (das er zusammen mit Sarah Ishikawa und Murray Silverstein verfasst hat).
Darin geht es zwar um Architektur (also richtige Architektur, Gebäude, Wände usw.), die Autoren hatten jedoch die Hoffnung geäußert, dass die von ihnen beschriebenen Lösungsstrukturen auch auf andere Bereiche angewendet würden – und Design Patterns ist der Versuch der Gang of Four, diese Methode auf Software zu übertragen.
Mit dem Titel Design Patterns für die Spieleprogrammierung will ich keineswegs sagen, dass die Design Patterns der Gang of Four nicht auf Spiele anwendbar sind. Im Gegenteil: Das Kapitel Design Patterns neu überdacht stellt viele der klassischen Design Patterns vor, legt den Schwerpunkt dabei aber gezielt auf deren Anwendung in der Spieleprogrammierung.
Umgekehrt denke ich, dass dieses Buch auch auf Software anwendbar ist, bei der es sich nicht um Spiele handelt. Ich hätte ebenso gut einen Titel wie Weitere Design Patterns wählen können, ich finde aber, dass Spiele oft besonders ansprechende Beispiele ermöglichen. Sie wollen doch nicht wirklich schon wieder ein Buch über Angestelltendatensätze und Bankkonten lesen, oder?
Die hier vorgestellten Patterns sind nicht nur für andere Software nützlich, sie sind meiner Ansicht nach auch besonders gut für Probleme geeignet, die bei der Spieleprogrammierung häufig auftreten:
Zeit und Abläufe sind oftmals das Kernstück einer Spielarchitektur. Vorgänge müssen in der richtigen Reihenfolge und zum richtigen Zeitpunkt stattfinden.
Die Entwicklungszyklen folgen oft eng aufeinander und die Programmierer müssen in der Lage sein, schnell neue Versionen mit vielen verschiedenen neuen Verhaltensweisen zu entwickeln, ohne sich dabei gegenseitig im Weg zu stehen oder überall in der Codebasis Spuren zu hinterlassen.
Wenn die Verhaltensweisen festgelegt sind, folgen die Interaktionen. Monster beißen den Helden, Zaubertränke werden angerührt und Bomben sprengen Freund und Feind in die Luft. Solche Interaktionen müssen stattfinden können, ohne dass die Codebasis zu einem verworrenen Knäuel wird.
Und zu guter Letzt kommt der Performance bei Spielen eine maßgebliche Bedeutung zu. Spieleentwickler befinden sich in einem dauerhaften Wettstreit, um herauszufinden, wer das meiste aus einer Plattform herausholen kann. Tricks und Kniffe, mit denen sich einige CPU-Takte einsparen lassen, können den Unterschied zwischen einem hervorragend bewerteten, millionenfach verkauften und einem von Bildaussetzern geprägten sowie genervten Spieletestern verrissenen Spiel bedeuten.
Das vorliegende Buch ist in drei größere Teile gegliedert. Der erste (I) besteht aus der Einführung, die Sie gerade lesen, und dem folgenden Kapitel 1.
Der zweite Teil (II), Design Patterns neu überdacht, stellt einige der Entwurfsmuster aus dem Buch der Gang of Four vor. Ich lege in den einzelnen Kapiteln meine Ansichten zu den einzelnen Patterns dar und beschreibe, wie sie sich auf die Spieleprogrammierung anwenden lassen.
Danach folgt der Hauptteil des Buches (III bis VI), in dem 13 Design Patterns präsentiert werden, die sich als nützlich erwiesen haben. Sie sind in vier Kategorien unterteilt: Sequenzierungsmuster (III, Sequencing Patterns), Verhaltensmuster (IV, Behavioral Patterns), Entkopplungsmuster (V, Decoupling Patterns) und Optimierungsmuster (VI, Optimization Patterns). Die dazugehörigen Patterns werden jeweils auf einheitliche Art und Weise beschrieben, damit Sie das Buch als Referenz verwenden können und schnell finden, was Sie suchen:
Der einleitende Abschnitt erläutert kurz und knapp die Zielsetzung des Design Patterns und beschreibt, für welche Problemstellungen es gedacht ist. Diese Informationen sollen es Ihnen ermöglichen, das Buch bei der Suche nach einem Pattern, das bei der Lösung eines Problems hilfreich wäre, schnell durchforsten zu können.
Der Abschnitt Motivation beschreibt ein Anwendungsbeispiel des Patterns. Im Gegensatz zu konkreten Algorithmen ist ein Pattern mehr oder weniger formlos, solange es nicht auf ein bestimmtes Problem angewendet wird. Ein Pattern ohne ein Beispiel zu erklären, käme einem Backkurs gleich, in dem der Teig keine Erwähnung findet. Dieser Abschnitt liefert sozusagen den Teig, der in den nachfolgenden Abschnitten gebacken wird.
Der Abschnitt Das Pattern fasst die wesentlichen Charakteristiken und Eigenschaften des im vorausgegangenen Beispiel dargestellten Entwurfsmusters zusammen. Wenn Sie eine trockene, lehrbuchhafte Beschreibung des Patterns suchen, werden Sie hier fündig. Dieser Abschnitt kann auch zur Wissensauffrischung dienen, wenn Ihnen der Gebrauch eines Patterns schon geläufig ist und Sie sich vergewissern möchten, dass Sie nichts vergessen haben.
Bisher wurde das Pattern nur anhand eines einzigen Beispiels erläutert. Wie aber können Sie feststellen, ob es für Ihr Problem geeignet ist? Der Abschnitt Anwendbarkeit enthält einige Richtlinien dazu, wann die Verwendung des Patterns sinnvoll ist und wann man besser darauf verzichten sollte.
Der Abschnitt Konsequenzen weist auf mögliche Risiken bei der Anwendung des Patterns hin.
Wenn es Ihnen wie mir geht und Sie ein konkretes Beispiel brauchen, um etwas wirklich zu begreifen, sind Sie im Abschnitt Beispielcode richtig. Hier finden Sie eine schrittweise Implementierung des Patterns, damit Sie genau nachvollziehen können, wie es funktioniert.
Patterns unterscheiden sich von Algorithmen dadurch, dass sie ergebnisoffen sind. Wenn Sie ein Pattern mehrmals verwenden, werden Sie es vermutlich jedes Mal anders implementieren. Der Abschnitt Designentscheidungen geht dem auf den Grund und zeigt verschiedene Möglichkeiten bei der Anwendung des Patterns auf.
Und zum Abschluss wird in einem kurzen Abschnitt Siehe auch ... erklärt, in welcher Beziehung das Pattern zu anderen Patterns steht. Außerdem finden Sie hier Hinweise auf Open-Source-Code, der das Pattern in der Praxis einsetzt.
Die Codebeispiele in diesem Buch sind in C++ geschrieben, das heißt jedoch nicht, dass die Design Patterns nur in dieser Programmiersprache nützlich sind oder dass C++ besonders gut dafür geeignet wäre. Fast alle Programmiersprachen sind geeignet, allerdings setzen einige Patterns das Vorhandensein von Objekten und Klassen voraus.
Ich habe aus verschiedenen Gründen C++ gewählt. Zunächst einmal handelt es sich hierbei um die bei kommerziell vertriebenen Spielen verbreitetste Programmiersprache. C++ ist sozusagen die Lingua franca der Spielebranche. Außerdem bildet die C-Syntax, auf der C++ beruht, auch die Grundlage für Java, C#, JavaScript und viele andere Programmiersprachen. Selbst wenn Sie sich nicht mit C++ auskennen, stehen die Chancen daher nicht schlecht, dass Sie den Beispielcode mit ein klein wenig Mühe verstehen können.
Es ist nicht das Ziel dieses Buches, Sie C++ zu lehren. Die Beispiele sind so einfach wie möglich gehalten und sollen weder guten Programmierstil noch eine besondere Verwendungsart darstellen. Denken Sie beim Betrachten des Codes an das dahinterstehende Konzept, nicht an den Code, der es formuliert.
Der Code wurde insbesondere nicht im »modernen« Stil (C++11 oder neuer) verfasst. Die Standardbibliothek wird nicht verwendet und Templates nur selten. Der C++-Code ist zwar nicht besonders elegant, aber er ist kompakt und ich hoffe, dass er auf diese Weise für Leute, die Erfahrung mit C, Objective-C, Java oder anderen Sprachen haben, leichter verständlich ist.
Um Platz zu sparen, werde ich in den Beispielen hin und wieder Code weglassen, wenn Sie ihn schon kennen oder er für ein Pattern nicht relevant ist. Diese Stellen sind durch Auslassungspunkte gekennzeichnet.
Betrachten Sie beispielsweise eine Funktion, die bestimmte Aufgaben erledigt und dann einen Rückgabewert liefert. Für das Pattern ist nur der Rückgabewert von Bedeutung, nicht die Erledigung der Aufgaben. Der Beispielcode sieht dann folgendermaßen aus:
bool update() { // Aufgaben erledigen ... return isDone(); }Patterns sind ein Bestandteil der Softwareentwicklung, der einem ständigen Wandel und kontinuierlichen Erweiterungen unterliegt. Dieses Buch möchte den von der Gang of Four angestoßenen Prozess fortsetzen, die bislang entdeckten Patterns zu dokumentieren und mit anderen zu teilen. Und natürlich ist dieses Vorhaben mit dem vorliegenden Buch noch lange nicht abgeschlossen!
Sie selbst sind ebenfalls Teil dieses Prozesses. Wenn Sie Ihre eigenen Patterns entwickeln und die im Buch vorgestellten optimieren (oder sie ablehnen!), erweisen Sie der Community der Softwareentwickler ebenfalls einen Dienst. Sollten Sie Vorschläge haben, Fehler entdecken oder anderweitiges Feedback geben wollen, melden Sie sich bitte!
Bevor wir uns kopfüber in einen Haufen Patterns stürzen, ist es vermutlich hilfreich, wenn ich Ihnen an dieser Stelle zunächst kurz darstelle, wie ich persönlich über die Softwarearchitektur und deren Anwendung in der Spieleprogrammierung denke. Dadurch wird der Rest des Buches möglicherweise besser verständlich. Zumindest möchte ich Sie mit einigen schlagenden Argumenten bewaffnen, falls Sie demnächst in eine Diskussion hineingezogen werden, in der es darum geht, wie furchtbar (oder fabelhaft) Design Patterns sind.
Beachten Sie bitte, dass ich keine Vermutungen darüber anstelle, auf welcher Seite Sie bei einer solchen Diskussion stehen würden. Wie jeder Waffenhändler, beliefere ich gern alle Beteiligten ...
Wenn Sie dieses Buch von vorne bis hinten durchlesen, werden Sie anschließend weder die der 3D-Grafik zugrunde liegende lineare Algebra noch die für Physiksimulationen erforderliche Infinitesimalrechnung beherrschen. Es wird nicht erklärt, wie man einen binären Suchbaum rekursiv durchläuft oder wie man bei der Audiowiedergabe den Widerhall eines Raums simuliert.
Meine Güte, der letzte Absatz wäre ein wirklich furchtbarer Werbetext für das Buch!
Dieses Buch thematisiert den Code, der all die genannten Dinge miteinander verknüpft. Es geht weniger darum, den Code zu schreiben, sondern vielmehr darum, ihn zu organisieren. Jedes Programm ist in irgendeiner Form organisiert – selbst wenn es dabei nur nach der Devise »Stopf den ganzen Krempel in main() und guck, was passiert« zugeht. Interessanter ist die Frage, was denn eigentlich eine gute Organisation auszeichnet. Wie unterscheidet sich eine gute Architektur von einer schlechten?
Über diese Frage grüble ich seit etwa fünf Jahren nach. Natürlich verfüge ich, ebenso wie Sie, über ein gewisses Gespür für gutes Design. Aber wir alle haben doch schon so schlechte Codebasen gesehen, dass es das Beste gewesen wäre, sie zu löschen, um sie von ihrem Leiden zu erlösen.
Machen wir uns nichts vor: Die meisten von uns haben die eine oder andere davon sogar selbst fabriziert!
Einige wenige Glückliche haben aber auch die gegenteilige Erfahrung machen dürfen, mit wunderbar organisiertem Code zu arbeiten – der Sorte Codebasis, die sich anfühlt wie ein sorgfältig ausgewähltes Luxushotel voller Bediensteter, die eifrig darauf warten, den Gästen jeden Wunsch von den Augen abzulesen. Doch worin besteht dieser Qualitätsunterschied eigentlich?
Für mich bedeutet gutes Design, dass das gesamte Programm bestens für Änderungen, die ich gegebenenfalls daran vornehmen möchte, gerüstet ist. Ich kann eine Aufgabe durch einige wenige ausgewählte Funktionsaufrufe lösen, die sich problemlos einfügen lassen, ohne an der friedlich ruhenden Struktur des Codes zu kratzen.
Das klingt hübsch, ist aber tatsächlich kaum machbar. »Schreib den Code einfach so, dass Änderungen die ruhende Struktur des Codes unberührt lassen.« Jaja.
Lassen Sie mich das einmal etwas genauer aufschlüsseln. Entscheidend ist, dass es bei der Architektur um Änderungen geht. Irgendjemand muss die Codebasis ändern. Wenn der Code nicht geändert wird (sei es nun, weil er bereits perfekt ist, oder aber weil er so elendig ist, dass niemand seinen Texteditor damit besudeln möchte), ist das Design irrelevant. Ein Maßstab für die Qualität eines Designs ist die Einfachheit, mit der sich Änderungen vornehmen lassen. Eine Codebasis, an der keine Änderungen vorgenommen werden, ist wie ein Sprinter, der die Startlinie erst gar nicht verlässt.
Bevor Sie den Code ändern, um eine neue Funktion hinzuzufügen, einen Fehler zu beheben oder warum auch immer Sie Ihren Texteditor sonst gestartet haben mögen, müssen Sie verstehen, wie der schon vorhandene Code funktioniert. Es ist natürlich nicht nötig, das gesamte Programm zu kennen, Sie müssen aber durchaus alle relevanten Bestandteile in Ihrem »Primatenhirn« hinterlegen.
Es ist eine merkwürdige Vorstellung, dass es sich hierbei buchstäblich um eine Form der optischen Texterkennung handelt.
Dieser Schritt wird gern unter den Teppich gekehrt, obwohl er zu den zeitraubendsten Aufgaben der Programmierung überhaupt gehört. Wenn Sie der Ansicht sind, dass das Paging einiger Daten von der Festplatte ins RAM zu langsam abläuft, dann versuchen Sie mal, das mit ganzen zwei Sehnerven und einem menschlichen Großhirn zu bewerkstelligen.
Sobald Sie Ihre kleinen grauen Zellen mit den richtigen Inhalten gefüttert haben, denken Sie ein wenig nach und gelangen schließlich zu einer Lösung. Sie werden vermutlich eine Weile Hin- und Herüberlegen, meistens funktioniert das aber relativ unkompliziert – denn wenn Sie das Problem erst mal verstanden und die zugehörigen Stellen des Codes gefunden haben, ist die eigentliche Programmierung manchmal sogar trivial.
Also lassen Sie Ihre muskulösen Finger ein Weilchen über die Tastatur wandern, bis die richtigen »Lämpchen« auf dem Bildschirm angehen, und die Aufgabe ist erledigt, richtig? Nicht so hastig. Bevor Sie Tests für den Code programmieren und ihn zur Überprüfung einchecken können, müssen Sie nicht selten erst mal aufräumen.
Ist da eben der Begriff »Tests« gefallen? Oh ja, tatsächlich. Das Programmieren von Unit-Tests kann bei manchen Spielen ziemlich schwierig sein, der Großteil der Codebasis lässt sich aber bestens testen.
Ich werde jetzt keine Predigt zu diesem Thema halten, möchte Sie aber an dieser Stelle bitten, mehr automatisierte Tests in Betracht zu ziehen (sofern Sie das nicht ohnehin schon tun). Oder haben Sie etwa nichts Besseres zu tun, als den Kram immer wieder von Hand zu überprüfen?
Vielleicht haben Sie etwas zusätzlichen Code in Ihr Spiel eingebracht, möchten aber verhindern, dass der nächstbeste Programmierer über die Ecken und Kanten stolpert, die Sie im Code hinterlassen haben? Sofern es sich nicht gerade um eine sehr geringfügige Änderung handelt, ist oft eine gewisse Umstrukturierung nötig, damit sich Ihr neuer Code nahtlos in das übrige Programm einfügt – aber wenn Sie das richtig anstellen, wird der nächste Programmierer nicht feststellen können, zu welchem Zeitpunkt die einzelnen Codezeilen jeweils programmiert wurden.
Langer Rede, kurzer Sinn: Das Ablaufdiagramm der Programmierung sieht in etwa aus wie in Abbildung 1.1:
Abb. 1.1: Ihr Arbeitstag kurz zusammengefasst
Wenn ich genauer darüber nachdenke, finde ich die Tatsache, dass es kein Entkommen aus dieser Schleife gibt, doch etwas beunruhigend.
Es ist nicht gerade offensichtlich, aber ich denke, dass ein Großteil der Softwarearchitektur von dieser Lernphase bestimmt wird. Den Code in die Hirnzellen aufzunehmen, geht so qualvoll langsam vonstatten, dass es sich lohnt, nach Strategien zur Verringerung seines Volumens zu suchen. Deshalb ist ein ganzer Teil dieses Buches den Entkopplungsmustern gewidmet, und darüber hinaus befasst sich auch ein guter Teil der Design Patterns mit diesem Konzept.
Der Begriff »Entkopplung« lässt sich auf verschiedene Weise definieren. Meiner Auffassung nach bedeutet die Entkopplung zweier Codeabschnitte, dass man den einen verstehen kann, ohne den anderen verstehen zu müssen. Wenn Sie den Code entkoppeln, können Sie über beide Teile unabhängig voneinander nachdenken. Das hat den enormen Vorteil, dass Sie nur denjenigen Code in Ihrem Gedächtnis aufnehmen müssen, der Ihr aktuelles Problem betrifft – und nicht auch noch die andere Hälfte.
Das ist meiner Ansicht nach das entscheidende Ziel der Softwarearchitektur: Minimierung des Wissens, das man im Kopf haben muss, um Fortschritte machen zu können.
Die nachfolgenden Schritte spielen natürlich auch eine Rolle. Eine andere Definition der Entkopplung besagt, dass man Änderungen an einem Teil des Codes vornehmen kann, ohne zugleich auch den anderen Teil ändern zu müssen. Es liegt auf der Hand, dass irgendetwas geändert werden muss, aber je geringer die Kopplung ist, desto weniger beeinflusst eine Änderung den Rest des Programms.
Das klingt doch großartig, oder? Alles entkoppeln und schon kann man programmieren wie ein Wirbelwind. Jede Änderung betrifft nur ein oder zwei ausgewählte Methoden und Sie können auf der Struktur der Codebasis herumtanzen, ohne auch nur den Schatten einer Spur zu hinterlassen.
Das ist genau der Grund, warum die Leute sich für Abstrahierung, Modularität, Design Patterns und Softwarearchitektur begeistern. Es ist wirklich ein Vergnügen, mit einer guten Softwarearchitektur zu arbeiten – und produktiver zu sein, weiß jeder zu schätzen. Eine vernünftige Architektur bewirkt einen immensen Anstieg der Produktivität. Man kann gar nicht oft genug betonen, wie tiefgreifend die Auswirkungen sind.
Aber es gibt im Leben nun mal nichts umsonst. Eine gute Architektur erfordert Anstrengung und Disziplin. Bei jeder Änderung und jeder Ergänzung einer neuen Funktion sollten Sie stets um eine elegante Integration der entsprechenden Neuerung in den Rest des Programms bemüht sein. Sie müssen sorgfältig darauf achten, den Code nicht nur ordentlich zu organisieren, sondern diese Organisation während der zigfachen kleinen Änderungen, die jeder Entwicklungszyklus mit sich bringt, auch beizubehalten.
Die Beibehaltung der Struktur bedarf besonderer Aufmerksamkeit. Ich habe schon oft erlebt, dass Programme anfangs wunderbar strukturiert waren und dann allmählich tausend kleine Tode starben, weil die Programmierer immer wieder »mal eben nur eine winzig kleine Änderung« dazupackten.
Hier verhält es sich wie bei der Gartenarbeit: Es reicht nicht aus, die Blumen nur zu pflanzen – man muss sie auch beschneiden und Unkraut jäten.
Sie müssen sich also ein paar Gedanken dazu machen, welche Teile des Programms entkoppelt werden sollen und dort dann Abstrahierungen vornehmen. Außerdem müssen Sie herausfinden, wo Erweiterungen von vornherein vorgesehen werden sollten, um dadurch künftige Änderungen zu erleichtern.
Softwareentwickler lassen sich von diesem Konzept wirklich begeistern. Sie malen sich aus, dass andere Entwickler (oder sie selbst) in Zukunft eine leistungsfähige Codebasis vorfinden, in der alles machbar ist und die geradezu danach schreit, erweitert zu werden. Sie stellen sich gewissermaßen die einzig wahre Spiel-Engine vor.
Doch an diesem Punkt wird es knifflig: Wenn Sie eine Abstrahierung vornehmen, handelt es sich um eine spekulative Annahme, dass Sie diese Flexibilität später einmal benötigen werden. Sie fügen dem Spiel Code und damit Komplexität hinzu, deren Entwicklung, Debugging und Maintenance Zeit kostet.
Wenn Sie mit Ihrer spekulativen Annahme richtig liegen, lohnt sich der Aufwand und Sie können den Code später problemlos nutzen. Allerdings ist es wirklich schwierig, genaue Vorhersagen dazu zu treffen, ob dies für die Zukunft wirklich sinnvoll und nötig ist – und sollte die Modularität gar nicht erforderlich sein, kann sie sich schnell sogar nachteilig auswirken, denn letzten Endes haben Sie es hier mit zusätzlichem Codeballast zu tun.
Im Englischen wird häufig das Akronym YAGNI (You Aren’t Gonna Need It, Das wirst du nicht brauchen) in regelrechter Mantra-Manier gegen den Drang verwendet, darüber zu spekulieren, was in Zukunft benötigt werden wird.
Wenn die Programmierer hierbei übereifrig werden, führt das zu einer Codebasis, deren Architektur außer Kontrolle gerät. Überall finden sich Interfaces und Abstrahierungen, Plug-in-Systeme und abstrakte Basisklassen, virtuelle Methoden und alle möglichen Erweiterungen.
Es dauert ewig, in diesem Gesamtkonstrukt überhaupt Code zu finden, der tatsächlich eine richtige Aufgabe erledigt. Wenn Sie eine Änderung vornehmen möchten, gibt es vermutlich irgendein Interface, das hilfreich wäre – aber viel Spaß beim Suchen. Theoretisch sollten all diese Entkopplungen dazu führen, dass man weniger Code verstehen muss, um ihn ändern zu können – aber schon allein die Abstraktionsschichten dürften Ihre gesamte Gedächtniskapazität in Anspruch nehmen.
Codebasen dieser Art sind der Grund dafür, dass manche Leute die Softwarearchitektur als solche ablehnen, insbesondere Design Patterns. Man kann sich leicht so sehr in dem Code selbst verzetteln, dass man aus den Augen verliert, dass man ja eigentlich ein Spiel liefern möchte. Der Sirenengesang der Erweiterbarkeit lockt zahllose Entwickler an, die dann jahrelang an einer Engine arbeiten, ohne jemals herauszufinden, wofür diese Engine überhaupt verwendet werden kann.
Insbesondere im Bereich der Spieleentwicklung wird in Bezug auf die Softwarearchitektur und die Abstrahierung mitunter kritisiert, dass die Performance der Spiele darunter leidet. Viele der Patterns, die den Code flexibler machen, beruhen auf virtuellen Weiterleitungen, Interfaces, Zeigern, Benachrichtigungen und anderen Mechanismen, die zumindest etwas Zeit kosten.
Ein interessantes Gegenbeispiel sind Templates in C++. Mitunter lässt sich mithilfe der Template-Metaprogrammierung eine Abstrahierung der Interfaces erzielen, ohne dass sich dadurch irgendwelche Nachteile zur Laufzeit ergeben.
Hier steht eine ziemliche Bandbreite an Flexibilität zur Verfügung. Wenn Sie Code schreiben, der eine konkrete Methode einer Klasse aufruft, legen Sie diesen Aufruf bereits zum Zeitpunkt der Programmierung fest – die Klasse ist somit hartkodiert und fest vorgegeben. Wenn Sie hingegen eine virtuelle Methode oder ein Interface verwenden, ist erst zur Laufzeit bekannt, welche Klasse aufgerufen wird. Diese Vorgehensweise ist sehr viel flexibler, bedeutet aber auch einen Mehraufwand zur Laufzeit.
Die Template-Metaprogrammierung liegt irgendwo dazwischen. Hier fällt die Entscheidung, welche Klasse aufgerufen wird, beim Kompilieren, nämlich wenn das Template instanziiert wird.
Dafür gibt es einen Grund: Bei einem Großteil der Softwarearchitektur geht es darum, Ihr Programm flexibler zu machen und die Vornahme von Änderungen zu erleichtern. Letztendlich bedeutet das, weniger Annahmen in das Programm hineinzustecken. Sie verwenden Interfaces, damit Ihr Code mit jeder beliebigen Klasse zusammenarbeitet, die das Interface korrekt implementiert – statt nur mit der einen, die fürs Erste ausreichen würde. Sie verwenden Beobachter (Observer, Kapitel 4) und Benachrichtigungen (siehe Kapitel 15, Event Queue (Ereigniswarteschlange)), damit nicht nur zwei Bestandteile des Spiels miteinander kommunizieren können, sondern zukünftig auch drei oder vier.
Bei der Performance geht es immer auch um Annahmen. Viele Optimierungen sind nur aufgrund konkreter Einschränkungen möglich. Können wir mit Sicherheit annehmen, dass es nie mehr als 256 Feinde gibt? Großartig, dann können wir die ID auch in einem einzigen Byte unterbringen. Rufen wir hier nur Methoden eines bestimmten Typs auf? Gut, dann können wir eine statische Weiterleitung oder inline programmieren. Gehören alle Objekte zur selben Klasse? Fein, dann können wir ein schönes zusammenhängendes Array (siehe Kapitel 17, Data Locality) verwenden.
Das heißt natürlich nicht, dass mehr Flexibilität schlecht ist! Wir können unser Spiel schnell verändern – ebenso sind möglichst zügige Fortschritte in der Entwicklung unverzichtbar, damit die Sache Spaß macht. Auf einem Blatt Papier kann niemand ein ausgewogenes Spieldesign entwickeln, nicht mal der SimCity-Erfinder Will Wright. Dazu ist es notwendig, zu experimentieren und immer wieder alles zu überprüfen.
Je schneller man Ideen umsetzen und ausprobieren kann, wie sie sich auswirken, umso mehr kann man experimentieren und desto wahrscheinlicher wird es, möglicherweise auf etwas wirklich Großartiges zu stoßen. Selbst wenn die richtigen Spielmechanismen feststehen, ist noch viel Zeit für das Feintuning erforderlich – denn schon die kleinste Unausgewogenheit kann den Spielspaß ruinieren.
Es gibt hier keine einfache Lösung. Eine erhöhte Flexibilität, die das Entwickeln eines Prototyps beschleunigt, geht mit gewissen Performanceeinbußen einher. Und umgekehrt verringern Optimierungen die Flexibilität.
Meiner Erfahrung nach ist es allerdings einfacher, ein gelungenes Spiel zu beschleunigen, als einem schnellen Spiel mehr Spielspaß zu verleihen. Den Code flexibel zu halten, bis sich kaum noch etwas am Spiel ändert, ist ein gangbarer Kompromiss. Danach können dann einige der Abstrahierungen entfernt werden, um die Performance zu verbessern.
Das bringt mich zum nächsten Punkt, nämlich dass verschiedene Programmierstile ihre Berechtigung haben. In diesem Buch geht es vor allem darum, sauberen und gut zu wartenden Code zu produzieren und insofern steht wohl außer Frage, dass es meine Pflicht ist, die Programmierung »ordentlich« zu erledigen – aber auch »schlampiger« Code kann nützlich sein.
Eine vernünftige Systemarchitektur erfordert sorgfältige Überlegungen, und das wiederum kostet Zeit. Darüber hinaus ist die Maintenance (Aufrechterhaltung) einer guten Architektur über den Zeitraum der Lebensdauer eines Projekts hinweg ziemlich aufwendig. Sie sollten Ihre Codebasis behandeln wie Camper ihren Campingplatz: Versuchen Sie stets, sie in einem besseren Zustand als dem vorgefundenen zurückzulassen.
Das zahlt sich aus, gerade wenn Sie längere Zeit an und mit dem Code arbeiten. Wie ich aber bereits erwähnt habe, erfordert das Spieldesign eine Menge Experimentier- und Erkundungsarbeit. Gerade im Anfangsstadium der Entwicklung ist es üblich, Code zu schreiben, von dem Sie wissen, dass Sie ihn bald wieder löschen werden.
Wenn Sie nur herausfinden möchten, ob die Spielmechanik überhaupt funktioniert, ist eine sorgfältige Systemarchitektur Zeitverschwendung und es dauert länger, bis das Spiel tatsächlich läuft und Sie ein Feedback erhalten. Funktioniert die Spielidee nicht, wäre die in eleganten Code investierte Zeit vergeudet.
Die Prototyperstellung, also das »Zusammenklatschen« von Code, der gerade eben so weit funktioniert, dass Designfragen beantwortet werden können, hat absolut ihre Berechtigung. Dabei gibt es allerdings einen wichtigen Vorbehalt: Wenn Sie planen, Wegwerf-Code zu schreiben, müssen Sie sich vergewissern, dass Sie ihn auch tatsächlich wieder entfernen können. Zu oft habe ich schon erlebt, wie unfähige Manager folgendes Spielchen treiben:
Chef: »He, wir haben da eine Idee, die wir ausprobieren wollen. Nur ein Prototyp, Sie müssen das nicht ordentlich ausarbeiten. Wie lange brauchen Sie dafür?«
Programmierer: »Tja, wenn ich dies und jenes weglasse, keine Tests durchführe, auf die Dokumentation verzichte und Bugs wie Sand am Meer in Ordnung sind, dann ein paar Tage.«
Chef: »Klasse!«
Einige Tage später ...
Chef: »He, der Prototyp ist großartig. Können Sie noch ein paar Stunden dranhängen und ein wenig aufräumen, damit wir das Programm ausliefern können?«
Sie müssen sich vergewissern, dass die Anwender des Wegwerf-Codes begreifen, dass der Code, obwohl er anscheinend funktioniert, nicht gewartet werden kann und neu geschrieben werden muss. Wenn auch nur die geringste Wahrscheinlichkeit besteht, dass der Code dauerhaft verwendet werden wird, sollten Sie defensiv vorgehen und ihn gleich von Anfang an ordentlich ausarbeiten.
Hier ein Trick, mit dem Sie sicherstellen können, dass der Prototyp-Code nicht zu »echtem« Code wird: Schreiben Sie ihn in einer anderen Programmiersprache als das Spiel. Auf diese Weise muss er auf jeden Fall neu geschrieben werden, bevor er Eingang in Ihr Programm findet.
Im Allgemeinen sind folgende Zielsetzungen zu beachten:
Wir wollen eine vernünftige Architektur, damit der Code über die Lebensdauer des Projekts hinweg verständlich bleibt.
Wir wollen eine gute Laufzeitperformance erreichen.
Wir wollen anstehende Änderungen schnell erledigen können.
Ich finde es bemerkenswert, dass es bei diesen Zielsetzungen immer um eine Form von Geschwindigkeit geht: Die Geschwindigkeit der langfristigen Entwicklung, die Ausführungsgeschwindigkeit des Spiels und die Geschwindigkeit der kurzfristigen Entwicklung.
Diese Ziele sind zumindest teilweise gegensätzlich. Langfristig erhöht eine vernünftige Architektur die Produktivität, aber die Beibehaltung der Architektur bedeutet kurzfristig auch, dass jede Änderung ein klein wenig aufwendiger ist.
Diejenige Implementierung, die sich am schnellsten schreiben lässt, ist nur in den seltensten Fällen auch diejenige, die am schnellsten läuft. Vielmehr ist ein beträchtlicher Zeitaufwand für die Optimierung erforderlich. Und sobald diese erledigt ist, neigt die Codebasis dazu, zu versteinern: Hochoptimierter Code ist unflexibel, und es ist sehr schwierig, ihn zu ändern.
Es gibt immer den Druck, das heutige Arbeitspensum auch heute zu erledigen – um alles andere kann man sich morgen kümmern. Aber wenn alle Änderungen so schnell wie möglich durchgeführt werden, wird die Codebasis zu einem Durcheinander aus Hacks, Bugs und Inkonsistenzen, das unsere zukünftige Produktivität unterminiert.
Auch hier gibt es keine einfache Lösung, nur Kompromisse. Den E-Mails zufolge, die ich erhalte, lassen sich viele Leute davon entmutigen. Insbesondere für Neulinge, die einfach nur mal ein Spiel programmieren möchten, ist es ziemlich einschüchternd, wenn man ihnen sagt: »Eine richtige Lösung gibt es nicht, nur verschiedene Ausprägungen von falschen Lösungen.«
Ich selbst hingegen finde das spannend! Sehen Sie sich andere Tätigkeitsfelder an, denen manche Leute ihre gesamte Karriere widmen, um sie zu meistern. Immer gibt es irgendwelche zentralen Einschränkungen. Gäbe es eine einfache Lösung, würden sich alle daran halten. Ein Aufgabengebiet, das man in einer Woche zu beherrschen lernt, ist letzten Endes doch langweilig. Oder haben Sie etwa schon mal von einer bemerkenswerten Karriere im Ausheben von Straßengräben gehört?
Möglicherweise haben Sie ja doch schon von solch einer Karriere gehört, ich habe das nicht weiter recherchiert. Soweit ich weiß, spricht nichts dagegen, dass sich passionierte Straßengrabenausheber auf Fachtagungen für Straßengrabenausheber treffen und eine eigene Subkultur pflegen. Wer bin ich denn, das zu beurteilen?
Meiner Meinung nach gibt es hier einige Parallelen mit den Spielen selbst. Ein Spiel wie Schach wird wohl niemals vollständig verstanden werden, weil die verschiedenen Spielfiguren so perfekt aufeinander abgestimmt sind. Sie könnten Ihr ganzes Leben damit verbringen, brauchbare Strategien zu erforschen. Bei einem schlecht gestalteten Spiel hingegen verfolgt man immer wieder die eine gewinnbringende Taktik, bis man es gelangweilt beendet.
Ich habe seit einiger Zeit immer mehr das Gefühl, dass es doch eine geeignete Methode gibt, um mit diesen Einschränkungen fertig zu werden: Einfachheit. Inzwischen gebe ich mir bei meinem Code die größte Mühe, die einfachste und unmittelbarste Lösung des Problems zu schreiben. Bei dieser Sorte Code ist Ihnen beim Betrachten sofort klar, was er leistet, und es ist kaum eine andere Lösung vorstellbar.
Ich versuche zunächst, die passenden Datenstrukturen und Algorithmen zu finden (mehr oder weniger in dieser Reihenfolge) und mache dann auf dieser Grundlage weiter. Meiner Erfahrung nach produziere ich insgesamt weniger umfangreichen Code, sofern es mir gelingt, die Dinge einfach zu halten. Und das bedeutet wiederum, dass ich mir weniger Code merken muss, um Änderungen daran vorzunehmen. Außerdem läuft das Programm so meist auch recht schnell, weil weniger Mehraufwand anfällt und auch weniger Code auszuführen ist. (Das trifft allerdings mit Sicherheit nicht immer zu. Auch in einigen wenigen Codezeilen lassen sich jede Menge Schleifen und Rekursionen unterbringen.)
Bekanntlich beendete Blaise Pascal einen Brief mit den Worten »Ich hätte einen kürzeren Brief geschrieben, hatte aber leider keine Zeit.« Ein weiteres Zitat stammt von Antoine de Saint-Exupéry: »Perfektion ist nicht dann erreicht, wenn es nichts mehr hinzuzufügen gibt, sondern wenn man nichts mehr weglassen kann.«
In diesem Zusammenhang ist mir aufgefallen, dass die Kapitel dieses Buches nach jeder Durchsicht kürzer geworden sind – manche waren am Ende um ganze 20% geschrumpft!
Ich behaupte damit jedoch keineswegs, dass das Schreiben einfachen Codes weniger Zeit erfordert. Man könnte annehmen, dass dem so wäre, weil letzten Endes insgesamt weniger Code vorhanden ist, aber eine gute Lösung besteht nicht aus einer Anhäufung von Code, sondern einem Konzentrat.
Mit wirklich eleganten Problemen werden wir nur selten konfrontiert. Meistens handelt es sich um eine Reihe verschiedener Fälle, die eintreten können: Wenn der Fall Z eintritt, soll X die Aktion Y ausführen, aber Aktion W, wenn der Fall A eintritt usw. Mit anderen Worten: Eine lange Liste mit verschiedenen vorgegebenen Verhaltensweisen. Die unterschiedlichen Fälle einfach der Reihe nach zu programmieren, ist die Lösung, die am wenigsten Nachdenken erfordert. Das lässt sich insbesondere bei Programmieranfängern oft beobachten: Sie produzieren am laufenden Band einen Haufen bedingter Abfragen, eine für jeden möglichen Fall, der ihnen in den Sinn kommt.
Elegant ist das allerdings nicht – und außerdem versagt derartiger Code häufig, wenn er mit Eingaben gefüttert wird, die nur minimal von den Beispielen abweichen, die der Programmierer berücksichtigt hat. Wenn wir an Eleganz denken, schwebt uns eher eine allgemeine Lösung vor: ein kleiner Schnipsel Logik, der dennoch eine Vielzahl von Anwendungsfällen korrekt behandelt.
Diese Logik zu entwickeln, besitzt eine gewisse Ähnlichkeit mit der Verwendung regulärer Ausdrücke oder dem Lösen eines Puzzles: Es ist manchmal nicht ganz einfach, die verborgene Ordnung zu entdecken, die all den verschiedenen Fällen zugrunde liegt. Aber wenn Sie es schaffen, ist das ein wirklich erhebendes Gefühl.
Die meisten Leser überspringen die einführenden Kapitel, also – herzlichen Glückwunsch, dass Sie es bis hierhin geschafft haben! Ich kann Ihre Geduld kaum hinreichend belohnen, aber ich biete Ihnen einige Ratschläge an, die Ihnen hoffentlich von Nutzen sein werden:
Abstrahierung und Entkopplung vereinfachen und beschleunigen die Entwicklung Ihres Programms, Sie sollten Ihre kostbare Zeit aber nicht dafür aufwenden, sofern Sie nicht sicher sind, dass der fragliche Code diese Flexibilität auch tatsächlich benötigt.
Schenken Sie der Performance des Codes bei jedem Entwicklungsschritt Beachtung, aber verschieben Sie maschinennahe und sehr detaillierte Optimierungen zeitlich so weit wie möglich nach hinten.
Eins dürfen Sie mir glauben: Zwei Monate vor dem Auslieferungstermin ist kein geeigneter Zeitpunkt, um sich des lästigen kleinen Problems anzunehmen, dass das Spiel nur mit 1 FPS (frames per second, Bilder pro Sekunde) läuft.
Loten Sie die Möglichkeiten aus und nehmen Sie Änderungen zügig vor, führen Sie sie aber nicht so hastig durch, dass Sie ein wirres Durcheinander hinterlassen – schließlich müssen Sie weiterhin damit arbeiten.
Wenn Sie planen, Wegwerf-Code zu schreiben, verschwenden Sie keine Zeit damit, ihn aufzuhübschen. Rockstars verwüsten Hotelzimmer nur deshalb, weil sie genau wissen, dass sie das Hotel am nächsten Tag verlassen.
Und zum Schluss das Wichtigste: Wenn das Produkt Spaß machen soll, sorgen Sie dafür, dass auch die Entwicklung Spaß macht.
Kapitel 2
Command (Befehl)
Kapitel 3
Flyweight (Fliegengewicht)
Kapitel 4
Observer (Beobachter)
Kapitel 5
Prototype (Prototyp)
Kapitel 6
Singleton
Kapitel 7
State (Zustand)
Das Buch Design Patterns: Entwurfsmuster als Elemente wiederverwendbarer objektorientierter Software ist inzwischen über 20 Jahre alt. Würde es sich dabei um einen Wein handeln, wäre er inzwischen wohl alt genug, um ihn genussvoll trinken zu können – in der schnelllebigen Softwarebranche ist das praktisch schon antik. Die anhaltende Popularität des Buches zeigt aber auch, wie zeitlos Design doch im Vergleich zu vielen Frameworks oder so manchem anderen Verfahren ist.
Zwar sind die Design Patterns nach wie vor von Bedeutung, wir haben in den letzten beiden Jahrzehnten aber auch dazugelernt. In diesem Abschnitt werden wir uns einige der ursprünglichen Patterns der Gang of Four ansehen, und ich hoffe, zu jedem einzelnen von ihnen den einen oder anderen nützlichen Beitrag leisten zu können.
Ich bin der Ansicht, dass manche Patterns (Singleton, Kapitel 6) überbeansprucht und andere (Command, Kapitel 2) unterschätzt werden. Einige sind hier aufgeführt, weil ich ihre Bedeutung für die Spieleprogrammierung aufzeigen möchte (Flyweight und Observer, Kapitel 3 und 4). Und in manchen Fällen ist es einfach nur interessant zu sehen, wie die Patterns mit dem Gesamtbild der Programmierung verwoben sind (Prototype und State, Kapitel 5 und 7).
