Coding für Kids: Pong Clone

Pong Clone

In diesem Teil von „Coding für Kids“ kommt mehr Bewegung in die Programmierung. Wir erstellen eines der ersten Computerspiele – einen Pong Clone. Wie das mit Lazarus geht, erfahren Sie hier.

Oft wird Pong (oder Telly Tennis) für das erste Computerspiel überhaupt gehalten. Das stimmt aber nicht. Das erste Spiel war 1951 das mathematische Nim-Spiel auf dem NIMROD – Rechner. Ein Jahr später folgte Tic-Tac-Toe. Pong gab es erst in den 1970 Jahren.


Das Original Telly Tennis (Quelle: Wikipedia.org)

Unsere Version für Lazarus ist aber eine Abwandlung. Es ist die 1-Spieler-Version.

Vorbereitungen

Los geht’s mit Anlegen eines neuen Projekts. Im Lazarus-Menü unter Projekt>Neues Projekt „Anwendung“ auswählen.

neues Projekt
Neues Projekt als Anwendung erzeugen

Lazarus erstellt dann ein neues Projekt und einem neuen Formular. Das wird gleich gespeichert mit Projekt speichern. Im folgenden Dateidialog erzeugt man am besten einen neuen Ordner für das Projekt und legt den Projektnamen fest. Im zweiten Schritt möchte Lazarus gleich den Dateinamen des Formulars wissen, z. B. habe ich hier HauptForm gewählt.

Das Orginal Pong gab es nur in Schwarz-Weiß meine Version wird Grün-Weiß. Die Einstellung der Hintergrundfarbe des Formulars erfolgt im Objektinspektor unter der Eigenschaft Color.

Hintergrundfarbe
Hintergrundfarbe des Formulars einstellen

Als nächstes legen wir fest, wie das Spiel ablaufen soll.

  1. Das Spiel beginnt nach Klicken auf STARTEN
  2. Der Ball soll an 3 Seiten jeweils abprallen (links, oben und unten)
  3. An der rechten Seite soll sich der Schläger befinden
  4. Trifft der Schläger den Ball, wird das als Fehler gezählt und angezeigt
  5. Nach 100 Sekunden ist das Spiel vorbei

Jetzt können wir überlegen, welche Elemente wir zum Spielen brauchen:

  • des Ball
  • einen Schläger
  • eine Punkteanzeige für nicht erwischte Bälle
  • eine Zeitanzeige
  • ein Anzeige für STARTEN

und festlegen, welche Komponenten wir dafür in Lazarus aussuchen:

  • des Ball → TImage
  • einen Schläger → TImage
  • eine Punkteanzeige für nicht erwischte Bälle → TLabel
  • eine Zeitanzeige → TLabel
  • ein Anzeige für STARTEN → TLabel

Jetzt können wir die Komponenten auf das Formular ziehen. Die Schriftfarbe der Labels wird im Objektinspektor unter Font > Color eingestellt. Wir verwenden hier clWhite.

Der Label für „Starten“ braucht besondere Einstellungen. Er soll in der Bildschirm-Mitte anzeigt werden. Dafür sind mehrer Änderungen von Eigenschaften notwendig. Hier Änderungen sind rot dargestellt. Die Einstellung des Cursors auf crHandPoint, soll zeigen, dass hier geklickt werden kann.

Objektinspektor
Einstellungen des Labels „Starten“

Ereignisse

Wie soll sich jetzt der Ball bewegen können? Das erledigt eine andere Komponente, aber eine nicht-visuelle: Der TTimer.

Er befindet sich in der Komponentenpalette unter System.

Komponentenpalette
Komponentenpalette System mit TTimer

Der Timer hat nur eine einfache Aufgabe. Ist er aktiviert wird, nach einer einstellbaren Zeit (Interval), ein Ereignis ausgelöst. Das nutzen wir im Spiel. Nach Klicken auf STARTEN aktivieren wir den Timer, der dann für die Bewegung des Balls sorgt.

