Vorwort
Warum schreibe ich dieses Tutorial?
Ein Tutorial über Codestrukturierung? Wer braucht denn sowas?
Nun ja, ich habe keine Ahnung. Ich schreibe dieses Tutorial hauptsächlich weil mir gerade langweilig ist, aber auch weil ich glaube, daß es manchen gut täte, sich mal über gewisse Dinge beim Scripten Gedanken zu machen.
An wen richtet sich dieses Tutorial?
Dieses Tutorial richtet sich an Neulinge und Fortgeschrittene, bzw an all jene die schonmal verzweifelt sind, weil sie älteren Code nicht mehr verstanden haben, bzw an änderungen bestimmter Stellen verzweifelt sind. Die meisten der Erfahrenen bastler, brauchen dieses Tutorial wahrscheinlich nicht beachten, da sie schon Erfahrungen mit dieser Art von Problemen bekanntschaft gemacht haben, und bereits Gegenmaßnahmen ergriffen haben.
Struktur? Pah! Sowas hab ich nicht nötig!
Viele denken, sie haben es nicht nötig sich beim Schreiben ihres Codes gedanken über die Zukunft zu machen, oder denken gar nicht daran, daß sie zu einem späteren Zeitpunkt vielleicht einmal wieder das Chaos welches sie fabriziert haben, durchschauen müssen.
Spätestens aber wenn man sich mit einer dieser Situationen konfrontiert sieht, wird man sich wahrscheinlich wünschen etwas auf die Struktur seiner Scripts geachtet zu haben.
Situation 1: Man tüftelt gerade ein gewaltiges Kampfsystem/Menü/Script zusammen. Diese Arbeit zieht sich ne Zeit lang hin. Am Ende kehrt man zu einem älteren Teil des Scripts zurück und stellt mit entsetzen fest, daß man gar keinen Durchblick mehr hat. Vor allem Leute wie ich, bei denen es stark von der Motivation abhängt wie lang sie Arbeiten und wie groß die "Pausen" sind, haben solche Situationen schon oft angetroffen.
Situation 2: Zur erstellung eines bestimmten Scripts, benötigt man gewisse Berechnungen. Diese Berechnung kommt nun aber an den verschiedensten Stellen im Script vor. Man kopiert diese also an alle möglichen Stellen. Mit entsetzen stellt man zu einem späteren Zeitpunkt fest, daß etwas mit dieser Berechnung nicht stimmt, und darf sich durch alle möglichen Stellen des Scripts wühlen um diese Berechnung an allen Stellen auszubessern. Das führt oft zu Unmut, vor allem wenn dies öfter vorkommt.
Solche und noch einige andere Situationen lassen sich vermeiden, wenn man sich an bestimmte Richtlinien hält und etwas vorrausplant.
Manche spielen auch ein Spiel und bemerken eine Funktion die sie auch gerne in ihren Spielen haben möchten. Sie öffnen den Rm2k öffnen besagtes Projekt und stellen fest, daß sie absolut keinen Plan haben, wie derjenige das jetzt angestellt hat.
Man tut sich also nicht nur selber einen gefallen wenn man etwas Struktur in seinen Code bringt!
Worum geht es jetzt genau in diesem Tutorial?
Ich versuche allen die es interessiert, etwas über Codestruktur und Reuse beizubringen. Jedes dieser Themen ist ist in einem eigenen Abschnitt untergebracht. Es soll damit das Arbeiten an Projekten über längeren Zeitraum erleichtert werden, da man (hoffentlich) auch später noch den Durchblick hat.
Abschnitt 1: Struktur und Kommentare
Welche Scripts sollten Strukturiert werden?
Prinzipiell sollten alle Scripts so gut wie möglich Strukturiert und Kommentiert werden. Vor allem bei größeren und Komplizierteren Scripts ist dies eigentlich unbedingt erforderlich.
Allerdings kann man bei kleineren Events mal ein Auge zudrücken. Nehmen wir an, in einem Intro gibt es ein Event, welches eine Person ein paar Schritte gehen lässt, und dann zwei Worte mit einer anderen Person wechselt. Natürlich könnte man jetzt einen Kommentar zu beginn des Events schreiben, was da drin vor sich geht, doch vermutlich wäre dieser Kommentar dann genau so lang wie das Event selbst. Außerdem reicht ein kurzes hinsehen, und man weiß ca was da vor sich geht.
Verwende stets sprechende Namen!
Der 1. Punkt um Scripts übersichtlich zu halten, ist es sprechende Namen zu verwenden.
Doch was genau sind sprechende Namen?
Sehen wir uns mal folgende Berechnung an:
A - B = C
Wer kann sich hierbei denken worum es geht? Wahrscheinlich nur wenige.
Verwenden wir aber andere bezeichnungen für diese Werte sieht die Sache wahrscheinlich schon ganz anders aus:
Angriff - Verteidigung = Schaden
Schon lässt sich erkennen, daß es sich hierbei anscheinend um die Berechnung für den Schaden in einem Kampfsystem handelt.
Ein Name ist also immer dann Sprechend, wenn er aussagt, wofür, das was den Namen trägt, steht.
Man sollte Prinzipiell allem, was man benennen kann sprechende Namen geben. Events, Variablen, Switches, u.s.w.
Vor allem bei Mapevents, sollte man dies stets beachten, da man dann gleich beim bearbeiten der Map ungefähr erkennen kann, was dieser Event eigentlich tut. Ansonsten muss man sich immer wieder die ganzen Events durchschauen, bis man erkennt was da eigentlich vor sich geht. Bei 3 Maps mit jeweils 2 Events merkt man sich das vielleicht noch. Aber wer weiß, nach der 200. Map noch, was welches Event auf der 1. Map macht?
Kommentare sind unsere Freunde, also nutzt sie!
Ein Kommentar, am anfang eines Events, der beschreibt, was dieses Event macht ist nie verkehrt. Diese Beschreibung sollte nicht nur umfassen, was genau vor sich geht, sondern nach Möglichkeit auch welche Variablen/Switches gebraucht werden. Da alle Variablen/Switches global für das gesamte Projekt gelten, ist es immer gut zu wissen, welche Variablen/Switches nach ausführen eines Events Änderungen unterzogen wurden.
Auch innerhalb des Scripts sollte man vielleicht vor kleineren Berechnungen kurz anreissen, was diese Berechnung macht, da es im Scriptfenster nicht immer sofort ersichtlich ist, wie die Berechnung aussieht. Da der Rm2k auch immer nur eine Operation an einer Variable zulässt und eine Berechnung etliche Operationen erfordert, kann es vielleicht von Vorteil sein, mal einen Kommentar in folgender Form anzugeben.
<>Note: Schaden = Angriff - Verteidigung
<>Variable Ch[xxx: Schaden]Set, Var[yyy]val.
<>Variable Ch[xxx: Schaden]-, Var[zzz]val.
Hierbei steht yyy für die Nummer von Angriff und zzz für die Nummer von Verteidigung.
Diese Berechnung mag ja noch übersichtlich sein, aber wenn es mal so aussehen würde:
Schaden = ((((Angriff * 4) - (Verteidigung * 2)) + Random[0, Glück]) * 100) / Elementarverteidigung%
Hier sieht die Sache schon anders aus. Hier würde ich sogar noch einen kurzen prägnanten Kommentar dazuschreiben, was genau passiert und die Berechnung alleine nicht stehen lassen.
Zu welchen Befehlen genau sollte ich einen Kommentar Schreiben?
Vor einzelnen Befehlen, sollte man keine Kommentare schreiben, da das nur die Größe des Scripts aufblähen würde, und man erreicht genau das Gegenteil von dem was man haben möchte. Die Übersicht geht verloren.
Eigentlich sollte man nur größere Codeblöcke Kommentieren, und immer dann wenn nicht klar ersichtlich ist, was genau passieren soll.
<>Note: Held einen Schritt nach oben bewegen.
<>Move Event...: Hero, Up
Hier ist der Kommentar gänzlich überflüssig, da man ja sehr rasch erkennen kann was der Befehl macht.
Also, schreibt eure Kommentare nur dort hin wo sie auch gebraucht werden. Denkt jetzt aber nicht nur: "Ich weiß eh, was das ganze Zeug hier jetzt macht."
Denkt auch nach ob ihr nach ein paar Wochen/Monaten noch immer wisst, was diese 5 Befehle dort in dem Event machen. Und zwar ohne die Befehle noch mal anzusehen, durchzugehen und zu studieren.
Verwendet Kommentare zur optischen Trennung!
Der Rm2k verwendet ein Point & Click System um die Scripte erstellen zu lassen. Das mögen einige als Angenehm empfinden, hat aber einen gewaltigen Nachteil! Wenn man seine Scripte per Hand schreiben kann, kann man Leerzeilen machen, um gewisse Stellen optisch von anderen zu trennen. Das geht mit dem System vom Rm2k nicht. Oder etwa doch?
Sehen wir uns mal folgendes an:
<>Variable Ch[xxx: blubb1]Set, Var[aaa]val.
<>Variable Ch[xxx: blubb1]-, Var[bbb]val.
<>Variable Ch[xxx: blubb1]*, Var[ccc]val.
<>Variable Ch[xxx: blubb1]-, Var[ddd]val.
<>Variable Ch[zzz: blubb2]Set, Var[xxx]val.
<>Variable Ch[zzz: blubb2]-, Var[yyy]val.
<>Variable Ch[zzz: blubb2]Set, Var[yyy]val.
<>Variable Ch[zzz: blubb2]+, 55.
Hier sieht es so aus, als wäre das eine Berechnung.
<>Variable Ch[xxx: blubb1]Set, Var[aaa]val.
<>Variable Ch[xxx: blubb1]-, Var[bbb]val.
<>Variable Ch[xxx: blubb1]*, Var[ccc]val.
<>Variable Ch[xxx: blubb1]-, Var[ddd]val.
<>Note:
<>Variable Ch[zzz: blubb2]Set, Var[xxx]val.
<>Variable Ch[zzz: blubb2]-, Var[yyy]val.
<>Variable Ch[zzz: blubb2]Set, Var[yyy]val.
<>Variable Ch[zzz: blubb2]+, 55.
Jetzt kann man erkennen, daß dies anscheinend zwei unterschiedliche Berechnungen sind. Ein einzelner leerer Kommentar, kann also viel bewirken. Gut gesetzt, erhöht ein solcher die Lesbarkeit eines Scripts immens.
Ordnet eure Variablen und Switches!
Auch bei der Reihenfolge der Varialben/Switches lässt sich einiges an Struktur ins Spiel bringen. Man findet, Variablen/Switches nicht nur leichter wenn man sie ordentlich anordnet, es hat sogar einen praktischen Aspekt! Man kann sich einiges an Scriptingarbeit ersparen, wenn man bei seinen Variablen Ordnung hält.
Kommen wir wieder zu unserer Berechnung:
Schaden = Angriff - Verteidigung
Bevor dies irgendeine Wirkung hat, muss man natürlich die Entsprechenden Werte des Angreifers, bzw Ziels in die Variablen eintragen. Nehmen wir einmal an, wir haben einen riesigen Berg an Variablen, mit Statuswerten für alle Monster und Helden.
Jetzt könnten wir mit Forks durchgehen wer Angreift und dann die jeweiligen Statuswert in Angriff eintragen, und das selbe nochmal für Verteidigung machen.
Hat man diesen riesigen Berg an Variablen aber ordentlich angeordnet, so lässt sich mit einer zweiten kurzen Berechnung herausfinden, in welcher Variable der entsprechende Statuswert steht, und kann ihn in die jeweilige Variable eintragen. Das kann viel Scriptingarbeit ersparen, und ist daher übersichtlicher und leichter zu handhaben. Man stelle sich nur mal vor, es kommt später noch eine weiterer Statuswert zur berechnung hinzu. Was lässt sich leichter hinzufügen, 10 Forks oder zwei Variablenänderungen?
2. Abschnitt: Code Reuse
Was zur Hölle bedeutet Reuse?
Wenn man das Wort Reuse deutsch ausspricht, kann man vielleicht nicht sofort erkennen was es bedeutet, aber spricht man es englisch aus (Re use) erkennt man schnell, daß es sich schlicht und einfach um Wiederverwendbarkeit handelt. Der zweite Abschnitt behandelt also die Wiederverwendung von Code.
Was bringt Code Reuse?
Code Reuse ist extrem wichtig. Es hilft nicht nur den Code klein zu halten, sondern auch fehler auszubessern, oder nachträglich änderungen an den Scripts vorzunehmen.
In der "richtigen" Programmiererwelt findet Code Reuse exzessiven Gebrauch, also warum sollte man nicht auch beim Rm2k davon gebrauch machen?
Wie funktioniert Code Reuse?
Nun da wir wissen, was wir eigentlich wollen, kommen wir dazu wie man es anwendet.
Der Gedanke hinter Code Reuse ist es, unsere Skripte in kleine Module aufzuteilen, die sich hinterher wiederverwenden lassen. Ein kleines Beispiel haben wir schon kennengelernt, und zwar die Schadensberechnung.
<>Note: Schaden = Angriff - Verteidigung
<>Variable Ch[xxx: Schaden]Set, Var[yyy]val.
<>Variable Ch[xxx: Schaden]-, Var[zzz]val.
Dies wäre jetzt so ein kleines Modul. Ein in sich abgeschlossener Vorgang, den wir jetzt an verschiedenen Stellen verwenden könnten.
Angenommen wir wollen diese Berechnung an 3 verschiedenen stellen in einem Kampfsystem anwenden. Einmal bei einem normalen Angriff und bei zwei Skills noch dazu. Diese Skills machen einfach den doppelten und dreifachen schaden eines normalen Angriffs.
Diese Skills würden dann wahrscheinlich so den Schaden berechnen:
<>Note: Doppelschlag
<>Note: Schaden = (Angriff - Verteidigung) * 3
<>Variable Ch[xxx: Schaden]Set, Var[yyy]val.
<>Variable Ch[xxx: Schaden]-, Var[zzz]val.
<>Variable Ch[xxx: Schaden]*, 2.
------------------------
<>Note: Dreifachschlag
<>Note: Schaden = (Angriff - Verteidigung) * 3
<>Variable Ch[xxx: Schaden]Set, Var[yyy]val.
<>Variable Ch[xxx: Schaden]-, Var[zzz]val.
<>Variable Ch[xxx: Schaden]*, 3.
Wie man sieht, unterscheiden sich diese beiden Skills kaum von unserer normalen Schadensberechnung. Es kommt immer nur eine Variablenänderung dazu.
Wir könnten uns jetzt einfach ein Common Event mit der normalen Schadensberechnung machen. Nennen wir es auch gleich (sprechend) Schadensberechnung. In dieses Common Event schreiben wir einfach die ganz normale Schadensberechnung rein, wie sie schon die ganze zeit erwähnt wurde. Unsere Skills würden dann wie folgt aussehen:
<>Note: Doppelschlag
<>Note: Schaden = Schaden * 2
<>Call Event: Schadensberechnung
<>Variable Ch[xxx: Schaden]*, 2.
------------------------
<>Note: Dreifachschlag
<>Note: Schaden = Schaden * 3
<>Call Event: Schadensberechnung
<>Variable Ch[xxx: Schaden]*, 3.
Jetzt fragen sich, sicherlich einige, warum haben wir dieses kleine Codestückchen nicht einfach kopiert und eingefügt wie vorher?
Das hat zwei Gründe. Erstens haben wir uns ein wenig Arbeit erspart. Der Code wurde etwas kleiner und übersichtlicher (Stichwort: Strukturierung). Und zweitens hat das ganze nun einen weiteren gewaltigen Vortel. Nehmen wir an, wir wollten unsere Schadensberechnung abändern. Sei es aufgrund eines Rechenfehlers, oder einfach nur weil sie uns so wie sie ist nicht gefällt. Im ersten Fall, müssten wir die Schadensberechnung nun an 3 Stellen ändern.
Haben wir diese Berechnung aber extra in einem Common Event ausgelagert und den Code wiederverwendet, so müssen wir den Code nur an einer Stelle ändern, und zwar im Common Event. Dadurch wirkt sich nun die Änderung auch auf alle Skills aus die diese Schadensberechnung verwenden. Damit haben wir uns dann wieder ein wenig Arbeit erspart.
Jetzt erscheint es nicht so schlimm, ein wenig Copy&Paste mehr zu betreiben, immerhin spart man sich dadurch einen Common Event. Allerdings muss man bedenken, daß man manchen Code nicht nur an 3 Stellen hat, sondern an 5 oder 10. Nach längerem Arbeiten an einem Projekt vergisst man vielleicht wo man überall dieses Stückchen Code verwendet hat. Und sobald man was ändert, übersieht man eine Stelle. Dann würde ein Stückchen alter (fehlerhafter?) Code übrigbleiben, und die Suche geht weiter.
Parameter und Rückgabewerte
Manchmal wollen wir vielleicht, daß ein Modul aufgrund von unterschiedlichen Bedingungen andere Ergebnisse liefert. Außerdem wollen wir, natürlich das Ergebniss verwenden, sonst hätte das Modul keine nenneswerte verwendung.
Um das verhalten eines Stückchens Code zu steuern haben wir sogenannte Parameter. Ein Parameter ist nichts anderes als eine Variable die dem Code (Modul) eine Verhaltensweise vorgibt.
Wir haben sogar schon zwei Parameter kennengelernt.´Nämlich "Angriff" und "Verteidigung" im Falle unserer Schadensberechnung. Diese beiden Parameter sagen unserer Schadensberechnung nun, mit welchen Werten der Schaden berechnet werden soll. Ohne Parameter liefert ein Modul also immer das selbe Ergebnis.
Und was ist jetzt der Rückgabewert? Der Rückgabewert ist eben jene Variable in der das Ergebniss unseres Moduls gespeichert wird. Auch hier haben wir schon einen kennengelernt. Nämlich "Schaden". Unser Modul "Schadensberechnung" berechnet jetzt also den Schaden und speichert das Ergebnis in der Variable "Schaden" ab. Damit kann man überall im Gesamten Projekt, wo man einen Schaden berechnet haben will, einfach dieses Modul (hier ein Common Event) aufrufen und mit der Variable "Schaden" das Ergebnis Abfragen. Traditionellerweise hat ein Modul immer nur einen Rückgabewert, so wie die meisten Berechnungen nur ein Ergebnis haben. Im Rm2k ist es aber durchaus möglich mehrere Rückgabewerte zu liefern.
Wie gestalte ich meine Parameterlisten und Rückgabewerte?
Es ist sehr wichtig genau zu definieren, welche Parameter ein Modul erhält und welche Werte es zurückliefert. Diese sollten sich danach nicht mehr ändern. Eines der wichtigsten Konzepte beim Code Reuse ist es nämlich, daß wenn man ein Modul ändert man nicht auch die Stellen ändern muss, die dieses Modul verwenden, denn sonst könnte man ja gleich wieder mit der Copy&Paste Methode weitermachen.
Wenn wir unsere Schadensberechnung wie folgt ändern:
<>Note: Schaden = (Angriff * 4) - (Verteidigung * 2)
<>Variable Ch[xxx: Schaden]Set, Var[yyy]val.
<>Variable Ch[xxx: Schaden]*, 4.
<>Variable Ch[xxx1: Schaden2]Set, Var[zzz]val.
<>Variable Ch[xxx: Schaden]-, Var[xxx1]val.
So hat das keine Auswirkung auf die Stellen die unser Modul verwenden. Wie sieht es aber aus, wenn wir noch einen weiteren Statuswert hinzufügen? Nehmen wir an wir wollen einen Statuswert "Glück" hinzufügen, um ein wenig Zufall in die Berechnung zu bringen. Wir müssten unsere Liste an Parametern um eine Variable "Glück" erweitern. Damit müssten wir auch alle anderen Stellen ändern die unser Modul aufrufen, denn wir müssten dort zuerst der Variable "Glück" einen Wert geben.
Wie kann man das nun vermeiden? Wir müssen unsere Parameterliste so allgemein wie möglich halten, und so kompakt wie möglich.
Ändern wir unsere Parameterliste nun von "Angriff" und "Verteidigung" auf "Angreifer" und "Verteidiger". Was hätte das für eine Auswirkung auf unser Modul?
<>Note: Ermittle den Wert für "Angriff" aufgrund der Variable "Angreifer"
...
<>Note: Ermittle den Wert für "Verteidigung" aufgrund der Variable "Verteidiger"
...
<>Note: Schaden = (Angriff * 4) - (Verteidigung * 2)
<>Variable Ch[xxx: Schaden]Set, Var[yyy]val.
<>Variable Ch[xxx: Schaden]*, 4.
<>Variable Ch[xxx1: Schaden2]Set, Var[zzz]val.
<>Variable Ch[xxx: Schaden]-, Var[xxx1]val.
(Ich habe absichtlich den Code weggelassen der die Werte ermittelt, da er irrelevant wäre, und es sowieso keine Einheitlich Lösung für alle Spiele gibt)
Wenn man jetzt unser Modul aufrufen würde, muss man vorher nicht mehr Angriff und Verteidigung festlegen, sondern Angreifer und Verteidiger. Den Rest ermittelt die Schadensberechnung.
Dadurch wird zwar die Schadensberechnung etwas länger, aber das ganze hat folgenden Vorteil:
Sollte ich wie erwähnt noch den Statuswert Glück ins Spiel bringen wollen, so müsste ich nur Aufgrund von "Angreifer" den Wert abfragen und bräuchte außerhalb des Moduls nichts mehr ändern.
Wie erwähnt sollte jedes Modul nur ein Ergebnis liefern. Dies trägt zur übersicht bei, und wenn es notwendig ist ein zweites Ergebnis zu liefern, merkt man, daß man wohl schon wieder ein zweites Modul anfertigen könnte, welches dieses Ergebnis ermittelt.
Wie man anhand von Parametern und Rückgabewerten sieht, kann es ziemlich schwierig sein festzustellen, welche Variablen ein Modul verwendet. Man müsste sich durch den gesamten Code wühlen um dies herauszufinden. Darum ist es wichtig wie im 1. Abschnitt beschrieben wurde, zu kommentieren, welche Variablen verwendet werden.
Dürfen sich die Werte der Parameter in einem Modul ändern?
Prinzipiell dürfen sie schon, sollten es aber nicht. Der Grund lässt sich in einem Wort zusammenfassen: Übersicht.
Wenn ein Modul seine Parameter während der Ausführung ändert, kann es vorkommen, daß man zu einem Späteren Zeitpunkt nicht mehr weiß, warum in der Variable plötzlich ein anderer Wert steht. Mit Rückgabewerten ist das was anders. Man erwartet ja, daß sie einen anderen Wert bekommen, Parameter hingegen sollen nur ein Verhalten festlegen, aber keine Ergebnisse liefern. Sollte es sich nun aber nicht verhindern lassen, daß man Parameter in einem Modul mit anderen Werten belegt, so sollte dies unbedingt im Kommentar des Moduls festgehalten werden, wann, wie und warum sich der Wert des Parameters ändert.
Schlusswort
Brauche ich das ganze hier um ein Projekt zu machen?
Nein! Dies sind alles Richtlinien und Ratschläge um es den Leuten leichter zu machen, ein größeres Projekt zu erstellen, welches über einen längeren Zeitraum erstellt wird. Leute wie ich, die mal Wochen lang den Rm2k anrühren, haben es mit diesen Richtlinen sicherlich leichter sich nachher wieder in das Projekt einzuarbeiten. Außerdem hilft es anderen, die lernen wollen wie gewissen Dinge funktionieren, sich in dem Projekt zurechtzufinden.
Verschiedene Leute, verschiedene Stile.
In einer echten Programmierumgebung entwickeln die Leute immer wieder eigene Stile wie sie ihre Projekte strukturieren und Dokumentieren. Das werden auch die Leute hier mit dem Rm2k machen. Ihr solltet Prinzipiell so Kommentieren, daß ihr euch nachher in dem Projekt zurecht findet, sonst macht das alles keinen Sinn. Sollten sich dann noch andere mit eurem Werk zurechtfindne, ist das auch noch schöner, denn dann wisst ihr, daß ihr eure Arbeit gut gemacht habt!
Aus! Schluss! Ende!.
So, das war's nun mit meinem Tutorial, ich hoffe ich konnte euch einen Stoß in die richtige Richtung geben, und euch Ansätze für ordentliche Codestrukturierung geben. Diese Richtlinien garantieren nicht den Erfolg eines Projektes und sichern nicht die Qualität des Spiels, aber es soll das Arbeiten an dem Projekt erleichtern, und das ist doch schon einiges.
Am Ende möchte ich dann noch mal allen Gratulieren, die es bis hier her durchgehalten haben!
~The_Burrito