Home > Informatik > Stufe EF > Folge 6

6.1 Einen Roboter zeichnen (Neu 11/2021)

Schritt 1 - Die Klasse Roboter erzeugen

⇒ Starten Sie BlueJ und sichern Sie das letzte Projekt aus Folge 5 unter einem neuen Namen. Das ist wichtig, damit Sie die Klassen Circle und Head zur Verfügung haben, die Sie in Folge 5 implementiert haben.

⇒ Erstellen Sie dann in diesem Projekt eine neue Klasse Roboter. Sie können sich den folgenden Quelltext über die Zwischenablage kopieren:

import java.awt.*;

public class Roboter
{
    Head kopf;
    Rectangle rumpf, basis;
    Circle wheel1, wheel2;
    
    public Roboter()
    {
        kopf   = new Head(250,100,30);
        rumpf  = new Rectangle(230,130,40,80,new Color(191,191,223));
        basis  = new Rectangle(210,210,80,20,new Color(191,191,207));
        wheel1 = new Circle(220,240,10,new Color(95,95,111));
        wheel2 = new Circle(280,240,10,new Color(95,95,111));
    }
    
    public void paint(Graphics g)
    {
       kopf.paint(g);
       rumpf.paint(g);
       basis.paint(g);
       wheel1.paint(g,true);
       wheel2.paint(g,true);
    }
}

Es handelt sich um die erste Version der Klasse Roboter. Eine Anwendung ist das natürlich noch nicht; Sie müssen erst selbst einige Dinge erledigen. Wenn Sie mit den folgenden Übungen fertig sind, müsste der Roboter, der von der entsprechenden Anwendung erzeugt wird, so aussehen:

So müsste der Roboter aussehen, wenn Sie den Quelltext zum Laufen gebracht haben.
Autor: Ulrich Helmich 2021, Lizenz: siehe Seitenende

Übung 6.1-1

Schauen Sie sich noch einmal den Quelltext der Klasse Circle an und implementieren Sie dann eine ähnliche Klasse Rectangle, welche Rechtecke mit einem dünnen schwarzen Rand zeichnet. Die benötigten Parameter können Sie dem obigen Quelltext entnehmen.

Für Profis: Wenn Sie wollen, können Sie die Klasse Rectangle natürlich noch flexibler machen. Über zwei weitere Parameter könnte man beispielsweise die Randstärke und die Randfarbe festlegen.

Lösungsvorschlag (nur für Lehrpesonen, Zugangsdaten erforderlich).

Übung 6.1-2

Verändern Sie den Quelltext der Java-Anwendung so, dass Sie die Klasse Roboter testen können.

Lösungsvorschlag (nur für Lehrpesonen, Zugangsdaten erforderlich).

Aufgabe 6.1-3

Zeichnen Sie ein Implementationsdiagramm für das neue Projekt, in dem alle fünf Klassen berücksichtigt werden: Circle, Rectangle, Head, Roboter und Anwendung.

Lösungsvorschlag (nur für Lehrpesonen, Zugangsdaten erforderlich).

Schritt 2 - variable X-Position

Wenn wir den Roboter mit Hilfe von Buttons nach rechts oder links bewegen wollen, bringt es natürlich gar nichts, wenn wir die X-Position des Roboters festlegen. Beim Fahren nach links muss die X-Position kontinuierlich verringert werden, beim Fahren nach rechts muss sie erhöht werden. Die Größe des Roboters und die Y-Position kann zunächst einmal beibehalten werden.

Und damit wären wir schon bei Ihren nächsten Aufgaben:

Übung 6.1-4

Verändern Sie den Quelltext der Klasse Roboter so, dass der X-Wert der Startposition als Parameter übergeben werden kann.

Wenn Sie diese Übung erfolgreich erledigt haben, können Sie Ihre Anwendung ja so verändern, dass drei verschiedene Roboter an drei verschiedenen X-Positionen gleichzeitig angezeigt werden:

Drei verschiedene Roboter-Objekte an unterschiedlichen X-Positionen
Autor: Ulrich Helmich 2021, Lizenz: siehe Seitenende

Schritt 3 - Eine Anwendung mit Buttons

Wir wollen nun unsere Anwendung mit zwei Buttons ausstatten. Wenn man auf den linken Button klickt, soll sich der Roboter um 20 Pixel nach links bewegen. Beim Klick auf den rechten Button soll eine entsprechende Bewegung um 20 Pixel nach rechts ausgelöst werden.

Als erstes kleines Ziel nehmen wir uns aber erstmal vor, die Anwendung lediglich um zwei - noch funktionslose - Buttons zu bereichern.

