Informatik

Im Spiel: ein Typ für alle Objekte

Am Ende der Einführungsphase hast du ein eigenes Spiel gebaut. In dieser Lektion baust du daran weiter – mit den Werkzeugen dieses Kapitels.

Neu ist hier nichts. Generalisierung, Polymorphie, abstrakte Klassen und Schnittstellen hast du in 1.3 bis 1.6 kennengelernt. Was hier dazukommt, ist die Gelegenheit, sie alle am selben Stück Code hintereinander einzusetzen und dabei zu sehen, was jedes einzelne davon wirklich bewirkt.

Optional. Diese Seite ist ein Zusatzangebot am Ende des Kapitels. Sie führt nichts Neues ein, und weder der Rückblick noch die späteren Kapitel setzen sie voraus. Wenn du sie überspringst, fehlt dir nichts.

So arbeitest du mit dieser Seite. Der Quelltext ist jedes Mal vollständig da. Du baust nichts von vorn – du liest, veränderst und erweiterst. Die interessante Frage ist nie „läuft es?", sondern immer: „Was musste ich dafür anfassen?"

Scratch for Java in fünf Minuten

Diese Seite macht einen Entwurf mit der Grafikbibliothek Scratch for Java sichtbar. Du musst sie nicht kennen – hier steht alles, was du dafür brauchst. Geladen wird sie über libraries="scratch"; import-Zeilen gibt es nicht.

Begriff Was er bedeutet
Stage die Bühne, 480 × 360 Pixel. Der Punkt (0, 0) liegt in der Mitte, die y-Achse zeigt nach oben. Man schreibt eine eigene Klasse, die von Stage erbt.
Sprite eine Figur auf der Bühne. Auch dafür schreibt man eigene Klassen, die von Sprite erben.
addCostume("coin_gold") gibt einer Figur ihr Bild. Die Bibliothek bringt über 1700 fertige Kostüme mit.
this.add(objekt) fügt eine Figur oder einen Text der Bühne hinzu. Was nicht hinzugefügt wurde, sieht man nicht.
run() wird für jedes Objekt etwa 60-mal pro Sekunde aufgerufen. Alles Fortlaufende steht hier drin.
getTouchingSprite(Muenze.class) liefert das berührte Objekt oder null, wenn gerade keins berührt wird. Das null muss man immer abfragen.

Dazu isKeyPressed(KeyCode.RIGHT) für die Tastatur, changeX(4) zum Bewegen, remove() zum Entfernen und new Text(text, x, y, breite) mit showText(...) für die Anzeige.

Zum Nachlesen: Die erste Grafik erklärt Bühne und Koordinatensystem, Eigene Sprites die eigenen Figurenklassen mit run(). Alle Methoden auf einen Blick: Referenz.

Woran es hakt

Im Gerüst aus der Einführungsphase gab es genau eine Sorte einzusammelbarer Dinge. Ein Spiel, das Spaß macht, hat mehr: Münzen bringen Punkte, Herzen geben Leben zurück, Fallen kosten welche.

Unten läuft genau das. Steuere den Hasen mit den Pfeiltasten und sammle ein, was du findest.

Das Spiel funktioniert. Der Entwurf nicht.

Aufgabe 1: Was kostet eine neue Sorte?

a) Du möchtest einen Schlüssel einbauen, der 50 Punkte bringt. Zähle auf, welche Dateien du dafür anfassen musst.

b) Wie viele Zeilen von Spieler.run() handeln von der Steuerung – und wie viele davon, was andere Objekte tun?

c) In 1.4 stand ein Warnzeichen für einen schlechten Entwurf. Welches siehst du hier?

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Stufe 1: eine gemeinsame Oberklasse

Die Frage aus 1.3 lautet: Was haben Münze, Herz und Falle gemeinsam?

Am Aussehen liegt es nicht – ein Kostüm ist keine Gemeinsamkeit. Am Verhalten auch nicht: Die drei tun ja gerade Verschiedenes. Gemeinsam ist ihnen der Anlass: Alle drei tun etwas, wenn der Spieler sie berührt.

Nach oben gehört nicht, was gleich ist, sondern was jede Unterklasse beantworten muss. Die Frage „Was geschieht bei Berührung?" stellt sich für alle drei – nur fällt die Antwort jedes Mal anders aus.

Das ergibt eine Oberklasse mit genau einer Methode:

public class Spielobjekt extends Sprite {
 
    public Spielobjekt(String pKostuem, int pX, int pY) {
        this.addCostume(pKostuem);
        this.setPosition(pX, pY);
    }
 
    /** Was geschieht, wenn der Spieler dieses Objekt berührt? */
    public void beruehrtVon(Spieler pSpieler) {
    }
}

Und damit schrumpft die Kollisionsprüfung in Spieler.run() auf drei Zeilen – für immer, egal wie viele Sorten noch dazukommen:

Spielobjekt getroffen = this.getTouchingSprite(Spielobjekt.class);
if (getroffen != null) {
    getroffen.beruehrtVon(this);
}

