RGSS/Tutorials/Rubykurs 3 - RGSS Teil 1

Aus Scientia
Wechseln zu: Navigation, Suche
Rubykurs Schwierigkeitsgrad Stern.png
Autor Kai D
Thematik Ruby
Vorraussetzungen Programme: RPG-Maker XP

Fähigkeiten: Grundlagen des

Eventscripting im RPG-Maker XP

Andere Teile

Nachdem die Grundlagen von Ruby und OOP besprochen wurden, wird es Zeit diese Kenntnisse auch anzuwenden. In diesem Kapitel sollen erstmals richtige Scripte geschrieben werden, mit denen man als Makerer auch etwas anfangen kann.

RGSS ist die Abkürzung für Ruby Game Scripting System. Es ist weder eine eigene Skriptsprache, noch ist es ein abgespecktes Ruby - wie fälschlicherweise oft behauptet wird. RGSS ist lediglich eine Bibliothek, welche verschiedene Klassen für Grafik und Sound zur Verfügung stellt. Man schreibt also weiterhin in normalen Ruby, nur dass man zusätzlich der zuvor behandelten Klassen noch neue Klassen kennenlernt, welche das Anzeigen von Grafiken, das Abspielen von Sounddateien, sowie viele andere nützliche Dinge ermöglichen.

Am Anfang des Kapitels werden wir uns kurz mit dem Aufbau des Scripteditors beschäftigen. Danach geht es um das Erstellen eigener Szenen und schließlich kommt die Grafik dran: Anzeigen von Bildern, Erstellen von eigenen Menüs etc.

Aufbau des Scripteditors

Im Scripteditor sollte sofort auffallen, dass fast jedes Script einen Präfix hat. Beispielsweise Scene_Menu, Scene_File und Scene_Title haben den Präfix Scene. Andere Präfixe sind Sprite, Spriteset, Arrow, Window und Game. Dann gibt es noch die Interpreter-Scripte und das Script Main. Auch wenn weder der Name eines Scriptes, noch der Name einer Klasse wirklich eine Rolle spielt, weisen uns diese Präfixe und Namen doch auf die Funktion einer Klasse hin.

  • Scene: Die Scene-Scripte teilen das Spiel in verschiedene Spielabschnitte ein. Sie kontrollieren praktisch, was alles innerhalb eines Spielabschnittes geschehen soll. Ein möglicher Spielabschnitt wäre beispielsweise der Kampf. Die Scene Scene_Battle legt also den Ablauf des Kampfes fest.
  • Window: Die Window-Scripte sind Nachkommen (also Subklassen) der Superklasse Window. Sie gehören zu den Grafikklassen, die wir an späterer Stelle noch erklären wollen. Die Klassen sind also zum Anzeigen von Fenstern und Menüs da.
  • Sprite: Ebenfalls eine Grafikklasse, Subklasse der Grafikklasse Sprite. Sie sind zum Anzeigen einfacher oder animierter Bilder zuständig.
  • Arrow: Wieder eine Subklasse der Sprite-Klasse. Sie dienen zum Anzeigen von Auswahlpfeilen, die im Kampfsystem gebraucht werden.
  • Spriteset: In Spielabschnitten, in denen sehr viele Grafiken angezeigt werden müssen (beispielsweise auf der Map) ist es sinnvoll, die vielen Grafiken in einem gemeinsamen Objekt zu verwalten. Hierfür sind Spriteset-Klassen da. Sie sind also nur eine Sammlung mehrerer Sprites und anderer Grafikklassen.
  • Game: Der eigentliche Inhalt des Spieles wird in diesen Klassen aufbewahrt. Sie sind zur Datenspeicherung und -Verarbeitung zuständig. Die Koordinaten von Spieler und Events, die Einstellungen des Spiels, das Inventar der Truppe - all derlei Sachen werden in Game-Klassen aufbewahrt.
  • Interpreter: Der Interpreter ist im Grunde genommen eine Art Übersetzer. Er liest die Eventcommands eines Events ein und wandelt sie in Rubycode um. Er setzt also die Eventbefehle, die wir im Maker im Eventeditor einstellen, in Ruby um. Verändert man den Interpreter, so verändert sich auch die Ausführung der Eventcommands. Auf diese Weise kann man Eventcommands also auch neue Aufgaben o.ä. geben. Die Einstellungen im Maker kann man damit allerdings nicht beeinflussen, sondern nur, wie diese interpretiert/übersetzt werden.
  • Main: Dies ist keine Klasse, sondern nur ein Stück Code im Hauptprogramm. Es startet das Spiel und die Scripte im Scripteditor.

Eigene Testszene erstellen

Grundsätzlich sollte man sich angewöhnen, alle Elemente, die in einem Spielabschnitt vorkommen, in die dazugehörige Scene zu schreiben. Zwar kann man Grafiken auch per Script ausführen, doch ist diese Variante mit vielen Komplikationen verbunden. Bevor wir also lernen, wie man Grafiken auf dem Bildschirm anzeigt, müssen wir uns mit Scenen beschäftigen.

Damit eine Scene überhaupt arbeiten kann, muss ihre main Methode von dem Script Main aufgerufen werden.

Main

Wir haben gelernt, dass Code außerhalb von Methoden sofort ausgeführt werden. Steht der Code innerhalb einer Klasse, so wird er von der Klasse ausgeführt (beispielsweise die Methode attr_accessor), steht er außerhalb der Klasse, wird er von einem Hauptprogramm-Objekt ausgeführt. Der Code im Script Main ist in keine Klasse eingebunden, er wird also vom Hauptprogramm-Objekt selbst ausgeführt.

begin
  # Prepare for transition
  Graphics.freeze
  # Make scene object (title screen)
  $scene = Scene_Title.new
  # Call main method as long as $scene is effective
  while $scene != nil
    $scene.main
  end
  # Fade out
  Graphics.transition(20)
rescue Errno::ENOENT
  # Supplement Errno::ENOENT exception
  # If unable to open file, display message and end
  filename = $!.message.sub("No such file or directory - ", "")
  print("Unable to find file #{filename}.")
end

Das Script sieht auf den ersten Blick recht kompliziert aus, doch das liegt nur daran, dass einige hier verwendeten Befehle noch nicht im Kurs behandelt wurden. Der begin, rescue und end Block kann also erst einmal getrost ignoriert werden. Er ist für das Anzeigen der Fehlermeldung, sogenannter Exception verantwortlich.

Wichtig sind nur folgende Codezeilen:

Graphics.freeze
$scene = Scene_Title.new
while $scene != nil
  $scene.main
end

Gehen wir den Code Schritt für Schritt durch: Graphics ist ein Modul, welches für das Anzeigen der Grafiken, das Zählen der Frames etc. verantwortlich ist. Es steht, wie viele RGSS-Klassen nicht im Scripteditor, sondern ist direkt in die DLL kompiliert. Grund dafür ist, dass vor allem Grafik- und Soundprozesse zu rechenintensiv sind, um in reinen Ruby geschrieben zu werden. Wir können also nur auf die Methoden der Graphics-Klasse zugreifen, wie sie wirklich arbeitet, bleibt uns verborgen.

Glücklicherweise stehen in der Helpdatei (und auch in der TechWiki) alle Methoden dieser RGSS-Klassen aufgelistet.

Die Methode Graphics.freeze friert den Bildschirm ein. Der Effekt ist derselbe, wie beim Event Command Prepare for Transition und er hat auch die gleiche Aufgabe: An späterer Stelle des Scriptes wird die Methode Graphics.transition aufgerufen, die, genauso wie Execute Transition, den aktualisierten Bildschirm über den veränderten einblenden wird, und damit einen Übergangseffekt bildet. Die beiden Eventcommands sind in erster Linie dafür verantwortlich, einen weichen Übergang beim Wechsel von einer zur nächsten Map zu erzeugen. Die beiden Rubybefehle werden vor allem dafür benutzt, um einen weichen Übergang zwischen zwei Szenen (oder in diesem Fall zwischen Main und einer Szene) zu erzeugen.

$scene = Scene_Title.new erzeugt die erste Szene, Scene_Title, und weist ihr die globale Variable $scene zu. Daraufhin kommt eine while-Schleife, welche unendlich oft die Methode main der jeweiligen Scene-Klasse ausführt, bis das Programm beendet werden soll (was dann der Fall ist, wenn die Variable $scene auf nil zeigt. Probiert es aus und gibt in einem CallScript $scene=nil ein. Ihr werdet sehen, das Programm wird dann sofort beendet).