⇒ Betrachten Sie dazu den folgenden minimalen Quelltext und kopieren Sie diesen über die Zwischenablage in eine neue Java-Klasse in Ihrer Anwendung:

import java.awt.*;
import javax.swing.*;

public class Anwendung2 extends JFrame
{
    Button ok;
    
    public Anwendung2()
    {
        setSize(500,500);
        setTitle("Eine Java-Anwendung mit einem Button");
        setResizable(false);
        setVisible(true);
        initComponents();
    }
   
    public void initComponents()
    {
        ok = new Button("OK");
        setLayout(null);
        ok.setBounds(50,170,100,40);
        ok.setFont(new Font("Arial",1,18));        
        add(ok);
    }
    
    public static void main(String[] args) 
    {
        new Anwendung2();
    }
}

Diese Anwendung zeigt nach dem Starten einen mit "OK" beschrifteten Button. Zunächst muss das Button-Objekt deklariert werden.

Button ok;

In der Methode initComponents() wird der Button nun initialisiert:

ok = new Button("OK");

Bei dieser Initialisierung wird die Beschriftung des Buttons festgelegt und als Parameter übergeben.

Dann kommt der Befehl

setLayout(null);

der dafür sorgt, dass man Java-Komponenten pixelgenau in der Anwendung platzieren kann. Diese pixelgenaue Positionierung erfolgt dann mit dem setBounds()-Befehl:

ok.setBounds(50,170,100,40);

Die vier Parameter legen die Position der linken oberen Ecke des Buttons fest sowie seine Breite und seine Höhe.

Wenn man die Schriftart und -größe ändern will, die auf dem Button zu sehen ist, muss man das mit dem Befehl setFont() regeln. Hier wurde Arial, Normaldarstellung, Größe 18 gewählt.

Die Anwendung hat zwar nun einen Button, "weiß" aber noch nichts davon. Das regelt dann der Befehl

add(ok);

Erst jetzt hat die Anwendung die Komponente ok in ihre Komponentenliste aufgenommen.

Aufgabe 6.1-5

Ergänzen Sie Ihre Roboter-Anwendung um zwei Buttons im unteren Bereich der Anwendung. Der linke Button soll mit "links", der rechte mit "rechts" beschriftet sein.

Schauen Sie sich dazu gern die "komplexere Java-Anwendung" an, die genau für solche Zwecke bereitgestellt wurde.

Lösungsvorschlag (nur für Lehrpesonen, Zugangsdaten erforderlich).

Ein Roboter mit zwei Buttons
Autor: Ulrich Helmich 2021, Lizenz: siehe Seitenende

So ungefähr müsste Ihre Anwendung jetzt aussehen, wenn Sie den Roboter mit einem X-Wert von 250 initialisiert haben.

Schritt 4 - Der Button wird aktiv

Wenn Sie auf einen der Buttons klicken, passiert noch nichts. Sie haben ja auch noch keine Befehle hinterlegt, die beim Klicken des Buttons ausgeführt werden sollen. Um dies zu erreichen, müssen wir die Anwendung stark erweitern.

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class Anmwendung extends JFrame implements ActionListener
{
    Roboter robbi;
    Button  links, rechts;
    
    public Anmwendung()
    {
        ...
        links.addActionListener(this);
        rechts.addActionListener(this);
        ...
    }

    public void actionPerformed(ActionEvent event)
    {
       if (event.getSource() == links)
       {
          // bewege den Roboter nach links
       }
       else if (event.getSource() == rechts)
       {
          // bewege den Roboter nach rechts
       }
      repaint();
    }
    
    public void paint(Graphics g)
    {  
       robbi.paint(g); 
    }

    public static void main(String[] args) 
    {
        new Anmwendung();
    }
}

Dieser nicht lauffähige (da unvollständige) Quelltext zeigt, wie Sie die Buttons "zum Leben" bringen können. Zunächst einmal müssen Sie die Bibliothek java.awt.event.* in Ihre Klasse einbinden. Dann erweitern Sie die Klasse um einen ActionListener:

public class Anmwendung extends JFrame implements ActionListener

Durch das Schlüsselwort extends erbt die Klasse Anwendung alle Attribute und Methoden der Mutterklasse JFrame. Durch das Schlüsselwort implements wird eine Art Doppelvererbung ermöglicht. Die Klasse Anwendung "erbt" zwar nichts von der Klasse ActionListener, aber die Klasse ActionListener schreibt der Klasse Anwendung genau vor, welche Methoden wie und mit welchen Parametern zwingend implementiert werden müssen.