Die Eigenschaft Interval wird auf 40 eingestellt. Das bedeutet, dass alle 40 Millisekunden ein Ereignis ausgelöst wird. Die Ereignis-Methode wird, im Objektinspektor auf der Seite Ereignisse unter OnTimer mit einem Doppelklick erzeugt.

Ereignis-Methode
Ereignis-Methode des Timers

Lazarus erstellt dann einen leeren Prozedur-Rumpf, der befüllt werden muss.

procedure THauptForm.Timer1Timer(Sender: TObject);
begin
end;

Auch für die verbleibende Spielzeit verwenden wir einen Timer. Wir möchten die Sekunden von 100 auf 0 zählen. Damit setzen wir das Interval auf 1000 (Millisekunden = 1 Sekunde) und erzeugen eine Ereignis-Methode.

Für die Spielzeit definieren wir eine Variable Zeit in der private-Sektion von THauptForm.

type
 THauptForm = class(TForm)
...
 private
 { private declarations }
  Zeit : integer;
  Fehler : integer;
  deltaX : integer;
  deltaY : integer; 
...
 end;

In der Ereignis-Methode von Timer2 wird diese Variable, dann immer um 1 (Sekunde) verringert und im Label angezeigt.

Am Ende folgt die Prüfung, ob die Zeit abgelaufen ist und das Spiel zu Ende ist.

procedure THauptForm.Timer2Timer(Sender: TObject);
begin
  Zeit:=Zeit-1;
  LabelZeit.Caption:=IntToStr(Zeit);
  if Zeit<=0 then SpielEnde;
end;

Der Ball prallt

Damit stellt sich nur noch die Frage, wie soll sich der Ball jetzt bewegen? Das wird über 2 Variablen realisiert.

Die Position des ImageBall wird über deltaX und deltaY verschoben. Beide können unterschiedliche Werte haben, dadurch sind alle Winkel möglich.

cfk_pong_bewegungball
Bewegung des Balls

Bei jedem Timer-Ereignis werden also die Positionen geändert. Im Quellcode sieht das so aus:

ImageBall.Left:=ImageBall.Left+deltaX; 
ImageBall.Top:=ImageBall.Top+deltaY;

Da sich ImageBall und ImageSchlaeger auf dem PanelSpielflaeche befinden, ist die Nullposition von beiden an der linken, oberen Ecke des Panels.

cfk_pong_berechnungen
Positionen der von Ball und Schläger auf dem Spielfeld

Was kann nun alles mit dem Ball passieren?

Berührung mit der linken Wand

Berührt der Ball die linke Wand, wird deltaX mit (-1) multipliziert, dadurch kehrt sich die Richtung um. Der Ein- und Austrittswinkel sollen gleich groß sein, deswegen bleibt deltaY gleich.

if ImageBall.Left<0 then deltaX:=deltaX*(-1);

Berühung oben und unten

Tritt der Ball die obere oder untere Wand passiert das Gleiche, nur dieses Mal mit deltaY. Es wird mit (-1) multipliziert und deltaX bleibt gleich.

if (ImageBall.Top<0) or (ImageBall.Top+ImageBall.Height>PanelSpielflaeche.Height) then 
  deltaY:=deltaY*(-1);

Der Schläger trifft

Die spannende Frage ist jetzt: Was passiert am rechten Rand und unserem Schläger? Haben wir den Ball getroffen?

Hier müssen mehrere Bedingungen erfüllt sein:

bx:=ImageBall.Left+ImageBall.Width; 
by:=ImageBall.Top+Round(ImageBall.Height/2); 
if ((bx>=ImageSchlaeger.Left) and (bx<=ImageSchlaeger.Left+ImageSchlaeger.Width)) 
and ((by>=ImageSchlaeger.Top) and (by<=ImageSchlaeger.Top+ImageSchlaeger.Height)) then 
  deltaX:=deltaX*(-1);

Der Ball geht ins Aus

Trifft der Schläger nicht, Ist die Position Left des Balls irgendwann größer als die Breite der Spielfläche. Dann ist er im Aus.

if ImageBall.Left>PanelSpielflaeche.Width then 
  begin 
    Fehler:=Fehler+1; 
    NeuerBall; 
  end;

