RGSS für Anfänger/Funktionen und Klassen
| RGSS für Anfänger | |
|---|---|
| Autor | Ranmaru-kun |
| Thematik | RGSS |
| Vorraussetzungen | Programme: keine Fähigkeiten: keine |
| Andere Teile | |
Inhaltsverzeichnis
Funktionen
Der eigentliche Kern von Ruby und dem RGSS sind die Klassen. Mit denen funktioniert eigentlich das gesamte Programm. Da wir hier objektorientiert programmieren, sind Klassen die Basis von allem. Anschaulich erklärt ist ein Objekt eine einzelne Instanz einer Klasse und eine Klasse ist ein Datentyp, der verschiedene Werte haben kann und mit anderen Klassen interagieren kann. Um diese Interaktionen zu ermöglichen, gibt es Funktionen. Die heißen im englischen Ruby "methods", aber "Methoden" klingt so beschissen, dann sag ich hier lieber Funktionen. Eine einfache Funktion sieht in Ruby so aus:
Aufbau
def foo() p "Hello World." end
Die Funktion foo macht genau das, was zwischen def und end steht, also in diesem Fall "Hello World." ausgeben. Diese Funktion kann ich jetzt nach Belieben aufrufen.
foo
Immer, wenn ich im Code foo schreibe, wird die Funktion foo aufgerufen. Im Prinzip ist alles eine Funktion, auch p (print). Die ist nur schon vorher eingebaut und muß nicht mehr von uns programmiert werden. Die zwei Klammern nach dem Funktionsnamen geben die Parameter an, welche die Funktion braucht, um zu funktionieren. In diesem Fall gar keine. Wenn man eine Funktion schreibt, die zum Beispiel rechnen soll, dann braucht man die aber. Schreiben wir als Beispiel einen einfachen Schadensalgorithmus für ein Kampfsystem.
Parameterübergabe
def berechneSchaden(heldstr, monstercon) return ((heldstr * 100) - (monstercon * 30) ) end schaden = berechneSchaden(50,40); p schaden # Ausgabe: 3800
Gehen wir den Code durch: zuerst wird die Funktion berechneSchaden definiert und es wird gesagt, daß sie zwei Parameter braucht, um zu funktionieren. Die Stärke des Helden und die Konstitution des Monsters. Dann wird der Schaden berechnet und mit dem Befehl return zurückgegeben. Und am Ende können wir sie dann außerhalb der Funktion weiterverwenden.
Hash
Ein erweiterter Schadensalgorithmus mit Hashes könnte so aussehen:
alex = {"Name"=>"Alex", "STR"=>50} blob = {"Name"=>"Blob", "CON"=> 40} def berechneSchaden(held, monster) schaden = (held["STR"] * 100) - (monster["CON"] * 30) return schaden end schaden = berechneSchaden(alex,blob) p schaden # Ausgabe: 3800
Hier können wir zum Beispiel einfache Charakter-Hashes benutzen, um den Schaden zu berechnen. Dazu wären nicht mehr so viele Variablen nötig, wie beispielsweise im RM2k und man braucht im Kampfsystem nur noch die Namen anzugeben, um zu berechnen. Die Hashes, die ich hier benutzt habe, sind natürlich sehr rudimentär, um es zu vereinfachen.
Klassen
Kommen wir nun zu den Klassen. Klassen sind, wie oben gesagt, Datentypen, welche Werte haben und interagieren können. Dazu benutzen diese Klassen solche Funktionen, wie wir sie oben gesehen haben. Eine Klasse könnten zum Beispiel die spielbaren Helden sein. Der Name einer Klasse muß immer mit einem großen Buchstaben anfangen!
class SpielbareHelden def initialize(name, alter, waffe, str=50, con=40) #Wenn str und con nicht übergeben werden, werden die Standartwerte genommen. @name = name @alter = alter @waffe = waffe @str = str @con = con end def info p "Der Held heißt " + @name + ", ist #{@alter} Jahre alt und kämpft mit " + @waffe + "n." end def werte p "Stärke: #{@str}, Konstitution: #{@con}" end end
Zunächst sollte jede Klasse eine initialize() Funktion haben. Diese Funktion wird dann aufgerufen, wenn ein neues Objekt angelegt wird. Alle Funktionen danach sind optional und für die Objekte nachher zu gebrauchen. Soll sie als Scene arbeiten, braucht sie auch eine main funktion. Die Variablen mit dem @ davor sind lokale Variablen, welche nur innerhalb der Funktionen der Klasse verwendet werden können, es sei denn, man verwendet attr_accessor, aber dazu später mehr.
Zunächst legen wir mal einen neuen Helden mit Namen Alex an.
alex = SpielbareHelden.new("Alex", 18, "Schwerter", 50) #Oder alex = SpielbareHelden.new("Alex", 18, "Schwerter", 50, 40)
Dies ruft die initialize() Funktion auf und speichert die Werte von Alex in das Objekt alex. Ab jetzt kann das Objekt alle Funktionen der Klasse verwenden.
Funktionen aufrufen
alex.info # Ausgabe: Der Held heißt Alex, ist 18 Jahre alt und kämpft mit Schwertern. alex.werte # Ausgabe: Stärke: 50, Konstitution: 40
Klassen sind ungemein praktisch, um gewisse Funktionen zu machen. So könnte man zum Beispiel den oben geschriebenen Schadensalgorithmus als Klassenfunktion für jeden Helden einbauen, um direkt darauf zugreifen zu können. Um Werte der lokalen Variablen ändern zu können, verwenden wir den attr_accessor, der bei Ruby gleich dabei ist.
attr_accessor/attr_reader
class SpielbareHelden def initialize(name, level, str, con) @name = name @level = level @str = str @con = con end attr_accessor :level attr_accessor :str attr_accessor :con attr_reader :name def werte p "LEVEL #{@level}: Stärke: #{@str}, Konstitution: #{@con}" end end
Jetzt können wir einen Helden anlegen, dessen Werte variabel sind. So kann man Levelaufstiege programmieren. Der Name braucht keinen attr_accessor, weil der nicht mehr verändert werden kann.
- attr_accessor = Von Außen lesbar und beschreibar
- attr_reader = Von außen lesbar
alex = SpielbareHelden.new("Alex", 10, 50, 40) alex.werte # Ausgabe: LEVEL 10: Stärke: 50, Konstitution: 40 alex.level += 1 alex.str += 10 alex.con += 8 alex.werte # Ausgabe: LEVEL 11: Stärke: 60, Konstitution: 48
Die Variablen, die den attr_accessor haben, werden also mit objekt.variable angesprochen. Ebenso werden die Klassenfunktionen mit objekt.funktion angesprochen. Mit Ausnahme der initialize() Funktion, die mit objekt = klasse.new angesprochen wird. Ganz einfach eigentlich.
Superklassen/Tochterklassen
Zuletzt noch eine Sonderart von Klassen: sogenannte Tochterklassen. Diese Klassen "erben" die Funktionen und Werte ihrer übergeordneten Klasse, welche in Ruby Superklasse genannt wird. Eine Tochterklasse legt man folgendermaßen an:
class Foo def initialize(name) @name = name end def ausgabe return "Hallo " + @name + "!" end end class Bar < Foo end bla = Foo.new("Alex") text1 = bla.ausgabe p text1 # Ausgabe: Hallo Alex! blubb = Bar.new("Hugo") text2 = blubb.ausgabe p text2 # Ausgabe: Hallo Hugo!
In diesem Fall würde die Klasse bar ohne Veränderungen alle Funktionen und Werte der Klasse foo erben. Wozu aber dann eine Tochterklasse anlegen? Nun, der Grund ist, daß die Tochterklasse die vererbten Dinge überschreiben kann. Das heißt im Klartext, daß ich eine Klasse kopieren kann und dann verschiedene Dinge ändern. Das ist dann praktisch, wenn ich als fauler Programmierer nicht nochmal die ganze Chose abtippen will.
Super
class Foo def initialize(name) @name = name end def ausgabe return "Hallo " + @name + "!" end end class Bar < Foo def ausgabe return super + " Du bist foobar!" end end bla = Foo.new("Alex") text1 = bla.ausgabe p text1 # Ausgabe: Hallo Alex! blubb = Bar.new("Hugo") text2 = blubb.ausgabe p text2 # Ausgabe: Hallo Hugo! Du bist foobar!
Der Befehl return super bedeutet also, daß das zurückgegeben wird, was die Superklasse zurückgibt und im Fall der Klasse Bar wird noch etwas hintendrangehangen, nämlich der Text "Du bist foobar!".