Das ist Polymorphie: Der Aufruf sieht immer gleich aus, ausgeführt wird die Fassung des Objekts, das tatsächlich getroffen wurde.

Stufe 2: was der Übersetzer erzwingt

Die Fassung oben hat eine Lücke. beruehrtVon hat in Spielobjekt einen leeren Rumpf. Wer eine neue Sorte schreibt und die Methode zu überschreiben vergisst, bekommt keine Fehlermeldung – das Objekt liegt einfach für immer im Weg herum und tut nichts. Man sucht den Fehler dann im Spiel statt im Quelltext.

Genau dafür gibt es abstrakte Klassen. Ein abstract an zwei Stellen macht aus dem stillen Fehler einen Übersetzungsfehler.

Drei Dinge sind dabei passiert, und jedes hat einen Namen aus diesem Kapitel:

  • Die Fallunterscheidung ist aus Spieler verschwunden. Sie steckt jetzt in der Vererbungshierarchie – jede Sorte weiß selbst, was sie tut (1.4).
  • Spielobjekt ist abstrakt. new Spielobjekt(...) lehnt der Übersetzer ab, und wer beruehrtVon vergisst, erfährt es beim Übersetzen statt beim Spielen (1.5).
  • Der Wert einer Münze steht als Konstante dort, wo er hingehört: in Muenze (1.2).

Aufgabe 2: die Probe aufs Exempel

a) Baue den Schlüssel ein: ein Objekt mit dem Kostüm keyBlue, das bei Berührung 50 Punkte gibt und verschwindet. Bedingung: Du darfst Spieler.java nicht anfassen.

b) Wie viele Dateien hast du geändert oder angelegt? Vergleiche mit deiner Antwort aus Aufgabe 1a.

c) Baue eine Bombe, die nicht verschwindet, sondern bei jeder Berührung ein Leben kostet. Welche Zeile aus Falle lässt du weg?

d) Lösche in Muenze die Methode beruehrtVon und übersetze. Welche Meldung kommt – und warum ist sie eine gute Nachricht?

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Stufe 3: eine Fähigkeit quer zur Hierarchie

Nur Leistungskurs. Dieser Abschnitt setzt 1.6 Schnittstellen voraus.

Das Spiel soll beim Aufbau vorrechnen, was überhaupt zu holen ist. Münzen und Schlüssel gehören in diese Rechnung – und ein Zeitbonus für die Zeit, die am Ende übrig bleibt.

Der Zeitbonus ist das Problem. Er ist kein Spielobjekt, er liegt nicht auf der Bühne, er hat kein Kostüm. Trotzdem soll er in derselben Abrechnung auftauchen. Eine gemeinsame Oberklasse gibt es dafür nicht: Muenze erbt schon von Spielobjekt, und ein Zeitbonus ist kein Sprite.

Genau das ist der Fall aus 1.6: Punkte zu bringen ist eine Fähigkeit, keine Art.

Falle ist dabei die aufschlussreichste Klasse: Sie ist ein Spielobjekt wie die anderen, aber nicht punktebringend – und muss deshalb wert() und bezeichnung() auch nicht anbieten. Eine Oberklasse hätte ihr beides aufgezwungen.

Aufgabe 3: Schnittstelle beurteilen (nur LK)

a) Warum steht Punktebringend als Schnittstelle da und nicht als abstrakte Klasse zwischen Sprite und Spielobjekt? Nenne zwei Gründe.

b) Zeitbonus hat kein Kostüm und liegt nicht auf der Bühne. Welche Zeile im Programm wäre nicht übersetzbar, wenn Punktebringend eine abstrakte Klasse wäre?

c) Ergänze einen zweiten Posten ohne Sprite: einen Lebensbonus, der pro übrigem Leben 25 Punkte bringt. Welche Dateien fasst du an?

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Und ohne Spiel?

Der Umbau auf dieser Seite ist kein Trick für Spiele. Er ist das Muster, nach dem in diesem Kapitel jede Aufgabe gebaut ist – nur fällt es hier schneller auf, weil man den Fehler sieht.

Im Spiel In den Aufgaben dieses Kapitels
Spielobjekt mit beruehrtVon Form mit flaeche(), Sensor mit messwert()
Münze, Herz, Falle Kreis und Quadrat, Thermometer und Hygrometer
if-Kette nach Sorten in Spieler if-Kette nach Fahrzeugtypen in der Mautstelle
Punktebringend quer zur Hierarchie Bezahlbar für Angestellte und Stromrechnungen

Wer in der Klausur vor einer Fahrzeugverwaltung sitzt, beantwortet dieselbe Frage wie hier: Welche Frage muss jede Unterklasse beantworten – und wo steht die Antwort?

Zum Weiterdenken: Spieler ist bisher kein Spielobjekt. Wäre es einer, könnten Gegner den Spieler genauso berühren wie er sie.

Was spricht dafür, was dagegen? Prüfe mit dem Satztest aus 1.3 – und überlege, was beruehrtVon beim Spieler überhaupt bedeuten würde.


Selbsttest

Im Spiel: ein Typ für alle Objekte

Teilbare URL erstellen

Abschnitte auswählen