Zwingend implementiert werden muss die Methode actionPerformed(ActionEvent event). Diese Methode "horcht" ständig, ob der Benutzer des Programms irgend eine Taste gedrückt oder eine Maustaste geklickt hat oder sonst irgend eine relevante Operation durchgeführt hat.

Wurde der linke Button angeklickt, dann ist die Bedingung

if (event.getSource() == links)

erfüllt, und die Befehle, die jetzt kommen, werden ausgeführt. Wurde dagegen der rechte Button angeklickt, dann ist die Bedingung

if (event.getSource() == rechts)

erfüllt, und die Befehle, die dann kommen, werden ausgeführt.

Anschließend wird der Befehl

repaint()

ausgeführt, der dafür sorgt, dass die Anwendung neu gezeichnet wird. Im Grunde ruft repaint() die von Ihnen implementierte paint()-Methode auf.

Wurde kein Button gedrückt, so wird die Methode actionPerformed() verlassen, ohne das etwas geschieht.

Sie können diese Anwendung zwar schon kompilieren und zum Laufen bringen, aber es passiert immer noch nichts, wenn Sie einen der beiden Buttons klicken. Warum nicht? Weil Sie noch keine Befehle definiert haben, die beim Klick auf einen der Buttons ausgeführt werden sollen. Das wollen wir jetzt im nächsten Schritt nachholen.

Schritt 5 - der Roboter wird erweitert

Wir wollen die Klasse Roboter nun um einen move(int dx)-Befehl erweitern. Wird beispielsweise robbi.move(-10) aufgerufen, dann soll sich der ganze Roboter um 10 Pixel nach links bewegen. Mit robbi.move(23) bewegt sich der Roboter um 23 Pixel nach rechts.

Der Einbau dieses Befehls ist gar nicht so einfach, wie man zunächst denkt. Ein erster Denkansatz wäre folgender:

    public void move(int dx)
    {
       x += dx;
       if (x < 30) x = 30;
       if (x > 430) x = 430;
    }

Wenn man die Klasse Roboter um diese move()-Methode erweitert, passiert gar nichts. Zwar wird die interne X-Position des Roboters verändert, und es wird sogar kontrolliert, ob der Roboter auch noch in das Fenster passt. Aber wenn man dann diesen Roboter mit der Anwendung verknüpft und in die actionPerformed()-Methode den move()-Befehl einbaut, etwa so:

    public void actionPerformed(ActionEvent event)
    {
       if (event.getSource() == links)
       {
          robbi.move(-10); repaint();
       }
       else if (event.getSource() == rechts)
       {
          robbi.move(10);
       }
       repaint();
    }

dann passiert gar nichts, wenn man auf einen der beiden Buttons klickt. Wir haben nämlich eine entscheidende Sache noch nicht beachtet. Schauen wir uns dazu mal den Quelltext des Konstruktors von Roboter an:

    
public Roboter(int xPos)
{
    x = xPos;

    kopf   = new Head(x,100,30);
    rumpf  = new Rectangle(x-20,130,40,80,new Color(191,191,223));
    basis  = new Rectangle(x-40,210,80,20,new Color(191,191,207));
    wheel1 = new Circle(x-30,240,10,new Color(95,95,111));
    wheel2 = new Circle(x+30,240,10,new Color(95,95,111));
    }

Bei der Erzeugung der Roboter-Objekte werden die Objekte kopf, rumpf, basis, wheel1 und wheel2 mit erzeugt. Dabei bekommen diese Objekte den X-Wert zugewiesen, mit dem das Roboter-Objekt initialisiert wurde. Wurde in der Anwendung also zum Beispiel

robbi = new Roboter(200);

aufgerufen, dann hat das Roboter-Objekt robbi einen Anfangs-X-Wert von 200. Dieser Anfangs-X-Wert wird nun an die Objekte kopf, rumpf, basis, wheel1 und wheel2 übergeben. Deren Anfangs-X-Wert ist also ebenfalls 200.

Ändern wir nun mit

robbi.move(20);

das Attibut x von robbi auf 220, dann hat das aber keinerlei Auswirkungen auf die X-Werte der fünf Unter-Objekte von robbi. Beim nächsten Aufruf der Roboter.paint()-Methode wird der Roboter wieder an der ursprünglichen Anfangsposition gezeichnet, weil alle Graphik-Objekte des Roboters noch diese Anfangsposition behalten haben.

Wie könnte man dieses Problem lösen?