Steuerungsarten

Die Steuerung können wir auf unterschiedliche Arten realisieren. Am besten gefällt mir die Variante mit der Maus. Die ist gleichzeig am einfachsten umsetzbar.

Es reicht eine einzige Zeile. Aber wo? Unsere Maus bewegt sich während dem Spiel auf einem Panel PanelSpielflaeche. Dieser Panel hat eine Ereignis-Methode namens MouseMove, die wir dafür benutzen können. Das Ereignis wird immer ausgelöst, wenn sich die Maus über dem Panel bewegt.

Als Parameter werden dem Ereignis auch die X und Y Position der Maus übergeben. Die Y-Position können wir mit der Top Eigenschaft unseres Schlägers verbinden.

procedure THauptForm.PanelSpielflaecheMouseMove(Sender: TObject;
 Shift: TShiftState; X, Y: Integer);
begin
  ImageSchlaeger.Top:=Y;
end;

Soll die Tastatur verwendet werden können, wird es ein bisschen aufwändiger. Zuerst müssen wir dafür sorgen, dass das Formular Tastatur-Ereignisse bekommt. Das passiert über eine Eigenschaft im Objektinspektor Keypreview. Diese muss auf TRUE gesetzt werden.

Dann ist die Realisierung wieder einfach. Als Ereignis in HauptForm verwenden wir KeyDown. Als Parameter übergeben wir die Variable Key. Diese können wir abfragen und die Position des Schlägers größer oder kleiner machen.

procedure THauptForm.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  //Mit den Pfeiltasten
  if Key=VK_UP then ImageSchlaeger.Top:=ImageSchlaeger.Top-10;
  if Key=VK_DOWN then ImageSchlaeger.Top:=ImageSchlaeger.Top+10;

  //Bedienung über Taste A und Y
  if Key=VK_A then ImageSchlaeger.Top:=ImageSchlaeger.Top-10;
  if Key=VK_Y then ImageSchlaeger.Top:=ImageSchlaeger.Top+10;
end;

Erweiterungen

Hier kommen noch ein paar Tipps, um das Spiel besser zu machen.

 

Ball wird schneller

Das Spiel wird ein wenig anspruchsvoller, wenn die Geschwindigkeit langsam zunimmt.  Das passiert bei uns in einer eigenen Prozedur. Sie wird immer aufgerufen, wenn der Schläger den Ball trifft.

Die Schrittweiten deltaX und deltaY werden über Zufallswerte angepasst.

  • Schrittweiten für X und Y berechnen
  • Der Zufallswerte liegt zwischen 0 und 2 durch -2 ergeben sich Werte zwischen -2 und 0
  • bei 0 bleibt die Geschwindigkeit gleich
  • bei -1 und -2 wird sie immer größer, der Ball schneller
procedure THauptForm.Zufallswerte;
begin
  deltaX:=deltaX+Random(3)-2;
  deltaY:=deltaY+Random(3)-2;
end;

Flickerfreie Bewegungen

Um das versetzen des Balls und Schlägers weicher zu machen, kann die Eigenschaft DoubleBuffered verwendet werden:

DoubleBuffered:=true;

Maus wird unsichtbar

Spielt man mit der Maus, stört meistens der Mauszeiger. Mit diesen Befehlen wird er unsichtbar und wieder sichtbar.

Mauscursor ausblenden:

PanelSpielflaeche.Cursor:=crNone;

Mauscursor wieder anzeigen:

PanelSpielflaeche.Cursor:=crDefault;

Das Ergebnis

So könnte das Ergebnis aussehen:

cfk_pong

Nun viel Spaß beim Nachprogrammieren.

Links

symbol_download

  Download des Quellcodes

Geschichte der Videospiele

NIM-Spiel

Lazarus installieren


Sie finden im Download-Ordner auch eine ausführbare Datei dieser Software. Wenn Sie Sicherheitsbedenken haben, lesen Sie bitte die Seite EXE-Dateien und das Internet.

Kommentar verfassen