Fassen wir also zusammen: Im Main-Script wird die erste Scene, also der erste Spielabschnitt erstellt: Scene_Title. Danach wird die Methode main dieser Klasse ausgeführt (ich werde das Ausführen von Instanzmethoden in Zukunft folgendermaßen schreiben: Klasse#instanzmethode, so ist ersichtlich, wer diese Methode ausführt. In diesem Fall also: Scene_Title#main).

Schauen wir, was diese main-Methode macht.

Testszene

Statt mit einer der bestehenden Szenen zu arbeiten, wollen wir im Folgenden unsere eigene Szene schreiben. Erstellt im Scripteditor ein neues Script mit dem Namen "Scene_Test" und fügt den unten stehenden Code ein.

class Scene_Test
  #--------------------------------------------------------------------------
  # * Ablauf der Szene (durch Main-Script aufgerufen)
  #--------------------------------------------------------------------------
  def main
    # Initialisierung der Instanzvariablen
 
    # Ausführen des Überblendeffektes
    Graphics.transition
    # Hauptschleife
    loop do
      # Aktualisieren der Grafiken
      Graphics.update
      # Aktualisieren der Tasteneingabe
      Input.update
      # Aktualisieren der Instanzvariablen
      update
      # Abbruch der Schleife, wenn eine neue Szene beginnt
      if $scene != self
        break
      end
    end
    # Bildschirm einfrieren für Überblendeffekt
    Graphics.freeze
    # Disposing der verwendeten Grafiken
 
  end
  #--------------------------------------------------------------------------
  # * Aktualisierung der Instanzvariablen
  #--------------------------------------------------------------------------
  def update
 
  end
end

Nun geht in das Script Main und ändert den Code $scene = Scene_Title.new in $scene = Scene_Test.new um. Nun wird anstelle der Titelszene unsere Testszene am Anfang des Programms abgespielt.

Die Testszene ist zugleich ein Musterbeispiel einer Szene. Jede Szene ist auf die gleiche Weise aufgebaut. Gehen wir die Szene doch einmal durch:

Das Main-Script erzeugt eine Instanz der Klasse Scene_Test und übergibt ihr als Variable $scene. Als nächstes beginnt das Main-Script in einer Endlosschleife ununterbrochen die Methode Scene_Test#main auszuführen.

Innerhalb dieser Methode befindet sich wieder eine Endlosschleife (ein loop-Iterator). Sie wiederholt ihr inneres solange, bis $scene nicht mehr auf die Instanz der Klasse Scene_Test zeigt. Oder anders ausgedrückt: Bis ein neuer Spielabschnitt/eine neue Szene begonnen wird.

Alles was vor dieser Schleife geschieht, passiert beim Beginn der Szene. Alles was innerhalb der Schleife geschieht, passiert während des Ablaufens der Szene. Alles was nach der Schleife steht, geschieht beim Beenden der Szene.

Unter das Kommentar "# Initialisierung der Instanzvariablen" werden alle Elemente, die sich in diesem Spielabschnitt befinden, erzeugt und an eine Instanzvariable gebunden. Auch alle Grafikobjekte werden hier erzeugt. Bei einer Scene_Kartenspiel könnte das Beispielsweise so aussehen:

@spieler = Spieler.new
@gegenspieler = Gegenspieler.new
@kartentisch_spriteset = Kartentisch_Spriteset.new

Danach kommt die Codezeile Graphics.transition. Wir haben sie ja bereits besprochen: Sie erzeugt den Überblendeffekt zwischen den Szenen. Werden alle Grafiken vor dieser Codezeile erstellt und angezeigt, so werden sie beim Beginn der Szene sanft hineingeblendet.

Als nächstes kommt unsere Schleife:

Graphics.update
Input.update
update
if $scene != self
  break
end

Graphics.update aktualisiert alle Grafiken auf dem Bildschirm. Zeigt man neue Bilder an, bewegt bereits bestehende Bilder oder löscht andere, so werden all diese Änderungen erst dann sichtbar, wenn diese Methode aufgerufen wird. Das ist von Vorteil, denn wenn man 10 Bilder beispielsweise nacheinander nach rechts bewegt und erst dann diese Methode aufruft, so erscheint es dem Spieler, als würden alle Bilder gleichzeitig bewegt werden. Die Methode hat jedoch noch eine andere Eigenschaft: Sie hält das Spiel kurzzeitig an. Bei einer Framerate von 40 Frames pro Sekunde heißt das: Das Spiel wird bei jedem Graphics.update ungefähr 1/40 Sekunde angehalten, so dass nach 40 Graphics.update Methoden, durch die der Bildschirm 40 Mal aktualisiert wurde, genau eine Sekunde um ist. Die Graphics.update Methode sollte möglichst nur an dieser Stelle der Szene und nirgends anders geschrieben werden. Auf diese Weise werden nämlich alle Elemente der Szene jeden Frame gleichmäßig aktualisiert.

Input.update überprüft, ob eine Taste gedrückt wurde. Man kann dann über weitere Methoden des Input-Moduls abfragen, welche Taste zuletzt gedrückt wurde. Auch das hat seine Vorteile, denn nach dem Input.update wird die Scene_Test#update Methode aufgerufen. In dieser Methode werden alle Elemente der Szene aktualisiert. Hier könnten auch Tasteneingaben überprüft werden. Drückt man nun beispielsweise die Enter-Taste, so wird dies durch die Methode Input.update registriert. Bis zum nächsten Aufruf dieser Methode weiß der Maker, dass die Taste Enter gedrückt wurde. Es ist nicht möglich, dass der Spieler schnell noch eine andere Taste drückt, denn diese würde erst beim erneuten Aufrufen der Input.update Methode bemerkt werden (da diese aber jede 1/40 Sekunde aufgerufen wird, kommt es auch zu keiner Verzögerung).

update ist einfach nur eine Methode der Szene. Zur Übersichtlichkeit werden die Instanzvariablen der Szene nicht im Schleifenkörper, sondern in einer extra Methode aktualisiert. Hier kommt alles rein, was während des Ablaufes der Szene geschehen soll.

Die Bedeutung von if $scene != self sollte mittlerweile bekannt sein. $scene verweist auf den aktuellen Spielabschnitt. self verweist auf das aktuelle Objekt. Wenn das aktuelle Objekt nicht mehr dem aktuellen Spielabschnitt entspricht (wenn beispielsweise der Spielabschnitt Menü verlassen wird und der Spieler sich zum Spielabschnitt Map begibt) soll die Schleife abgebrochen und die Szene beendet werden.

Beim Beenden der Schleife kommt es wieder zum einfrieren des Bildschirms. Viel wichtiger aber ist der Abschnitt "# Disposing der verwendeten Grafiken". An dieser Stelle müssen alle, während der Szene verwendeten, Grafiken disposed, also gelöscht werden. Wir haben gelernt, dass Ruby alle Objekte in den Müllschlucker GC wirft, die keine Variable mehr haben. Bei Grafiken gilt aber, dass diese mit der Methode dispose gelöscht werden sollten. Danach kann die Variable immer noch auf nil gesetzt werden (was aber nicht mehr notwendig ist, da die Szene dann ja sowieso beendet wird und die Instanzvariablen somit ihre Gültigkeit verlieren).

An dieser Stelle kann man auch alles hinschreiben, was beim Beenden der Szene passieren soll. Bei einem Kartenspiel wäre das beispielsweise das Austeilen des Gewinns. Im Kampf hingegen das Verteilen der Erfahrungspunkte.

Wenn ihr euer Programm startet, werdet ihr erst mal nur auf einen schwarzen Bildschirm weitergeleitet. Das ist unsere, wie man sieht noch leere, Scene_Test. Wenn ihr das Programm beenden wollt, klickt auf Alt+F4. Nun liegt es an uns, die Szene mit Leben zu füllen.

Tastenabfragen

Damit der Spieler im Spiel auch agieren kann, müssen wir Tastenabfragen erstellen. Wir kennen bereits im Maker einige Event Commands, welche überprüfen ob der Spieler eine Taste gedrückt hat. In Ruby sind diese Sachen im Grunde genommen sogar noch einfacher. Zum Abfragen der Tasteneingabe gibt es das Modul Input. Es besitzt folgende Methoden:

  • Die Input.trigger?(taste) überprüft, ob eine Taste gedrückt wurde. Sie erkennt jedoch nicht, wenn eine Taste gedrückt gehalten wird. Im Normalfall verwendet man diese Methode zur Steuerung in Menüs. Jeder Tastendruck bewegt den Cursor einen Menüpunkt weiter. Ein Gedrückthalten der Taste hingegen bewirkt keine weitere Bewegung.
  • Bei der Bewegung des Helden wäre das fatal, man müsste für jeden Schritt neu auf die Taste drücken. Zum Glück gibt es die Input.press?(taste) Methode, welche auch dann noch reagiert, wenn die Taste gedrückt gehalten wird.
  • Input.repeat?(taste) ist eine Art "Mischung" aus Input.trigger? und Input.press? und wird vor allem in Menüs verwendet. Dort will man zwar, dass der Spieler eine Taste gedrückt halten kann um so schnell von einer Zeile des Menüs in eine andere zu wechseln (stellt euch mal vor man müsste im Item-Menü um den Cursor zu bewegen jedes Mal neu auf die Tastatur hämmern), andererseits will der Spieler aber eventuell auch durch einzelnes Drücken der Taste durch das Menü schalten (insbesondere wenn es ein kleines Menü mit nur wenig Menüpunkten ist). Input.repeat? erlaubt beides. Drückt man die Taste nur kurz, wird das als einmaliger Tastendruck interpretiert. Hält man die Taste etwas länger gedrückt, so wird es als Gedrückthalten interpretiert. Im Gegensatz dazu würde Input.press? schon ein kurzes Drücken als Gedrückthalten interpretieren und im Itemmenü würde ein kurzer Tastendruck den Cursor schon 10 Items weiterbewegen.
  • Input.dir4 und Input.dir8 sind die idealste Lösung für Heldenbewegungen. Sie geben ein Fixnum zurück, welches die momentan gedrückte Richtungstaste darstellt. Welche Zahl für welche Richtung steht, könnt ihr in eurem Numpad (rechts auf der Tastatur) ablesen. 2 (unten), 4 (links), 6 (rechts), 8 (oben) sind die möglichen Rückgabewert bei dir4. Die Method dir8 erkennt auch, wenn man zwei Richtungstasten zum diagonalen Bewegen gedrückt hält (1 ist links unten, 3 ist rechts unten, 7 ist links oben, 9 ist rechts oben).

Die Abfrage der Richtungstasten lässt sich wunderbar in einen Case einbauen. Probieren wir es in unserer Testklasse einmal aus. Wir schreiben im folgenden nur unsere Scene_Test#update Methode um:

class Scene_Test
  def update
    case Input.dir4
      when 2 then print("die Taste UNTEN wurde gedrückt!")
      when 4 then print("die Taste LINKS wurde gedrückt!")
      when 6 then print("die Taste RECHTS wurde gedrückt!")
      when 8 then print("die Taste OBEN wurde gedrückt!")
    end
  end
end

Der Druck auf eine Richtungstaste sollte nun eine entsprechende Nachricht anzeigen. Zum Abfragen anderer Tasten verwenden wir die Konstanten des Input-Moduls. C steht für Enter, B für Abbrechen. Alle anderen Tasten findet man in der Helpdatei oder in der TechWiki. Da die Konstanten für die Tasten im Input-Modul stehen, müssen wir mit der Syntax Modulname::Konstantenname darauf zugreifen. Zum Beispiel so:

class Scene_Test
  def update
    if Input.trigger?(Input::C) then print("Die Entertaste wurde gedrückt!") end
  end
end

Bitmaps und Sprites

Grafiken aus Dateien laden und anzeigen

Beschäftigen wir uns also endlich mit Grafiken, damit wir in Zukunft eigene Menüs und Bilder per Ruby anzeigen können. Vorerst müssen wir klären, wie in der RGSS Grafiken überhaupt funktionieren.

In RGSS bestehen Grafiken eigentlich immer aus zwei Elementen: einem Träger und ein Bitmap. Man kann dies mit einem Gemälde vergleichen, welches aus einer Leinwand und aus dem eigentlichen Bild, welches auf die Leinwand draufgepinselt wurde, besteht.

Das Bitmap ist dabei die eigentliche Grafik, eine Tabelle aus verschiedenen, farbigen Pixeln. Die Klasse Bitmap ist wie alle Grafikklassen in die DLL compiliert und für uns nicht einsehbar, ihre Methoden sind jedoch in der Helpdatei aufgelistet.

Normalerweise erzeugt man Bitmaps, indem man eine Grafikdatei aus dem Projektordner einliest. Dies macht man für gewöhnlich mit dem RPG::Cache-Modul:

#allgemeine Syntax
mein_bitmap = RPG::Cache.dateityp("dateiname", farbton)
 
#Beispiel
mein_bitmap = RPG::Cache.battler("001-Fighter01", 0)

Das RPG::Cache-Modul liest die Grafiken ein und speichert sie, so dass sie fortan von überall aus verwendet werden können. Zu bemerken ist, dass nicht jeder Grafiktyp einen Farbton-Wert benötigt. Bei manchen Grafiktypen, z.B. Pictures, muss man diesen Wert nicht mit angeben. Bei dem Dateinamen darf man die Endung weglassen, es sie denn es gibt mehrere Dateien mit dem gleichen Namen.

Nun haben wir die Grafik, doch damit sie auf dem Bildschirm angezeigt werden kann, fehlt uns noch die Leinwand. Hierfür verwenden wir die Grafikklasse Sprite.

Ein Sprite ist eine Grafikklasse, die ein Bitmap auf dem Bildschirm anzeigt. Nehmen wir einmal an, wir haben vier Events, die die gleiche Eventgrafik verwenden. Im Spiel wird also vier Mal die gleiche Eventgrafik angezeigt. In diesem Beispiel wird der Unterschied zwischen Bitmaps und Sprites deutlich: Die Eventgrafiken sehen untereinander gleich aus, aber sie werden vier Mal an jeweils anderer Stelle angezeigt. Wir haben hier vier Sprites, welche das gleiche Bitmap verwenden. Im Gegensatz zu einem Gemälde, wo Leinwand und Farbe immer eine untrennbare Einheit bilden, kann in Ruby ein Bitmap von beliebig vielen Grafikklassen gleichzeitig genutzt werden. Das Bitmap enthält dabei nur die Information, wie eine Grafik aussieht, die Grafikklassen sorgen dafür, dass die Grafik auf dem Bildschirm angezeigt wird.

Einen Sprite erstellt man normal über die new Methode der Klasse.

mein_sprite = Sprite.new

Um Sprites ein Bitmap zuzuweisen, benutzen wir die Setter-Methode bitmap=, um auf das Bitmap zuzugreifen, die Getter-Methode bitmap().

mein_sprite.bitmap = RPG::Cache.battler("001-Fighter01", 0)

Zum Löschen des Sprites verwenden wir die Methode Sprite#dispose, die übrigens jede Grafikklasse beherrscht.

mein_sprite.dispose

Das war auch schon alles. Das Bitmap sollte nun in der oberen linken Ecke des Bildschirms angezeigt werden. Probieren wir es doch in unserer eigenen Szene aus!

Zuerst erzeugen wir unseren Sprite @sprite unterhalb des Kommentars "# Initialisierung der Instanzvariablen" und weisen ihm dort sein Bitmap zu. Um den Code dynamischer zu halten verwenden wir hierfür eine eigene Methode. Damit der Sprite am Ende auch wieder gelöscht wird, disposen wir ihn unterhalb des Kommentars "# Disposing der verwendeten Grafiken". Auch hierfür wird eine eigene Methode verwendet. Denkt immer daran, dass ihr nicht sparsam mit Methoden umgehen müsst. Je mehr Methoden ihr verwendet, desto dynamischer und einfacher lässt sich der Code hinterher bearbeiten.

Am Ende sieht unsere Szene folgendermaßen aus:

class Scene_Test
  #--------------------------------------------------------------------------
  # * Ablauf der Szene (durch Main-Script aufgerufen)
  #--------------------------------------------------------------------------
  def main()
    # Initialisierung der Instanzvariablen
    lade_grafiken()
    # Ausführen des Überblendeffektes
    Graphics.transition
    # Hauptschleife
    loop do
      # Aktualisieren der Grafiken
      Graphics.update
      # Aktualisieren der Tasteneingabe
      Input.update
      # Aktualisieren der Instanzvariablen
      update
      # Abbruch der Schleife, wenn eine neue Szene beginnt
      if $scene != self
        break
      end
    end
    # Bildschirm einfrieren für Überblendeffekt
    Graphics.freeze
    # Disposing der verwendeten Grafiken
    loesche_grafiken()
  end
  #--------------------------------------------------------------------------
  # * Initialisierung der Instanzvariablen und Laden der Grafiken
  #--------------------------------------------------------------------------
  def lade_grafiken()
    @sprite = Sprite.new
    @sprite.bitmap = RPG::Cache.battler("001-Fighter01", 0)
  end
  #--------------------------------------------------------------------------
  # * Disposen der Grafiken
  #--------------------------------------------------------------------------
  def loesche_grafiken()
    @sprite.dispose
  end
 
end

Unsere Grafik wird oben links auf dem Bildschirm angezeigt. Wir wollen ihre Position selbst festlegen. Hierfür verwendet wir die Attribute x und y der Sprite-Klasse.

mein_sprite.x = 100 #X-Koordinate
mein_sprite.y = 100 #Y-Koordinate

Um einen Sprite zu bewegen, müssen wir diese Werte einfach nur regelmäßig verändern. Probieren wir es in unserer Szene einmal aus.

class Scene_Test
  #--------------------------------------------------------------------------
  # * Aktualisierung der Instanzvariablen
  #--------------------------------------------------------------------------
  def update
    @sprite.x += 1
    @sprite.y += 1
  end
end

Veränderungen an den Instanzvariablen schreiben wir grundsätzlich in die Methode Scene_Test#update, um später noch den Überblick behalten zu können. Unser Bild wird sich nun lansam von der oberen linken Ecke in die untere rechte bewegen. Wir können unseren Sprite auch drehen. Das Prinzip ist das gleiche wie bei der Änderung seiner Koordinaten. Diesmal verwenden wir das Attribut angle. Es gibt an, in welchem Winkel der Sprite gedreht werden soll. Dabei übergeben wir Fixnum- oder Float-Werte. 180.0 würde also einer halben Umdrehung entsprechen.

class Scene_Test
  #--------------------------------------------------------------------------
  # * Aktualisierung der Instanzvariablen
  #--------------------------------------------------------------------------
  def update
    @sprite.x += 1
    @sprite.y += 1
    @sprite.angle += 1
  end
end

Wir könnten den Sprite auch andersherum drehen lassen. Man müsste nur subtrahieren, statt addieren.

Standardmäßig dreht sich der Sprite immer um den obersten linken Pixel als Mittelpunkt. Warum it das so? Bei Sprites gibt es, zusätzlich zu den Koordinaten x und y noch die Ursprungskoordinaten ox und oy. Denken wir an die Pictures im Maker zurück. Dort gibt es im Show Picture Dialog die Schaltfläche "The Origin" (den Ursprung). Dort kann man sich zwischen "Center" und "Upper Left" entscheiden. Hierbei werden die Ursprungskoordinaten festgelegt. In Ruby haben wir natürlich nicht nur die Auswahl zwischen diesen zwei Punkten, sondern können beliebige Koordinaten auswählen.

Was bewirken die Ursprungskoordinaten? Nehmen wir als Hilfe wieder unsere Metapher mit dem Gemälde. Stellen wir uns vor wir wollen unser Gemälde (=den Sprite) im Zimmer aufhängen. Hierfür verwenden wir einen Nagel, der das Gemälde an die Wand befestigt. Die Position des Nagels sind unsere x/y-Koordinaten. Aber es genügt nicht den Nagel nur in die Wand zu schlagen. Das Gemälde muss ja auch daran befestigt sein. Wir können nun den Nagel in die Mitte des Gemäldes schlagen. Wir können das Gemälde aber auch mit einem Faden an einen weiter oben liegenden Nagel befestigen.

Die Position des Nagels im Gemälde sind unsere Ursprungskoordinaten. Drehen wir das Gemälde, so wird es um den Nagel herum gedreht. Der Mittelpunkt der Drehung ist also immer auch der Ursprungspunkt des Sprites.

Testen wir es einfach mal, in dem wir die Ursprungskoordinaten verändern:

class Scene_Test
  #--------------------------------------------------------------------------
  # * Initialisierung der Instanzvariablen und Laden der Grafiken
  #--------------------------------------------------------------------------
  def lade_grafiken()
    @sprite = Sprite.new
    @sprite.bitmap = RPG::Cache.battler("001-Fighter01", 0)
    @sprite.ox = 50
    @sprite.oy = 75
  end
end

Ihr könnt ja ein wenig mit dem Verändern der Koordinaten und Ursprungskoordinaten herumexperimentieren. Euch sollte bald auffallen, dass man auch mit den Ursprungskoordinaten die Position des Bildes verändern kann, man also auf die Variablen x/y gar nicht angewiesen ist.

Probieren wir was anderes. Wie wäre es, wenn der Spieler den Sprite mit der Tastatur selbst bewegen könnte?

class Scene_Test
  #--------------------------------------------------------------------------
  # * Aktualisierung der Instanzvariablen
  #--------------------------------------------------------------------------
  def update
    case Input.dir4
      when 2 then @sprite.y += 1
      when 4 then @sprite.x -= 1
      when 6 then @sprite.x += 1
      when 8 then @sprite.y -= 1
    end
  end
end

Wir können dem Sprite jederzeit ein anderes Bitmap zuweisen. Sprite und Bitmap sind unabhängig voneinander. Dem Sprite interessiert nicht, was er anzeigt.

Wir haben uns gerade mit vielen Setter und Getter Methoden, oder anders gesagt, Attributen der Sprite-Klasse beschäftigt. Dieses Wissen wollen wir noch vertiefen. Diesmal fangen wir aber mit der Bitmap Klasse an.

Wir haben anfangs erklärt ein Bitmap sei mit der Farbe auf einem Gemälde vergleichbar. In der Informatik könnte man auch sagen, ein Bitmap sei eine Tabelle aus Farbpixeln. Wir können mit der Bitmap#get_pixel(x,y) Methode jeden einzelnen dieser Farbpixel ansprechen. Als Parameter müssen wir nur die gewünschten Koordinaten angeben. Rückgabewert ist ein Farbobjekt der Klasse Color.

Die Color Klasse repräsentiert die Farben in Ruby. Sie besteht aus 4 Attributen: red (rot), green (grün), blue (blau), alpha (transparenz), wobei jedes Attribut einen Zahlenwert von 0 bis 255 enthalten darf. Die Farbe Orange beispielsweise lässt sich mit red=255, green=115, blue=0 darstellen. Man kann auch eigene Color-Objekte erzeugen:

meine_farbe = Color.new(red, green, blue, alpha)
#Beispiel: Farbe Orange
orange = Color.new(255, 115, 0)

Da alpha einen default-Wert von 255 hat, muss es nicht extra angegeben werden.

Diese Methode gibt z.B. an, welche Farbe sich auf den Koordinaten 50/50 befindet:

class Scene_Test
  def farbe?
    farbe = @sprite.bitmap.get_pixel(50,50)
    print("Die Farbe hat einen Rot-Wert von #{farbe.red}, einen Grün-Wert von #{farbe.green} und einen Blau-Wert von #{farbe.blue}")
  end
end

Bitmap#width und Bitmap#height sind zwei Getter-Methoden (das heißt man kann sie lesen, aber nicht verändern) der Bitmapklasse, welche die Weite und Höhe des Bitmaps ausgeben. Eine andere Methode, die praktisch das gleiche macht, ist Bitmap#rect. Sie gibt den Rect (Abkürzung für Rectangle, was soviel wie Rechteck heißt) des Bitmaps aus. Was ist ein Rect? Dieser Begriff spielt in allen Grafikklassen eine wichtige Rolle, weswegen wir ihn unbedingt behandeln sollten.

Ein Rect ist im Grunde genommen nur eine Klasse mit 4 Attributen: x, y, width und height. Sie stellt also ein Rechteck dar. x und y geben den oberen linken Startpunkt des Rechtecks vor, width und height die Seitenlängen. Rect ist keine Grafikklasse, die irgendetwas auf dem Bildschirm anzeigt. Stattdessen kann man Rect mit den Auswahlkästchen in einem Grafikprogramm (z.B. Paint) vergleichen, mit denen man einen Teil der Grafik auswählen, verschieben, löschen, umfärben etc. kann. Jede Grafik ist grundsätzlich blockförmig (auch wenn sie durch transparente Stellen unförmig erscheinen kann). Daher hat jede Grafik auch einen rect. Mit Bitmap#rect kann man also die Höhe und Breite des Bitmaps in einem rect erhalten.

mein_bitmap.width
#ist das gleiche wie
mein_bitmap.rect.width

Sprites haben einen sogenannten src_rect Attribut. Er gibt an, welcher Ausschnitt von dem Bitmap des Sprites auf dem Bildschirm angezeigt werden soll. Und er ist, wie der Name schon sagt, ein Rect. Momentan zeigen wir auf dem Bildschirm einen ganzen Battler an, nun aber wollen wir nur noch den Kopf des Battlers einblenden. Mit src_rect kein Problem:

Wir können probeweise ja mal ein Grafikprogramm öffnen, ein Auswahlrechteck um den Kopf der Battlergrafik ziehen und die Werte nachmessen. Ich habe das soeben getan und bin zu folgendem Ergebnis gekommen:

  • oberster linker Pixel des Kopfes bei den Koordinaten: (40/3)
  • unterster rechter Pixel des Kopfes bei den Koordinaten (102, 38)

Unser Rect beginnt also mit dem x Wert 40 und dem Y Wert 3 und zieht hat eine Länge von 102-40=62 und einer Höhe von 35. Nachdem wir unseren Sprite erstellt haben, weisen wir ihm also einen neuen src_rect zu:

class Scene_Test
  def lade_grafiken
    @sprite = Sprite.new
    @sprite.bitmap = RPG::Cache.battler("001-Fighter01", 0)
    @sprite.src_rect = Rect.new(40, 3, 62, 35)
  end
end
Nun wird auf dem Bildschirm nur noch der Kopf des Battlers angezeigt.

Diese Technik wird übrigens auch bei den Charaktersets benutzt. Je nachdem welcher Animationsschritt der Charakterbewegung gerade angezeigt werden soll, erhält der Sprite einen neuen src_rect.

Die Transparenz des Sprites können wir übrigens mit dem Attribut opacity einstellen. Der Wert 255 steht für 0% Transparenz, der Wert 0 für 100% Transparenz.

Es gibt noch viele weitere Attribute, die ihr in der Helpdatei nachlesen könnt. Als nächstes wollen wir uns aber damit beschäftigen, wie man Bitmaps selber zeichnen kann.

Grafiken selbst erstellen

Bisher haben wir Bitmaps über das RPG::Cache Modul geladen. Nun lernt ihr eine neue Methode zum Erzeugen von Bitmaps kennen:

mein_bitmap = Bitmap.new(laenge, breite)

Durch diesen Code wird ein neues, leeres Bitmap erzeugt. Nun ist ein leeres Bitmap naturgemäß völlig unnütz, daher werden wir uns nun damit befassen, wie wir selbst Inhalte in ein Bitmap zeichnen können.

Bitmap#set_pixel(x,y,color) ermöglicht das Platzieren eines Farbpixels auf dem Bitmap. Als Parameter müssen die Koordinaten und die gewünschte Farbe angegeben werden.

Bitmap#fill_rect(rect, color) funktioniert fast genauso, nur dass diese Methode einen ganzen Bereich mit einer Farbe ausfüllt. Als Parameter müssen statt den Koordinaten ein Rect und eine Farbe übergeben werden.

Zur Übung werden wir ein hübsches Hintergrundfenster für unseren Battler gestalten. Dieses Fenster wird sehr simpel aussehen und komplett in Ruby gezeichnet sein. Es besteht aus einem transparentem, blauem Rechteck, dessen Seitenkanten nicht transparent sind. Hierfür benötigen wir nur zwei fill_rect Methoden:

class Scene_Test
  def lade_grafiken()
    #Erzeuge den Sprite und weise ihm das Bitmap zu
    @sprite = Sprite.new
    @sprite.bitmap = Bitmap.new(120, 180)
    #Erzeuge die blaue Farbe und den Rect
    blaue_farbe = Color.new(25,25,255)
    rechteck = @sprite.bitmap.rect
    #Fülle nun das gesamte Bitmap mit der blauen Farbe aus
    @sprite.bitmap.fill_rect(rechteck, blaue_farbe)
    #Als nächstes wollen wir ein inneres Bitmap, an den Rändern je 1 Pixel kleiner, ausfüllen
    rechteck.x += 1
    rechteck.y += 1
    rechteck.width -= 2
    rechteck.height -= 2
    #Die Farbe soll auf dieser Fläche transparent sein
    blaue_farbe.alpha = 155
    #Zeichne die Innenfläche!
    @sprite.bitmap.fill_rect(rechteck, blaue_farbe)
    #Nun wird in die Box noch etwas hineingefügt!
    fuelle_box()
  end
 
  def fuelle_box()
    #aber das erst im nächsten Abschnitt ^^
  end
end

Zugegeben, auf schwarzem Hintergrund wirkt ein Transparenter Rahmen nicht sonderlich beeindruckend, aber unsere kleine Box sieht doch schon ganz gut aus. Nun wollen wir in die Box den Battler reinhaben.

Natürlich könnten wir dafür einen zweiten Sprite erstellen und ihm die Battlergrafik zuweisen, das wäre aber eine sehr unschöne Methode, denn dann müssten wir ja immer zwei Sprites statt einen bewegen. Stattdessen nutzen wir den sogenannten Blocktransfer.

Blocktransfer ist eines der wichtigsten Verfahren im Umgang mit Grafiken. Es ermöglicht es, einen Ausschnitt eines Bitmaps auf ein anderes Bitmap zu übertragen. Die Methode, die wir dabei nutzen, heißt Bitmap#blt(x,y,bitmap,ausschnitt).

Für x und y geben wir an, an welche Position das neue Bitmap auf das alte draufkopiert werden soll. ausschnitt gibt den Ausschnitt (Rect) des Bitmaps an, das kopiert werden soll. Probieren wir es bei unseren Battler einmal aus:

class Scene_Test
  def fuelle_box()
    grafik = RPG::Cache.battler("001-Fighter01", 0)
    @sprite.bitmap.blt(2,2,grafik, grafik.rect)
  end
end

Schon wird unser Battler in die kleine Box platziert. Leider steht er nicht ganz mittig. Das zu erreichen wird etwas kniffliger. Es gibt keine Methode die das automatisch macht, also müssen wir eben selbst einen derartigen Code schreiben:

class Scene_Test
  def fuelle_box()
    grafik = RPG::Cache.battler("001-Fighter01", 0)
    #Messe die unterschiedliche Länge und Höhe der Box und des Battlers
    laengenunterschied = @sprite.bitmap.rect.width - grafik.rect.width
    hoehenunterschied = @sprite.bitmap.rect.height - grafik.rect.height
    #Der richtige Abstand muss so gewählt sein, dass gegenüberliegende Kanten den
    #gleichen Abstand haben. Der Längenunterschied muss also halbiert werden!
    @sprite.bitmap.blt(laengenunterschied/2, hoehenunterschied/2, grafik, grafik.rect)
    schreibe_text() #folgt im nächsten Abschnitt
  end
 
  def schreibe_text()
 
  end
end

Nun wird unser Battler mittig platziert.

Wie haben wir das gemacht? An sich ist die Vorgehensweise sehr einfach. Unsere Box ist größer als der Battler in ihr. Damit der Battler mittig in der Box steht, muss der Abstand vom Battler zur Box an allen Seiten gleich lang sein. Wir messen also zuerst den Längenunterschied von Battler und Box. Danach halbieren wir diesen und verschieben den Battler per blit-Befehl um die halbe Länge. Nun ist der Abstand rechts vom Battler zur Box die halbe Differenz aus Boxlänge und Battlerlänge. Genauso der Abstand links. Dasselbe tun wir noch mit der Höhendifferenz.

Wem das jetzt zu schnell ging, der kann sich diese Gif-Animation anschauen, wo unser Vorgehen zum Zentrieren des Battlers in der Box dargestellt ist.

Ruby rgss gifani1.gif

Zuletzt wollen wir noch den Namen des Battlers in die Box schreiben. Hierfür gibt es die Bitmap#draw_text Methode. Wer sich noch an Rm2k-Zeiten erinnert, wo man jeden Text in einem Grafikprogramm entwerfen und importieren musste, wird diese Methode lieben. Denn Ruby kann Texte selbst zeichnen und auf den Bildschirm anzeigen.

Die draw_text Methode verlangt folgende Parameter:

  • x,y,width,height --> die den Rect des Textes festlegen, oder ein Rect-Objekt
  • den Text, der geschrieben werden soll, als String
  • als optionaler Parameter die Ausrichtung des Textes als Fixnum. 0 steht für links (standard!), 1 für mittig, 2 für rechts.

Um Texteinstellungen zu ändern, muss man auf das Font-Objekt zugreifen. Jedes Bitmap hat ein Font-Objekt, welches einstellt, wie der Text angezeigt werden soll. Auf den Font greifen wir mit Bitmap#font zu. Dort können wir verschiedene Einstellungen (in Form von Attributen) tätigen:

  • Font#size legt die Größe der Schrift fest. Tipp: Normalerweise ist die Höhe der Schrift in Pixeln gleich dem Font#size
  • Font#name legt den Schriftfont fest (z.B. Arial, Times New Roman, Verdana etc.). Wichtig: Es können nur Schriftarten ausgewählt werden, die auf dem jeweiligen Computer installiert sind
  • Font#bold legt fest, ob die Schrift fett gezeichnet werden soll (true) oder nicht (false)
  • Font#italic macht das Gleiche für kursive Schrift
  • Font#color hier kann man dem Font ein Color-Objekt zuweisen um die Schriftfarbe zu ändern
class Scene_Test
  def schreibe_text()
    #Der Text soll mittig sein, sich über die ganze Box erstrecken 
    #und am Boden der Box angezeigt werden
    texthoehe = @sprite.bitmap.font.size + 2 #sicherheitshalber erhöhen wir die Schrifthöhe nochmal um 2
    textrect = Rect.new(0, @sprite.bitmap.height-texthoehe, @sprite.bitmap.width, texthoehe)
    @sprite.bitmap.draw_text(textrect, "Alex", 1)
  end
end
Schon haben wir eine Box mit einem Battler und einem Schriftzug.
Läuft ja alles perfekt! Als nächstes werden wir ein kleines Heldenauswahlmenü erzeugen. Ihr werdet sehen, dass die Grundlagen nicht viel anders als bei Event Commands ablaufen - nur viel einfacher.

Zuvor noch zwei wichtige Anmerkungen:

Wenn ihr ein Bitmap per RPG::Cache holt, dürft ihr es nicht verändern! Wir haben am Anfang des Kapitels gesagt, dass wir zum Verändern stets neue Bitmaps erzeugen und die aus dem RPG::Cache geladenen Grafiken per Blocktransfer auf die Neuen draufkopieren.

Eine alternative Möglichkeit ist das Kopieren von Bitmaps. Hierfür verwendet man die Object#dup Methode.

ein_bitmap = RPG::Cache.picture("ein_picture").dup
ein_bitmap.fill_rect(Rect.new(5,5,10,10), Color.new(50,50,50))

In diesem Beispiel wollen wir ein Picture laden und auf dieses Picture ein kleines Rechteck zeichnen. Da wir die Grafiken aus dem RPG::Cache nicht verändern sollten, haben wir die dup Methode hinten drangehängt.

dup ist eine Methode, die ein Objekt kopiert. Sie funktioniert mit fast allen Objekten. Ihre genaue Einsatzweise besprechen wir jedoch ein andern Mal (im Kapitel über immediate Values).

Während Grafiken, die per RPG::Cache geladen werden, nicht disposed werden müssen, ist dies bei per Ruby erstellten (oder per dup duplizierten) Grafiken leider notwendig (sonst wird der Speicher unnötig belastet, was sich negativ auf die Performance auswirken könnte). Also immer dran denken, dass solche Bitmaps vollständig disposed werden!

class Scene_Test
  def loesche_grafiken()
    @sprite.bitmap.dispose
    @sprite.dispose
  end
end

Mehr zu den Variablen

Nun wollen wir endlich unser erstes, fertiges und verwendbares Script schreiben: Ein Auswahlmenü für den Startcharakter.

Es soll bei Spielbeginn eingeblendet werden. Der Spieler kann an dieser Stelle seinen Lieblingshelden auswählen und mit diesem das Spiel beginnen.

Globale Variablen der RGSS

Bevor wir mit dem eigentlichen Menü beginnen, müssen wir noch einige Vorbereitungen treffen. Das Menü soll nach dem Titelbildschirm angezeigt werden. Also schauen wir uns das Script Scene_Title an. Auffallend an dieser Klasse ist, dass viele globale Variablen definiert werden. Diese globalen Variablen enthalten so ziemlich alle wichtigen Informationen eures Spieles.

An dieser Stelle beispielsweise wird die ganze Database eingelesen. Alles was ihr im Maker in die Database geschrieben habt, ist in diesen globalen Variablen festgehalten:

# Load database
$data_actors        = load_data("Data/Actors.rxdata")
$data_classes       = load_data("Data/Classes.rxdata")
$data_skills        = load_data("Data/Skills.rxdata")
$data_items         = load_data("Data/Items.rxdata")
$data_weapons       = load_data("Data/Weapons.rxdata")
$data_armors        = load_data("Data/Armors.rxdata")
$data_enemies       = load_data("Data/Enemies.rxdata")
$data_troops        = load_data("Data/Troops.rxdata")
$data_states        = load_data("Data/States.rxdata")
$data_animations    = load_data("Data/Animations.rxdata")
$data_tilesets      = load_data("Data/Tilesets.rxdata")
$data_common_events = load_data("Data/CommonEvents.rxdata")
$data_system        = load_data("Data/System.rxdata")

All diese globalen Variablen sind Arrays, die allerdings mit dem Index 1 beginnen. Der Index 0 gibt nil zurück! Die Objekte in diesen Arrays gehören den Klassen RPG::Actor, RPG::Class, RPG::Class, RPG::Skill usw. an. Ihr findet den Aufbau dieser Klassen in der Helpdatei des Makers (RGSS Reference Manual/Game Library/RPGXP Data Structure).

Die Klassen aus der Database werden im Spiel selbst nicht zur Datenverarbeitung genutzt. Dazu sind die Game_ Klassen verantwortlich. Globale Variablen, die auf diese Game_Klassen zeigen sind neben $game_system die folgenden:

# Make each type of game object
$game_temp          = Game_Temp.new
$game_system        = Game_System.new
$game_switches      = Game_Switches.new
$game_variables     = Game_Variables.new
$game_self_switches = Game_SelfSwitches.new
$game_screen        = Game_Screen.new
$game_actors        = Game_Actors.new
$game_party         = Game_Party.new
$game_troop         = Game_Troop.new
$game_map           = Game_Map.new
$game_player        = Game_Player.new

Für unser Auswahlmenü wollen wir die ersten 3 Helden, die in der Database erstellt wurden, zur Auswahl geben. Wir brauchen den Namen der Kampfgrafik und den Namen des Heldens, um für jeden Helden eine solche Auswahlbox, wie sie im vorherigen Kapitel geschrieben wurde, zu erzeugen. Die $data_ Arrayelemente sind rein strukturelle Klassen, das heißt sie bestehen nur aus Attributen (Getter/Setter-Methoden). Wir können alle Daten von ihnen problemlos auslesen. Die Attribute finden wir in der Helpdatei, z.B. bei RPG::Actor.

#Hole den ersten Helden aus der Database
mein_held = $data_actors[1]
#Hole den Namen des Heldens
heldenname = mein_held.name
#Hole die Kampfgrafik des Heldens
heldengrafik = mein_held.battler_name

Parallelzuweisung von Variablen

An der Stelle wollen wir uns noch kurz mit einigen Kniffen und Tricks von Ruby beschäftigen, der sogenannten Parallelzuweisung.

Wir können in Ruby innerhalb einer Zeile mehrere Variablen Werte zuweisen. Das klappt folgendermaßen:

a, b, c = 1, 2, 3
print(a) #=> 1
print(b) #=> 2
print(c) #=> 3

Die erste Variable erhält den ersten Wert. Die zweite Variable, die der ersten nach einem Komma folgt, erhält den zweiten Wert usw.

Nun stellen 1,2,3 ja eine Aufzählung dar, also einen Array. Wir können also auch schreiben

array = [1,2,3]
a,b,c = array

Dabei kommt das gleiche wie im obigen Code heraus. Im nächsten Beispiel wollen wir unsere drei Helden aus dem $data_actors Array auslesen:

held1, held2, held3 = $data_actors[1..3]

Nun wird dem held1 der Actor mit dem Index 1 zugewiesen, dem held2 der Actor 2 usw.

Mit Hilfe der Parallelzuweisung können wir auch Methoden schreiben, die mehrere Rückgabewerte haben. z.B. eine Methode div(x,y) die zwei Werte dividiert und als Rückgabewert den Quotienten, sowie den Rest der Division hat.

def div(x, y)
  [x / y, x % y]
end
teiler, rest = div(9, 4)
print(teiler) #=> 2
print(rest) #=> 1

Im Grunde genommen hat die Methode immer noch einen Rückgabewert, der ein Array aus verschiedenen Werten ist. Aber über Parallelzuweisung können wir vielen Variablen die Einzelwerte aus dem Array zuweisen.

Es gibt noch einen weiteren Trick in diesem Zusammenhang. Manchmal kommt es vor, dass zwei Variablen ihre Werte austauschen sollen. Dies funktioniert mit dem Befehl:

a , b = 1, 2
a, b = b, a
print(a) #=> 2
print(b) #=> 1

In diesem Fall wird a der Wert b zugewiesen und b der Wert a.

Erstellen eines eigenen Menüs

Nun wollen wir aber endlich anfangen unser Charakterauswahlmenü zu schreiben. Als erstes müssen wir in der Scene_Title die Methode command_new_game aufsuchen. Dort in der letzten Zeile finden wir den Code

  1. $scene = Scene_Map.new

Diese Zeile legt fest, welche Szene nach dem Titelbildschirm gestartet wird. Wir wollen nicht direkt auf die Map geworfen werden, sondern unser Charakterauswahlmenü starten. Also ersetzen wir das Scene_Map durch Scene_Charakterauswahl.

Und hier ist auch schon das Menü: RGSS/Tutorials/Rubykurs 3 - RGSS CharakterAuwahl#Erstellen eines eigenen Menüs. Dieser Artikel enthält die vollständigen Codebeispiele für die folgenden Kapitel. Wir befinden uns gerade im Kapitel "Erstellen eines eigenen Menüs", ihr solltet also vorerst nur das Codebeispiel für eben dieses Kapitel betrachtet.

Ihr könnt es bei euch ausprobieren. Es sollte folgendermaßen aussehen:

Menu1.png

Zunächst werden wir den Code Schritt für Schritt durchgehen und vor allem alle neuen Funktionen besprechen. Danach werden wir den Code optimieren, denn er ist eigentlich ein Negativbeispiel, wie ein Menü möglichst nicht auszusehen hat. Dabei besprechen wir gleichzeitig, wie man seine Scripte designed, worauf man achten sollte und wie ein gutes Script aussehen sollte.

Vom Aufbau gleicht unser Code eigentlich der Scene_Test. Am Anfang werden Instanzvariablen initialisiert und die Methode lade_grafiken() aufgerufen, welche den Sprite erzeugt und ihm ein Bitmap zuweist.

@cursor ist dabei ein Fixnum, der die aktuelle Position des Cursors festlegt. Schließlich basiert jedes Menü darauf, dass man zwischen verschiedenen Menüpunkten etwas auswählen kann. Diesen Menüpunkte geben wir Nummern von 0 an. Der erste Menüpunkt hat also die Nummer 0, der Zweite die Nummer 1 usw. Der aktuell ausgewählte Menüpunkt wird in der Variable @cursor gespeichert. Wer mit Event-Code schon einmal ein Menü geschrieben hat, wird sich erinnern, dass dort das Prinzip genau das gleiche ist.

Das alles dürfte also nichts Unbekanntes sein. Neu ist hingegen sicherlich der Ausdruck

@charaktere = Array.new(4) do |index| $game_actors[index+1] end

Wir haben Arrays bisher stets mit array = [element1, element2, element3, ...] erstellt. Aber da ein Array nur eine Instanz der Klasse Array ist, kann man ihn natürlich auch mit dem Standardkonstruktor new erzeugen. Genauso wie man auch Strings, Ranges usw. mit String.new("inhalt"), Range.new(start, ende) usw. erzeugen kann.

Was aber kann Array.new, was die normale Schreibweise nicht kann? Nun, wir können Arrays noch leichter mit einem Inhalt füllen. Als Parameter geben wir an, wie viele Elemente der neue Array bekommen soll. In den Block schreiben wir den Code, der den Inhalt bestimmt. Dieser Block bekommt als Parameter den aktuellen Index.

Beispiel: Wir wollen einen Array mit 20 Elementen, der mit Zufallszahlen gefüllt wird.

mein_array = Array.new(20) do rand(101) end

Diese Codezeile erzeugt einen Array mit 20 zufälligen Zahlen von 0 bis 100. Der mitgelieferte Block wird 20 Mal ausgeführt und dessen Ergebnis dem Array dabei jedes Mal als Element mitgeliefert. Wir könnten also genauso schreiben:

mein_array = []
20.times do mein_array << rand(101) end

Die Array.new Methode eignet sich auch in unserem Fall wunderbar, um den Array mit Elementen zu füllen. Statt zu schreiben:

@charaktere = [$game_actors[1], $game_actors[2], $game_actors[3], $game_actors[4]]

nutzen wir die Array.new Methode. Dabei wird das erste Element des Arrays mit dem ersten Element von $game_actors gefüllt usw.

Warum benutzen wir nicht @charaktere = $game_actors[1..4]? Nun, das liegt daran, das $game_actors kein Objekt der Klasse Array, sondern ein Objekt der Klasse Game_Actors ist. Und diese Klasse beherrscht nicht die Zuweisung über Ranges.

In unserer lade_grafiken() Methode wird der Sprite erzeugt, ihm ein Bitmap zugewiesen und der Sprite dan auf dem Bildschirm positioniert. Danach erfolgt die refresh Methode. Warum verwenden wir hier einen englischen Namen? Nun, alle Menüklassen in der RGSS enthalten eine refresh() Methode, die den Inhalt des Menüfensters neu anzeigt. Natürlich könnten wir die Methode auch anders nennen, aber es ist immer sehr sinnvoll bei den bestehenden Gepflogenheiten zu bleiben. Genauso wie wir unsere Klasse Scene_CharakterAuswahl, statt einfach nur Charakterauswahl genannt haben. Ebenso wäre es unklug, die update Methode in aktualisieren() zu benennen.

  1. def refresh()
  2.   x=0
  3.   @charaktere.each_index do |index|
  4.     zeige_auswahlbox(@charaktere[index], x, (index==@cursor))
  5.     x+= 160
  6.   end
  7. end

Die refresh() Methode dürfte eigentlich leicht verständlich sein. Wir iterieren den Array mit unseren Charakteren und rufen für jeden Charakter die zeige_auswahlbox() Methode auf. Auf zwei Dinge möchte ich dennoch aufmerksam machen:

Wir iterieren den Array hier nicht mit der Methode Array#each, sondern mit Array#each_index. Während die Array#each Methode dem mitgelieferten Block ihre Elemente als Parameter übergibt, gibt Array#each_index den Index ihrer Elemente an den Block. Im Grunde genommen läuft die Methode genauso ab wie diese Alternative:

(0..(@charaktere.size-1)).each do |index| ... end

Warum iterieren wir überhaupt den Index? Nun, wir wollen das Auswahlkästchen, welches gerade ausgewählt ist, weiß statt blau malen. Damit die Methode zeige_auswahlbox() auch weiß, in welcher Farbe sie ihr Kästchen malen soll, übergeben wir ihr als Parameter das Ergebnis der Aussage (index==@cursor).

index ist der Index, der gerade iteriert wird. @cursor enthält die Nummer des gerade ausgewählten Kästchens. Wenn beide Werte gleich sind, das zu zeichnende Kästchen also ausgewählt wird, dann wird true als Parameter übergeben. Nun wird das Kästchen auch weiß gezeichnet. Bei false wird es hingegen blau gemalt.

Die andere Sache, auf die ich aufmerksam machen möchte, ist das x. Jedes Kästchen ist 150 Pixel breit. Damit die Kästchen einen Abstand voneinander haben, wird jedes Kästchen 160 Pixel weit vom anderen entfernt gezeichnet. Damit entsteht ein Abstand von 10 Pixeln zwischen den Kästchen. Wir lösen dieses Problem, in dem wir die Variable x anfangs auf 0 setzen und dann nach dem Zeichnen jedes Kästchens um 160 erhöhen. Das x liefern wir der zeige_auswahlboy() Methode als Parameter, damit sie weiß, wo das Kästchen gezeichnet werden muss.

Wichtig ist, dass das x außerhalb des Blocks auf 0 gesetzt wird. Sonst würde der Block das x nicht kennen, würde es für eine Methode halten und einen NoMethodError ausgeben.

An der Stelle ein genereller Tipp: Lokale Variablen sollten soweit es geht immer außerhalb des Blocks einen Wert zugewiesen bekommen - und wenn es nur nil oder 0 ist. Wir haben gelernt, dass eine lokale Variable nur innerhalb ihres Blocks gültig ist. Ein Iteratorblock ist ebenfalls ein eigener Codeblock. Lokale Variablen, die hier drin erzeugt werden, sind außerhalb des Blocks nicht gültig.

2.times do r=rand(10) end
print(r) #NoMethodError

In diesem Beispiel sieht man deutlich, dass eine Variable, die innerhalb eines Blocks zum ersten Mal vorkommt, auch nur innerhalb des Blocks verwendet werden darf. Würden wir stattdessen schreiben:

r = 0
2.times do r=rand(10) end
print(r) #=> Eine Zufallszahl von 0 bis 9

dann würde r nicht zum Block, sondern zur Methode gehören. Dadurch kann r innerhalb der Methode und innerhalb des Blocks überall aufgerufen und verändert werden.

Schauen wir uns als nächstes an, wie die Auswahlboxen gezeichnet werden:

  1. def zeige_auswahlbox(charakter, x, ausgewaehlt=false)
  2.   #Erzeuge die Farbe und den Rect
  3.   if ausgewaehlt then
  4.     farbe = Color.new(255,255,255)
  5.   else
  6.     farbe = Color.new(25,25,255)
  7.   end
  8.   rechteck = Rect.new(x,0,150,200)
  9.   #Fülle nun das gesamte Bitmap mit der blauen Farbe aus
  10.   @sprite.bitmap.fill_rect(rechteck, farbe)
  11.   #Als nächstes wollen wir ein inneres Bitmap, an den Rändern je 1 Pixel kleiner, ausfüllen
  12.   rechteck.x += 1
  13.   rechteck.y += 1
  14.   rechteck.width -= 2
  15.   rechteck.height -= 2
  16.   #Die Farbe soll auf dieser Fläche transparent sein
  17.   farbe.alpha = 155
  18.   #Zeichne die Innenfläche!
  19.   @sprite.bitmap.fill_rect(rechteck, farbe)
  20.   #Zeichne die Charaktergrafik mittig(!) in die Box
  21.   grafik = RPG::Cache.battler(charakter.battler_name, 0)
  22.   grafik_x = (rechteck.width - grafik.width) / 2 + x
  23.   grafik_y = (rechteck.height - grafik.height) / 2
  24.   @sprite.bitmap.blt(grafik_x, grafik_y,grafik, grafik.rect)
  25.   #Schreibe nun den Namen des Charakters in die Box
  26.   #Der Text soll mittig sein, sich über die ganze Box erstrecken 
  27.   #und am Boden der Box angezeigt werden
  28.   texthoehe = @sprite.bitmap.font.size + 2 #sicherheitshalber erhöhen wir die Schrifthöhe nochmal um 2
  29.   textrect = Rect.new(x, rechteck.height-texthoehe, rechteck.width, texthoehe)
  30.   @sprite.bitmap.draw_text(textrect, charakter.name, 1)
  31. end

Obwohl die Methode sehr lang ist, dürfte schnell auffallen, dass ich einfach nur die Methoden unsere Scene_Test wiederverwertet habe. Nur wenige Änderungen wurden gemacht.

Da wäre einmal, dass am Anfang ausgewählt wird, welche Farbe zum Zeichnen der Box verwendet wird.

  1. if ausgewaehlt then
  2.   farbe = Color.new(255,255,255)
  3. else
  4.   farbe = Color.new(25,25,255)
  5. end

Der Code dürfte eindeutig sein. Die lokale Variable ausgewaehlt ist der Parameter, den wir in der refresh Methode zuvor mit (index==@cursor) zugewiesen haben. Wir können die Codezeilen aber noch etwas verkürzen. Dabei müssen wir uns nur in Erinnerung rufen, dass in Ruby jede Funktion einen Wert zurückgibt. Das gilt nicht nur für Methoden, sondern auch für Schleifen, If-Sätze und alles andere. Ein If-Satz gibt immer den Wert zurück, der als letztes in ihm ausgeführt wurde (also genauso wie Methoden). Wir können also auch schreiben:

  1. farbe = if ausgewaehlt then Color.new(255,255,255) else Color.new(25,25,255) end

In diesem Fall wird der Variable farbe immer das Objekt zugewiesen, welches im If-Satz zuletzt aufgerufen wurde. Wenn ausgewaehlt true ist, so gibt der If-Satz Color.new(255,255,255) (also weiß) als Wert zurück. Ansonsten Color.new(25,25,255) (blau).

In der Zeile darunter erhält unser Rect rechteck seine Maße zugewiesen.

  1. rechteck = Rect.new(x,0,150,200)

Wir erinnern uns: Die Box wird später mit fill_rect gezeichnet. Dabei wird die Fläche des rechtecks mit der Farbe ausgefüllt. Damit die Rechtecke nicht alle auf der gleichen Position angezeigt werden, haben wir statt einer Zahl als ersten Parameter die lokale Variable x überwiesen. Diese ist der Parameter, den wir in der refresh() Methode übergeben haben und der die Position der Box angibt.

Die Box wird also von den Koordinaten x/0 bis zu den Koordinaten x+150/200 gezeichnet. Wobei x bei der ersten Box 0 ist und bei jeder weiteren Box um 160 Pixel höher.

  1. grafik = RPG::Cache.battler(charakter.battler_name, 0)
  2. grafik_x = (rechteck.width - grafik.width) / 2 + x
  3. grafik_y = (rechteck.height - grafik.height) / 2
  4. @sprite.bitmap.blt(grafik_x, grafik_y,grafik, grafik.rect)

An der Stelle wird Grafik des Battlers in die Box gezeichnet. Da natürlich jeder Charakter eine andere Grafik hat, verwenden wir auch hier eine Variable. charakter ist woeder ein Parameter, der in der refresh() Methode überliefert wurde. Er ist das gerade iterierte Element aus dem @charakter Array und gibt also die Informationen über den Charakter an. Das Objekt ist eine Instanz der Klasse Game_Actor. Wir führen die Game_Actor#battler_name Methode aus, die uns den Dateinamen des Charakterbildes gibt. Diese wird dann per RPG::Cache geladen und später mit Blocktransfer in unsere Box gezeichnet. Damit der Battler in der Mitte der Box angezeigt wird, subtrahieren wir die Gesamtlänge und -höhe einer Box mit der Länge und Höhe des Charakterbildes, dividieren diese durch 2 und haben so den Abstand, den das Bild von den Rändern der Box haben muss.

textrect = Rect.new(x, rechteck.height-texthoehe, rechteck.width, texthoehe)
@sprite.bitmap.draw_text(textrect, charakter.name, 1)

Zuletzt wird noch der Name des Charakters geschrieben. Das Rechteck, welches die Position und Größe des Textes festlegt, richtet sich weitgehend nach der Auswahlbox. Als X-Koordinate wird wieder unsere lokale Variable x verwendet. Die Höhe ermitteln wir, wie auch in unserer Scene_Test aus der Differenz des rects der Auswahlbox und der Texthöhe, um den Text genau am Fuße der Auswahlbox anzuzeigen.

Bei der Bitmap#draw_text Methode übergeben wir als String natürlich den Namen des Helden, den wir mit der Methode Game_Actor#name erhalten.

Das war auch schon alles, was zum Zeichnen der Auswahlboxen nötig war. Nun gilt es nur noch, den Cursor zu steuern. Das machen wir in der Methode update().

  1. def update
  2.   if Input.trigger?(Input::RIGHT) then
  3.     if (@cursor += 1) > 3 then @cursor = 0 end
  4.     refresh
  5.   elsif Input.trigger?(Input::LEFT) then
  6.     if (@cursor -= 1) < 0 then @cursor = 3 end
  7.     refresh
  8.   elsif Input.trigger?(Input::C)
  9.     $game_party.actors.clear
  10.     $game_party.actors << @charaktere[@cursor]
  11.     $game_player.refresh
  12.     $scene = Scene_Map.new
  13.   end
  14. end

Der Code dürfte weitgehend verständlich sein. Wir fragen in einem If-Satz mit Input.trigger? die gedrückten Tasten ab. Wenn Rechts gedrückt wird, soll der @cursor um 1 erhöht werden. Bei Links um 1 verringert. Ein weiterer If-Satz stellt sicher, dass die Variable @cursor nie außerhalb des Bereichs von 0 bis 3 gelangen kann.

Bei Enter soll der ausgewählte Charakter in die Heldentruppe kommen und danach das Spiel beginnen. An der Stelle noch mal ein kleiner Exkurs zum Thema: Suchen von bestimmten Methoden. Es ist unmöglich alle Methoden der RGSS zu kennen. Wenn man eine bestimmte Funktion sucht, sollte man erst die Klasse suchen, die dafür zuständig ist.

Wir haben Grafikklassen (Sprite_, Spriteset_, Arrow_, Window_), Datenklassen (Game_) und Steuerklassen für das Spielgeschehen (Scene_) kennengelernt. Von Grafikklassen lassen wir grundsätzlich die Finger. Grafikklassen sollten nur von den Scene-Klassen gesteuert werden (und auch dort am besten nur über die update-Methode), von niemandem sonst. Die Scene-Klassen sollten in sich einen geschlossenen Kreislauf bilden, in dem wir uns ebenfalls nicht einmischen sollten. Die einzigsten Klassen, die man von überall aus beeinflussen kann und sollte, sind Game-Klassen. Von denen gibt es nämlich meistens auch eine globale Variable, wie $game_actors, $game_party, §game_player usw.

Wir wollen nun die Besetzung der Heldentruppe ändern. Also suchen wir nach der Game-Klasse Game_Party. Dort finden wir ganz oben die Setter-(attr_writer) und Gettermethoden (attr_reader) stehen. Darunter auch Game_Party#actors, ein Array, der die Game_Actor Objekte der Heldentruppe zurückgibt. Es gibt aber auch eine Game_Party#add_actor und eine Game_Party#remove_actor.

In unserem Menü leeren wir mit der Array#clear Methode (die alle Elemente aus einem Array löscht) den @actors Array der Heldentruppe und fügen mit Array#<< ein neues Game_Actor Element dem Array hinzu. Dieses Verfahren funktioniert zwar, wir sollten aber dennoch die add_actor und remove_actor Methoden verwenden. Der Grund ist, dass diese Methoden nicht nur den Array @actors verwalten, sondern auch alle anderen Aufgaben ausführen, die beim Ändern der Heldentruppe erforderlich sind.

Zum Beispiel wird auf der Map immer nur der erste Held der Truppe als Spielercharakter angezeigt. Wenn sich dieser erste Held ändert, muss der Spielercharakter mit der Methode $game_player.refresh neu geladen werden. Die add_actor Methode macht dies automatisch, wir müssen das erst noch manuell hinten dran schreiben.

Nun gut, wir haben alles Neue an diesem Menü durchgesprochen. Als nächstes beschäftigen wir uns, wie man den Code optimieren kann.

Zum nächsten Kursabschnitt