Eigentlich ganz einfach, allerdings auch etwas umständlich. Wir müssen jedes Graphik-Objekt mit einer eigenen move()-Methode ausstatten. Dazu müssen wir die Klassen Circle, Rectangle und auch Head um eine move()-Methode ergänzen. Hier der Quelltext der move()-Methode der Klasse Head:

    public void move(int dx)
    {
       x = x + dx;
       face.move(dx);
       leftEye.move(dx);
       rightEye.move(dx);
       nose.move(dx);
       mouth.move(dx);
    }  

Sie haben es richtig gesehen: Damit sich ein Head-Objekt bewegen kann, müssen sich die Komponenten des Head-Objektes ebenfalls bewegen. Die move()-Methode von Head ruft also die move()-Methoden von face, leftEye, rightEye, nose und mouth auf.

Aufgabe 6.1-6

Ergänzen Sie nun die Klassen Circle, Rectangle und Roboter ebenfalls um eine solche move()-Methode bzw. passen Sie die move()-Methoden entsprechend an.

Lösungsvorschlag (nur für Lehrpesonen, Zugangsdaten erforderlich).

Eine kleine Sache noch ist zu erledigen. Wenn die actionPerformed()-Methode das Objekt robbi um 10 Pixel nach links oder rechts bewegt, wird der Roboter an dieser Stelle neu gezeichnet - sofern Sie die move()-Methoden überall korrekt eingebaut haben. Dummerweise wird der alte Roboter aber nicht gelöscht. Es erscheinen nun also zwei Roboter gleichzeitig und übereinander in der Anwendung, was nicht gerade schön aussieht und überhaupt nicht sinnvoll ist.

Der Roboter nach dreimaligem Klicken des links-Buttons
Autor: Ulrich Helmich 2021, Lizenz: siehe Seitenende

Es gibt einen kleinen Workaround, mit dem man dieses Problem vermeiden kann.

⇒ Statten Sie die Klasse Roboter mit einem zusätzlichen Attribut für ein Rechteck aus:

Rectangle background;

⇒ Initialisieren Sie diesen Hintergrund im Konstruktor des Roboters folgendermaßen:

background = new Rectangle(10,30,480,320,Color.WHITE);

⇒ Gehen Sie nun in die paint()-Methode des Roboters und ergänzen Sie diese wie folgt:

    public void paint(Graphics g)
    {  
       background.paint(g);
       robbi.paint(g); 
    }

Bevor der Roboter gezeichnet wird, wird zunächst ein weißer Hintergrund in die Anwendung gezeichnet, der von einem schwarzen Rahmen umgeben ist. Jedes Mal, wenn sich jetzt der Roboter bewegt, wird vorher der Hintergrund neu gezeichnet. Damit wird der alte Roboter übermalt.

So sieht die Anwendung jetzt aus
Autor: Ulrich Helmich 2021, Lizenz: siehe Seitenende

So sieht die Anwendung jetzt aus, nachdem der Roboter dreimal nach links bewegt wurde.

Anregungen für eine Weiterentwicklung

Wenn Sie wollen und Ihnen Ihre Lehrkraft Zeit dafür gibt, können Sie das Roboter-Projekt ja noch etwas weiter entwickeln. Hier an paar Anregungen:

  • Der Roboter, so wie er hier dargestellt ist, sieht noch nicht allzu gut aus. Man könnte noch etwas an der graphischen Darstellung "feilen".
  • Der Roboter kann sich nur nach rechts und links bewegen. Eine sehr anspruchsvolle Aufgabe wäre es, wenn sich der Roboter auch nach vorne und nach hinten bewegen könnte. Zwei zusätzliche Buttons wären dann auch erforderlich.
    Dazu müsste er dann natürlich kleiner bzw. größer werden. Als erstes müssten Sie also eine Routine entwickeln, die den Roboter in variablen Größen zeichnen kann.
  • Es wäre auch schön, wenn sich der Roboter drehen könnte. Dazu müssten Sie dann vier verschiedene Seitenanschichten des Roboters entwickeln. Und vergessen Sie nicht zwei Buttons zum Drehen nach rechts bzw. nach links.
  • Wenn der Roboter Objekte aufheben und wieder fallen lassen könnte, könnten Sie Gegenstände von einem Ort zu einem anderen transportieren lassen.
  • Statt mit Buttons könnte man die Bewegungen des Roboters auch mit der Tastatur steuern. Dann müssten Sie aber selbst mal im Internet recherchieren, wie man das macht.

Das waren nur ein paar Ideen, die mir selbst spontan gekommen sind. Vielleicht haben Sie ja auch tolle Ideen zum Umsetzen.