BlitzBasic-Community-Tutorial

Aus Wikibooks

Wechseln zu: Navigation, Suche
Diese Seite hat mittlerweile eine Größe erreicht, die es als geeignet erscheinen lässt, sie in mehrere einzelne Seiten zu zerlegen. In welche Teile diese Seite zerlegt werden könnte, kann auf der Diskussionsseite besprochen werden. Wie man es macht, steht im Wikibooks-Lehrbuch im Abschnitt Buch in Kapitel untergliedern.


Dieses Buch steht im Regal Programmierung.

Inhaltsverzeichnis

[Bearbeiten] Einführung

[Bearbeiten] Vorwort

Hallo, erstmal! Du willst also die hohe Kunst der Programmierung erlernen. Ob du es glaubst oder nicht, indem du dieses Tutorial geöffnet hast, hast du den ersten Schritt auf dem langen Weg zu deinem ersten Spiel getan. Wahrscheinlich wirst du viele Rückschläge und Enttäuschungen erleiden, doch dies passierte jedem von uns, sogar den Programmierern von z.B. EA-Games.

Spieleprogrammierung bedeutet harte Arbeit. Nur wenn man sich wirklich dafür interessiert und sich anstrengt, kann man die schwierigen Aufgaben bewältigen. Doch das heißt NICHT, dass Programmieren keinen Spaß macht. Ganz im Gegenteil, es ist das interessanteste Hobby, das ich kenne.

In diesem Tutorial geht es um die Programmiersprache BlitzBasic. Möglicherweise hat dir jemand erzählt, dass C++ (eine weitere Programmiersprache) oder das Programm "GameMaker" wesentlich besser für Spieleprogrammierung geeignet ist. Das stimmt jedoch nicht ganz. BlitzBasic ist wesentlich mächtiger als der GameMaker. Das bedeutet, dass Spiele, die mit Blitz entwickelt wurden, wesentlich schneller sind. Außerdem kann man mit Blitz so gut wie ALLES machen, im Gegensatz zum GameMaker dessen Funktionsumfang stark begrenzt ist. C++ ist zwar schneller als Blitz und kann eigentlich auch viel mehr, aber da die Sprache sehr kompliziert ist, braucht man meist jahrelange Erfahrung, bis man einen simplen Snake-Klon zustande gebracht hat.

Ich rede hier so lässig von Programmiersprachen, aber was ist das überhaupt? Also, wie die meisten schon wissen, versteht der Computer keine "normalen menschlichen Wörter" sondern nur 1 und 0. Die 1 steht für "Strom fließt" und die 0 steht für "Strom fließt nicht" (Dies bezeichnet man übrigens als Binär-Code). Man kann sich vorstellen, dass so zu Programmieren ziemlich kompliziert ist; deshalb entwickelte man Assembler. Assembler ist sozusagen die "Ur-Sprache" und stellte eine Vereinfachung des Binärcodes dar. Häufig vorkommende Zahlenfolgen wurden zu "Wörtern" zusammengefasst. Ab da ging es bergauf mit der Programmierung. Es wurden immer mehr neue und leichter zu verstehende Programmmiersprachen entwickelt. Trotzdem ist so eine Sprache nicht mit einer normalen menschlichen Sprache wie z.B. Englisch zu vergleichen. Es ist vielmehr eine Art "Code" (Deshalb wird programmieren auch oft als "coden" bezeichnet).

Das Programm, das den Code in Binärcode umwandelt, nennt sich übrigens Compiler.

Ein weiterer Ausdruck, den du kennen solltest, ist "Syntax". Aber nicht vom Wort abschrecken lassen ;). Es ist einfach die "Grammatik" einer Programmiersprache

[Bearbeiten] Wo bekommt man Hilfe?

Es hat sich eine recht große Internetcommunity um BlitzBasic entwickelt. Auf www.blitzbase.de findet sich eine Übersicht über alle BlitzBasic-Befehle mit Beispielen und Erklärungen. Das könnte man quasi als Wörterbuch der Sprache BlitzBasic ansehen. Dieses Wikibuch hier versucht hingegen, die Grammatik beizubringen und den Einsatz der "Wörter" zu lehren.

Dieses Buch stellt nicht den Anspruch, perfekt zu sein, und stellt auch nicht an dich den Anspruch, alles auf Anhieb zu verstehen. Es ist ganz klar, dass beim Lesen Fragen aufkommen. Probleme sind ein Hauptbestandteil des Programmierens. Darum versuche, sie selbst zu lösen! Sollte dies nicht gelingen, so gibt es mehrere Internetforen, in denen du deine Fragen stellen kannst. Dieses Buch wurde größtenteils von Mitgliedern des Forums http://www.projectblitz.de (aber leider ist es nicht mehr online) geschrieben. Sollten sich direkt Fragen aus dem Text ergeben oder dir gar Fehler und Unklarheiten auffallen, wende dich bitte zum etabliertesten und größten deutschsprachige Blitzbasicforum http://www.blitzforum.de. Dort wird dir in der Regel schnell geholfen.

Wenn du des Englischen mächtig bist, gibt es noch eine weitere, sehr gute alternative Anlaufstelle: die internationale, englischsprachige Community auf der offiziellen BlitzBasic-Seite http://www.blitzbasic.com. Dort findet sich ein riesiges Forum voller hilfsbereiter Menschen und auch der offizielle BlitzBasic-Support. Besonders in BlitzBasic entdeckte Fehler sollten dort gemeldet werden.

[Bearbeiten] Über Basic

BlitzBasic basiert auf der Basic-Syntax; das bedeutet, dass es eine der am leichtesten zu erlernenden Sprachen ist. Allerdings wurde BlitzBasic (kurz: BB) um einige neue Features ergänzt (Directx-Unterstützung, Grafikbefehle) und auf Windows und andere neuere Betriebssysteme übertragen. Dabei wurde die einfache Basic-Syntax auf weite Strecken beibehalten. Auch wurden einige Optimierungen vorgenommen.


BlitzBasic3D (kurz: B3D) ist eine Erweiterung zu dem normalen Blitz. Wie der Name schon sagt, wird die Sprache durch einige spezifische 3D-Grafikbefehle erweitert. So können Würfel und andere 3D-Objekte durch einfache Befehle erstellt werden. Außerdem kann man die Kamera sehr einfach mit den Befehlen für Meshs an seine Bedürfnise anpassen. Ein vollständiges Kollisionssystem ist genauso vorhanden, wie die Möglichkeit, Heightmaps zu erstellen. Das Ganze basiert zwar noch auf DirectX-7-Basis, aber trotzdem lassen sich immer wieder erstaunliche Effekte auf den Bildschirm zaubern. Allerdings ist dafür mehr Übung notwendig, als für den Umgang mit einfachen 2D-Sachen.


BlitzPlus (kurz: b+) ist ebenfalls eine Erweiterung zu BlitzBasic. Wie auch B3D kann es alle 2D-Funktionen, aber keine 3D-Funktionen. Stattdessen kann mit b+ die sogenannte GUI (Graphical User Interface) verwendet werden. Damit lassen sich einfache Anwendungen realisieren, z.B. Grafikprogramme, Internet-Browser und Taschenrechner.


BlitzMax (kurz: BMax) ist eine erweiterte Version von BB. Es arbeitet mit z.T. anderer Syntax (anderen Befehlen) als die vorigen Versionen. Außerdem ist es betriebssystemübergreifend. Sprich: Ein Programm, das du in Windows entwickelt hast, kannst du auch für Linux oder Mac-Intosh PCs nutzbar machen.


Demoversionen der Editoren für die jeweiligen Versionen kann man sich hier herunterladen. Sie sind allerdings wie bei Demoversionen üblich stark eingeschränkt: http://blitzbase.de/2a.htm

Ich empfehle zum Ausprobieren Blitz3D.

[Bearbeiten] Grundlagen

[Bearbeiten] Print und Kommentare

Fangen wir mit unserem ersten Programm an. Tippe folgendes in den Editor und starte es mit F5:

Print "Mein erstes Programm!"
WaitKey
End

Jetzt müssten 2 Fenster auftauchen. Das große ganz unten nennt man Debugger. Es durchsuchte den Code nach möglichen Fehlern ("bugs") und teilt uns mit, wenn es einen findet. Den Debugger kann man aber auch an und abschalten indem man auf Program > DebugEnable? klickt.

In der Mitte sollte sich ein kleineres, schwarzes Fenster befinden. Dort läuft der Hauptteil deines Programmes ab (in diesem Fall steht dort "Mein erstes Programm!").

OK, wie funktioniert das jetzt?
Ganz einfach: Um Text auf dem Bildschirm auszugeben, benutzt man den Befehl (oder auch Anweisung genannt)

Print

Nach Print schreibt man einfach den gewünschten Text in Anführungsstrichen. Wie du sicher schon bemerkt hast, muss man jeden Befehl in eine neue Zeile schreiben. Demnach ist der nächste

WaitKey

Um zu verstehen, was er bewirkt musst du erstmal wissen, dass das Programm sich selbst abschaltet sobald es die letzte Zeile erreicht, und da der Computer ja eine "HighSpeed-Maschine" ist würde der Text höchstens eine Nanosekunde aufflimmern. Um das zu verhindern, benutzt man WaitKey: Wie der Name schon sagt, wartet dieser Befehl bis der Benutzer eine Taste drückt und fährt erst dann mit der nächsten Zeile fort.

Was der Befehl

 End

bewirkt, ist nicht schwer zu erraten. Es beendet das Programm. Aber wie schon erwähnt, schaltet sich das Programm am Schluss selbst ab. Doch dann meckert der Debugger; deshalb sollte man immer in die letzten Zeile ein end schreiben. Probiere es einfach mal ohne es aus.

Also, Print gibt den Text aus. Natürlich kann man ihn auch mehrfach verwenden.

Print "Hallo,"
Print "2. Zeile!"
WaitKey
End

Print hat aber den Nebeneffekt, dass er gleich eine neue Zeile anfängt. Wenn man das nicht möchte, dann muss man einfach "Write" verwenden.

Write "1. Zeile "
Write "hm, immernoch die 1. Zeile"
WaitKey
End

Achja, noch etwas:
Bei den meisten Spielen wird der code über 100 Zeilen lang (ich höre schon die ersten entsetzten Aufschreie). Man kann sich vorstellen, dass das ziemlich unübersichtlich wird. Deshalb gibt es sogenannte Kommentare. Diese überspringt der Compiler einfach und der Debugger meckert auch nicht.
Ein Kommentar wird mit einem Semikolon ( ; ) eingeleitet und reicht bis zum Ende der Zeile. So kann man sich zu jedem Befehl eine kleine Notiz schreiben, wenn man möchte:

Print "Hallo Welt!" ;gibt den Text in Anführungszeichen aus
WaitKey ;wartet auf einen Tastendruck
End ;beendet das Programm
;alles was ich hinter einen Strichpunkt schreibe wird nicht vom Compiler gelesen!

[Bearbeiten] Variablen

Was sind Variablen? In der Mathematik sind sie Platzhalter. In der Programmierung speichert man in ihnen Werte, die sich während des Spielverlaufes ändern, wie z.B. Leben, Schaden, Geschwindigkeit etc.

In BlitzBasic gibt es 3 Arten von Variablen:

Long  -Variablen können Ganzzahlen (1,2,3...)               speichern. 
Float -Variablen können Kommazahlen (3.2,74.33892)          speichern.
String-Variablen können Zeichenketten ("Hallo", "Ciao")     speichern.


Jede dieser Arten hat ein bestimmtes "Kennzeichen":

Float-Variablen: # 
String-Variablen: $
Long-Variablen: entweder % oder gar kein Zeichen


Wie erstellt (Fachausdruck: deklariert) man jetzt diese Variablen? Ganz einfach: Zuerst schreibt man den gewünschten Variablennamen:

leben 

dann fügt man das "Variablenzeichen" an

leben% ;in diesem Fall kann man es aber auch weglassen 

und zum Schluss den gewünschten Wert den man in der Variable speichern will:

leben% = 10

Hier nochmal alle 3 Arten in einem Beispiel:

leben = 10                 ;diesmal hab ich die Endung weggelassen.
Geschwindigkeit# = 7.333   ;bei Kommazahlen immer das englische Komma (.) schreiben.
name$ = "Friedrich"        ;Strings müssen immer in "" geschrieben werden.


Ein String ist einfach nur Text; eine Anhäufung von Zeichen.

OK, wie verwendet man jetzt Variablen? Am Besten kann man das an einem Beispiel zeigen:


;hier werden die Variablen erstellt
leben  = 10      
speed# = 7.333
name$  = "friedrich"
;hier werden sie auf dem Bildschirm ausgegeben
Print name
Print leben
Print speed

WaitKey
End



Wenn man den INHALT der Variable angeben möchte, dann schreibt man einfach nur den Variablennamen (ohne ""). (Probier einfach mal die Namen in "" zuschreiben und schau was passiert.)

Der Computer ersetzt einfach den Variablennamen (ohne "") mit ihrem Inhalt.

Natürlich könnte man auch schreiben:


Print "10"
Print "7.333"
Print "Friedrich"

WaitKey
End 


Aber das Tolle an Variablen ist ja, dass sie (was für eine Überraschung) variabel sind.

Wieder ein Beispiel:


Leben = 10
Schaden = 3

Print Leben     ;noch ist der Spieler unbeschadet

Leben = Leben - Schaden  ;aber jetzt wird von Leben der Schaden abgezogen

Print Leben    ;die gleiche Zeile wie oben und trotzdem wird etwas anderes ausgegeben

WaitKey
End 


Du dürftest jetzt eigentlich alles bis auf die folgende Zeile verstehen:

Leben = Leben - Schaden 


Dann lösen wir sie mal auf:

leben =

dürfte ja noch verständlich sein. Die Variable bekommt einfach einen neuen Inhalt.

leben = leben - schaden

wie gesagt: Der Compiler ersetzt Variablen mit ihrem Inhalt. Also dürfte die Zeile in Wahrheit so aussehen:

leben = 10 - 3

jetzt sollte eigentlich alles klar sein.

Falls du es noch nicht verstanden hast, kannst du dir noch das nächste anschauen.


leben      = leben + 10          ; man bekommt 10 leben dazu
schaden    = schaden + schaden   ; schaden wird verdoppelt
schaden    = schaden*2           ; das Gleiche
schaden    = schaden/2           ; er wird halbiert
speed      = leben               ; speed wird an leben angepasst
leben      = leben - 1           ; man verliert ein leben
uzga       = uzga + 1            ; man bekommt ein uzga^^
sabberblub = sabberblub - 1      ; man kann sich für eine Variable die dümmsten Namen einfallen lassen

Beachte: Am Computer benutzt man die Rechenzeichen * für das Multiplizieren und das / für das Dividieren.

ergebnis = a + b            ; Zwei Zahlen werden zusammengezählt und das Ergebnis in einer Variable      gespeichert
ergebnis = a - b            ; die Zahlen werden subtrahiert,
ergebnis = a * b            ; multipliziert,
ergebnis = a / b            ; und dividiert.


Außerdem kann man nicht nur mit Zahlvariablen rechnen, sondern auch mit Strings:

name$  = "Fritz"
alter = 14

meldung$ = "Dein Name ist" + name$ + " und du bist" + alter + " Jahre alt."
Print meldung$
Waitkey 
End 

Man schreibt also einfach die Zeichenkette (in "") und fügt dann mit einem Plus (+) eine Variable oder eine weitere Zeichenkette an. Achtung: es muss hinter einer Zeichenfolge immer $ stehen, sonst wird der Wert 0 Ausgegeben!


Man muss dieses "Mischmasch" aber nicht unbedingt in Variablen speichern. Man kann es auch direkt ausgeben:


name$ = "Fritz"
alter = 14
Print "Dein Name ist" + name +" und du bist" + alter +" Jahre alt."

Waitkey
End

[Bearbeiten] Input und der Umgang mit Funktionen

OK, du kannst immerhin schon Text ausgeben und mit Variablen arbeiten. Aber was nützt es einem der nicht programmieren kann? Gar nichts. Und jetzt kommt Input ins Spiel. Mit Input kann man die Eingabe eines Benutzers in eine Variable speichern.

Print "Gib deinen Spielernamen ein: "
Spielername$ = input() 
Print "Name: " + Spielername$

WaitKey
End

Input ist eine sogenannte Funktion (oft auch als Befehl oder Anweisung bezeichnet). Diese besteht aus dem Funktionsnamen und einem Parameterteil. Aber nicht vom Namen abschrecken lassen, Parameter bedeutet einfach "Wert". Und wenn eine Funktion mehrere Parameter benötigt, werden sie durch ein (deutsches) Komma getrennt. Die Parameterteile gehören immer zwischen zwei Klammern. Doch im 1. Beispiel ist er leer, da Input keine Werte benötigt (Es können aber welche eingesetzt werden). Es liest einfach die Eingabe ein und das war's, es gibt keine Variationen.

Print ist ebenfalls eine Funktion. Der Parameter, den Print benötigt, ist der String (Text), den es ausgeben soll. Aber, bei Print steht der Parameterteil nicht in Klammern. Wie geht denn das? Naja, es ist ja bekannt, dass Ausnahmen die Regeln bestätigen. Natürlich könnte man auch schreiben:

Print("Blah")

Aber es nützt uns nichts, da Print keinen Wert zurückliefert. Das bedeutet, dass man das Ergebnis dieser Funktion nicht in eine Variable schreiben kann, wozu auch? In diesem Fall ist das nicht nötig. (Man kann die Klammern aber nur in Basic-Dialekten weglassen, nicht in C++ oder anderen Sprachen)
Übrigens kann man bei Input, wenn man will, auch einen Parameter verwenden, nämlich den text der den user dazu auffordert, etwas einzugeben. Das würde dann so aussehen:

name$ = Input("Gib mal deinen Namen ein: ")
print name

BlitzBasic hat hunderte von diesen Funktionen auf Lager. Das Sortiment reicht von einfachen Text-Funktionen über mathematische Funktionen bis hin zu 3D-Effekten.

Hier gibt es eine komplette Liste (obwohl die wichtigsten auch im Tutorial behandelt werden): http://blitzbase.de/4a.htm

Wenn man mit der Eingabe des Benutzers auch rechnen möchte (z.B. bei einem Taschenrechner-programm) dann muss man den Rückgabewert von Input einfach in eine Zahl-Variable anstatt in eine String-Variable setzen:

Zahl1% = Input("Gib bitte eine Zahl ein: ")
Zahl2% = Input("Gib bitte noch eine Zahl ein: ")
Print Zahl1+Zahl2

[Bearbeiten] If-Konstruktionen

Mittlerweile kannst du also einen Text per "Print" ausgeben und weißt auch schon, wie du den Benutzer eine Eingabe tätigen lässt. Aber was ist nun, wenn du, abhängig von der Benutzereingabe, eine bestimmte Ausgabe erfolgen lassen möchtest? Nehmen wir das Beispiel einer X-beliebigen Quizshow: Der Spieler bekommt drei Antwortmöglichkeiten aus denen er wählen kann, aber nur eine ist richtig:

Print "Frage: Was ist 2+2?"
Print "(a) 6"
Print "(b) 4"
Print "(c) 5"

Antwort$ = Input ("a, b oder c? ")

If Antwort = "b" Then              ;Wenn die Antwort "b" ist
 Print "Richtig, du Mathegenie!"   ;Gebe den Text aus
EndIf                              ;Ende der If-Konstruktion


Huch, was haben wir denn da? Die Zeile (If Antwort = "b" Then) schaut, ob in der Variable "Antwort" der Text "b" gespeichert wurde. Wenn das so ist, wird die folgende Zeile ausgeführt. Man fragt also eine Bedingung ab, und wenn diese erfüllt ist, wird das gemacht, was zwischen If und Endif steht.

P.S.: Man kann das ganze auch - ohne 'EndIf' - in eine Zeile schreiben:

If Antwort = "b" Print "Richtig, du Mathegenie"

Oder wer's lieber etwas geschwätziger hat:

If Antwort = "b" Then Print "Richtig, du Mathegenie"


[Bearbeiten] If und seine Verzweigungen

Um beim Beispiel mit dem Gewinnspiel zu bleiben: Was ist, wenn man nicht nur etwas sagen möchte, wenn der Spieler gewonnen hat, sondern ihn auch auslachen möchte, wenn er daneben getippt hat? Dazu gibt es in BB einen weiteren Befehl:

Else (zu Deutsch: Sonst/Ansonsten)

Um den Gebrauch zu demonstrieren, habe ich das weiter oben stehende Beispielprogramm mal ein wenig modifiziert:

Print "Frage: Was ist 2+2?"
Print "(a) 6"
Print "(b) 4"
Print "(c) 5"

Antwort$ = Input ("a, b oder c? ")

If Antwort = "b" Then               ;Wenn die Antwort "b" ist...
    Print "Richtig, du Mathegenie!" ;Gib diesen Text aus
Else                                ;Ansonsten...
    Print "Muhahaha! Daneben!"      ;Gib diesen Text aus
EndIf

Alles was zwischen dem Befehl "Else" und "Endif" steht, wird nur ausgeführt, wenn die erste Bedingung, also Wahl = "b", nicht erfüllt wird.

Was ist aber nun, wenn ich für die Antworten "a" und "c" noch andere Antworten geben möchte als nur, dass der Spieler versagt hat? Naja, dafür gibt es den schönen Befehl "Elseif", der eine Kombination der Begriffe "If" und "Else" darstellt. Um den Gebrauch zu demonstrieren, habe ich das Beispielprogramm noch einmal umgestrickt:

Print "Frage: Was ist 2+2?"
Print "(a) 6"
Print "(b) 4"
Print "(c) 5"

Wahl$ = Input ("a, b oder c? ")

If Wahl = "b" Then  ;Wenn Wahl gleich "b" ist...
  Print "Richtig, du Mathegenie!" ;Gebe diesen Text aus
Elseif Wahl = "a"   ;Ansonsten: Wenn Wahl = "a"
  Print "Ha, voll daneben!"
ElseIf Wahl = "c"   ;Ansonsten: Wenn Wahl = "c"
  Print "Ha, knapp daneben ist auch vorbei!"
Else    ;Ansonsten...
  Print "Was hast du bitte schön eingegeben? Jedenfalls nicht a, b oder c."
EndIf

[Bearbeiten] Select/Case

Wie man beim vorherigen Beispielcode sieht, kann so eine If-Abfrage bei vielen möglichen Antwortmöglichkeiten schon ziemlich lang und umständlich werden. Deshalb gibt es "Select" und "Case". Man sagt erst, welche Variable überprüft werden soll (Das macht man mit Select Deinevariable) und fügt nachher die verschiedenen Antwortmöglichkeiten hinzu (Mit Case "Anwort") und default ist das Äquivalent (entsprechende) zu dem Befehl "Else". Um das Ganze einmal auf das bisher benutzte Beispiel zu übertragen:

print "Frage: Was ist 2+2?"
Print "(a) 6"
Print "(b) 4"
Print "(c) 5"

Wahl$ = Input ("a,b oder c? ")

Select Wahl                       ;Die Variable "Wahl" soll im folgenden überprüft werden...
Case "b"                          ;Antwort "b"
  Print "Richtig, du Mathegenie!" ;Gebe diesen Text aus
Case "a"                          ;Antwort "a"
  Print "Ha, voll daneben!"
Case "c"                          ;Antwort "c"
  Print "Ha, knapp daneben ist auch vorbei!"
Default                           ;Wenn keine der anderen Antworten passt...
  Print "Was hast du bitteschön eingegeben? Jedenfalls nicht a,b, oder c."
End Select


So, damit lassen sich schon nette Sachen basteln, kleine Textadventures oder Scherzprogramme. ;) Bastel einfach ein wenig mit den Codes rum, vertausche die Texte oder füge noch mehr Antwortmöglichkeiten hinzu, und du wirst sehen dass sich damit schon nette Sachen machen lassen.

[Bearbeiten] Schleifen

Tja, die Schleifen, die erleichtern einem das Leben. Es wäre sehr mühsam, wenn man für 200 Variablen die Wertzuweisungen von Hand einzeln eingeben müsste. Außerdem ist auch nur so ein Spiel möglich, da ja immer wieder das Bild aufgebaut und gelöscht werden muss, um die Illusion einer Bewegung zu erzeugen. Eigentlich wiederholen Schleifen nur die dazwischen stehenden Schritte bis zur einer Abbruchbedingung. Mit den Verzweigungen stellen Schleifen die Grundlage jeder Programmiersprache dar. Nur wer diese richtig einzusetzen weiß, kann richtig Programmieren.

[Bearbeiten] For/Next

Die For-Schleife ist ein Zähler-Schleife. Mit ihrer Hilfe kann man sich viel Arbeit sparen, vor allem in Verbindung mit Arrays und Types (dazu später mehr).


 For start=1 To 10
  Print "Ein Hoch auf die For-Schleife"
 Next 

 WaitKey 
 End

Dieses Progrämmchen würde 10-mal den Text ausgeben. Dabei ist das Next zwingend nötig, sonst folgt eine Fehlermeldung. Also wird alles was zwischen For und Next steht genau start-mal (was für ein ausdruck^^) ausgeführt. "start" ist in diesem Fall eine Variable, die in jedem Schleifendurchlauf "zunimmt". In diesem Beispiel beginnt sie bei 1 (start = 1) und wird bis 10 (To 10) erhöht. Also schreiben wir uns mal ein kleines CountDown-Programm. (obwohl es eher ein CountUp-Programm ist)

 For zaehler = 1 To 10
  Print zaehler
 Next 

Aber die For-Schleife muss nicht unbedingt in Einser-Schritten zählen.

 For zaehler = 0 to 10 Step 2
  Print zaehler
 Next 

 WaitKey
 End 

Hierbei würde jede 2. Zahl übersprungen werden. Man kann auch Step 3,4,5,6... angeben,dann werden halt mehrere Zahlen übersprungen. Wenn man die Schleife jetzt also rückwärts zählen lassen möchte, dann muss man sie mit -1er Schritten abarbeiten.

For zaehler = 10 to 1 Step -1
 Print zaehler
Next 

WaitKey
End

Und um daraus jetzt ein richtiges CountDown-Programm zu machen, muss man noch den Delay-Befehl einbauen. Wie der Name schon sagt, macht Delay etwas ähnliches wie WaitKey, nur dass es nicht wartet bis man eine taste drückt, sondern bis eine gewisse Zeitspanne vorüber ist. Und diese Zeitspanne gibt man als Parameter an (aber in Millisekunden). Wenn wir also wollten, dass erst nach einer Sekunde eine neue Zahl kommt, dann müssten wir als Parameter 1000 (=1 Sekunde) angeben:


For zaehler = 10 to 1 Step -1
 Delay(1000)
 Print zaehler
Next 

WaitKey
End

[Bearbeiten] Repeat/Until

Was zwischen Repeat und Until steht, wird solange wiederholt, bis die bei Until eingetragene Abbruchbedingung erfüllt wird.

 zaehler = 0 

 Repeat                ;WIEDERHOLE
  zaehler = zaehler + 1   ;erhöhe die variable "zähler"
  Print zaehler           ;Gib den wert des zaehlers am bildschirm aus
 until zaehler = 10    ;BIS der zaehler gleich 10 ist

Tja, wozu braucht man jetzt das Zeug? Dasselbe könnte man doch mit einer For-Schleife erzielen, oder nicht? Stell dir einfach mal vor, du würdest an einem Egoshooter arbeiten und du möchtest, dass der Gegner auf den Spieler schießt bis er ihn getroffen hat oder tot ist. Mit einer For-Schleife ist das unmöglich, da man ja nicht weiß, wie oft der Gegner schießen muss.

[Bearbeiten] Repeat/Forever

Achja, es gibt noch die Repeat/ForEver-Schleife.

Repeat 
 Print "immernoch Blah!"
Forever

Was das bedeutet ist wahrscheinlich klar. Diese Schleife endet nie und wird daher unendlich vorgeführt.

[Bearbeiten] While/Wend

Eine der Unterschiede zwischen While- und Repeat-Schleife besteht darin, dass bei der While-Schleife zuerst die Abbruchbedingung getestet wird. Fällt dieser Test positiv aus, wird die Schleife ausgeführt.

While Bedingung  ;solange die Bedingung stimmt
 Print "Blah"
Wend             ;ende der Konstruktion

Wie man sieht, kann man eine While-Schleife anstelle einer Repeat-Schleife zur Erstellung der Main Loop (davon aber später) nutzen. Meistens wird aber die Repeat-Schleife verwendet. Bei manchen Befehlen funktioniert nur die eine bei manchen wieder nur die andere Schleife (sind allerdings wenige Befehle die da kritisch reagieren, vor allem bei den 3D-Kollisons-Befehlen ist das der Fall).

While not a=1
Wend 

Dies ist eine besondere Schreibweise. Wenn a ungleich 1 ist wird die Schleife ausgeführt. Das wars dann an dieser Stelle, ist doch gar nicht so schwer, oder?

[Bearbeiten] Eigene Funktionen

Du weißt zwar schon, wie man mit Funktionen umgeht, aber du kannst dir auch Eigene programmieren. Dies geschieht in einer Function-Konstruktion, welche ungefähr so aussieht:

Function NameDerFunktion(parameter)
End Function

"Function" leitet also die Konstruktion ein und "End Function" beendet sie wieder. Zwischen die Klammern schreibt man die gewünschten Parameter. Diese sind jedoch Lokal, d.h. dass sie nur in deiner Funktion (also zwischen Function und EndFunction) verwendet werden können.

Aber am besten ich erklär dir das Ganze an einem Beispiel:

Function Volumen(laenge,breite,hoehe)   
 volumen = laenge * breite * hoehe   ;Volumen ist übrigens auch Lokal
 Return volumen
End Function

(diese Funktion berechnet das Volumen eines Quaders) Dürfte nicht allzu schwer sein, der einzige Befehl den du noch nicht kennst ist Return. Damit stellt man einfach den Rückgabewert einer Funktion ein und in diesem Fall soll eben das Volumen zurückgegeben werden. Und so verwendet man die Funktion:

vol = Volumen(1,4,7)
Print vol

In diesem Fall würde 28 (1*4*7) auf dem Bildschirm ausgegeben werden

[Bearbeiten] Variablen und Funktionen

Funktionen sind vom Hauptcode abgekapselt. Da man sich über sie seine eigenen Befehle erstellen will, dürfen sie nicht unbeabsichtigt vom Hauptcode beeinflusst werden. Dies würde geschehen, wenn Variablen, die in Funktion und Hauptcode denselben Namen haben, auch dieselbe Variable wären. Komisch formuliert? Ja, aber eigentlich ganz einfach. x in einer Funktion ist nicht dasselbe x wie das in einer anderen Funktion oder im Hauptcode.

x = 200
Print Test() 
Print x
Waitkey()
Function Test()
 x = x + 1
 Return x
End Function


Obwohl im Hauptcode x = 200 steht, ist das Funktionsergebnis x = 1. Die Funktion kann nicht ohne weiteres auf Variablen des Hauptcodes zugreifen. Für die Funktion ist der Wert der Variable x bei ihrem Start 0. Und sobald die Funktion endet, ist auch alles, was während ihr geschehen ist, vergessen. x ist für das Hauptprogramm weiterhin 0. x ist Lokal. Das heißt, es wird nicht von Codeabschnitt zu Codeabschnitt (Hauptcode->Funktion, Funktion->Hauptcode, Funktion-Funktion) weitergegeben. Zum Erstellen solcher lokalen Variablen gibt es einen eigenen Befehl, der aber nicht benötigt wird. Sobald man eine neue Variable einführt, setzt Blitz sie automatisch als lokal.

Local x = 10


Der schlaue Leser wird jetzt schon das Gegenstück erwarten. Der Praktiker wird sich denken können, wozu es dient. Der Englischfreak wird sich auch schon denken können, wie es heißt: global. Globale Variablen werden durch alle Funktionen mitgeschleift. Man kann von jeder Ecke des Programms aus auf sie zugreifen. Definiert werden sie überraschenderweise mit dem Befehl Global. Dies kann nur im Hauptcode erfolgen. So setzen wir x also in unserem Beispielcodeschnippsel global:

Global x = 200
Print Test() 
Print x
Waitkey()
Function Test()
 x = x + 1
 Return x
End Function

So, ein Wort geändert und schon wird etwas ganz anderes ausgegeben. Wenn man nun x schreibt, so verstehen Funktion und Hauptprogramm darunter dieselbe Variable. x wird auf 200 gesetzt, in der Funktion auf 201 erhöht, als Rückgabe angezeigt. Auch im Hauptprogramm ist x jetzt 201.


Diese Funktion gibt eine Zeichenkette zurück.

Function Test$()
 Return "Test"
End Function

Diese Funktion gibt eine Ganzzahl zurück.

Function Test%()
 Return 1
End Function

Diese Funktion gibt eine Kommazahl zurück.

Function Test#()
 Return 3.4
End Function

[Bearbeiten] BlitzBasic-Befehle ersetzen

Anstatt sich eigene Funktionen zu programmieren, kann man auch die Blitzbasic-Funktionen ersetzen. Ein Beispiel wäre der Befehl "Print". Mit ihm gibt man, wie schon bekannt, Text aus. Seine Parameter sind "Print derText". Nun wollen wir aber zusätzlich noch die Position, an der der Text angezeigt wird, angeben. Hierfür gibt es einen Befehl.

Locate x,y

Damit bestimmen wir, wo Print anfängt zu schreiben, und zwar bei den Koordinaten x und y. Gewöhnlich schreiben wir nun:

Locate 100,200
Print "das ist ein Text"

Es wird der Text "das ist ein Text" ab der Position 100,200 ausgegeben. Nun wollen wir aber nicht jedesmal, wenn wir einen Text ausgeben, den Befehl Locate vorher benutzen. Wir bräuchten einen Befehl,

PrintL Text,x,y

bei dem wir den Text und die Koordinate angeben. Es ist einfacher als man denken können, den Befehl

Print Text

durch unseren neuen Befehl

PrintL Text,x,y

zu ersetzen. Das ganze funktioniert so:

Function PrintL(Text$,x,y)
 Locate x,y
 Print Text$
End Function

Das war es schon. Der alte Befehl "Print" wurde durch unseren neuen ersetzt. Wenn wir nun einen Text schreiben wollen, schreiben wir einfach:

PrintL "das ist ein Text",100,200

Und schon erscheint der Text "das ist ein Text" ab der Position 100,200 Um Befehle zu ersetzen, schreibt man also einfach eine Funktion mit dem zu ersetzenden Befehl.


[Bearbeiten] Arrays

Wenn man, sagen wir 3 Spieler hat, und für jeden will man mehrere Werte speichern, die z.B. das Leben des Spielers, die x und die y Koordinate. Jetzt könnte man für jeden Spieler und jeden Wert eine Variable deklarieren:

spieler1_x=0
spieler1_y=0
spieler1_leben=100

spieler2_x=5
spieler2_y=5
spieler2_leben=100

spieler3_x=10
spieler3_y=10
spieler3_leben=100

Das wird allerdings sehr schnell in einen unübersichtlichen Code ausarten, geschweige denn von dem Aufwand den der Coder hat...

Daher kann man in Blitz ganze Speicherfelder, genannt Arrays,erstellen:

Dim spieler(2)

"Was soll das jetzt?" werden sich jetzt einige Fragen. Ganz einfach: damit haben wir 3 Variablen deklariert,von 0 bis 2,nämlich

spieler(0)
spieler(1)
spieler(2)

Die Zahl in den Klammern wird übrigens Index genannt. Diese kann man jetzt wie normale Variablen benutzen, hier einige Beispiele:

spieler(0)=5
Print spieler(0)
WaitKey
End

Die ausgabe des Programms wäre logischerweise 5.

Noch ein Beispiel:


spieler(0)=5
If spieler(0)=5 Then spieler(1)=spieler(0)
Print spieler(1)
WaitKey
End

Die Ausgabe wäre wieder 5.


Aber es geht noch besser: Man kann auch mehrere Dimensionen in ein Array packen, nämlich mit

Dim irgendwas(2,2)

Dann hätte man folgende Variablen zur Verfügung:

irgendwas(0,0)
irgendwas(0,1)
irgendwas(0,2)
irgendwas(1,0)
irgendwas(1,1)
irgendwas(1,2)
irgendwas(2,0)
irgendwas(2,1)
irgendwas(2,2) 

Man muss aber nicht unbedingt nur Zahlen darin speichern, auch Texte sind möglich,man muss nach dem Namen nur das bekannte $ setzen:

Dim texte$(2)
texte(0)="Hello " 
texte(1)="World"
texte(2)="!" 
Write texte(0)
Write texte(1)
Write texte(2)
WaitKey
End

Ein eleganterer Weg wären Types, sind aber manchmal, wenn man nicht so viel speichern will, zu aufwändig.

[Bearbeiten] Blitzarrays

Alternativ zu den normalen Arrays gibt es auch noch die Blitz-Arrays.

Zu erst definieren wir mal ein Blitz-Array. Dabei müssen wir darauf achten, dass diese nicht mit Dim sondern mit Global bzw. Local vereinbart werden. So kann man Blitz-Arrays, solange man sie mit Local definiert hat, auch nur in Funktionen verwenden. Weiters gilt, dass diese spezielle Art der Arrays nur eindimensional sein kann. Der Index muss bei einem Blitz-Array in eckigen Klammern stehen. Hier ein Beispiel:

Global A[3]

Natürlich kann ein Blitz-Array auch Texte oder Fließkommazahlen beinhalten. $ für Texte, und # für Fließkommazahlen. Dabei ist merkwürdig, dass man die Typkennung nur bei der Definition angeben darf. Wenn man das Blitz-Array verwendet, muss diese entfallen.

Hier ein Beispiel:

Global Musikanten$[3] ;Hier wird ein globales Blitz-Array definiert.

Musikanten[0] = "Hahn"  ;\
Musikanten[1] = "Katze" ; \ Hier werden die Werte zugewiesen.
Musikanten[2] = "Hund"  ; /
Musikanten[3] = "Esel"  ;/

For I = 0 To 3         ;\
   Print Musikanten[I] ; | Hier werden die Werte nach der Reihe ausgegeben.
Next                   ;/

Waitkey

Eine etwas elegantere Weise Daten einzulesen, wäre mit den Befehlen Data und Read. Hier das gleiche Beispiel noch mal mit den neuen Befehlen:

Global Musikanten$[3] ;Hier wird ein globales Blitz-Array definiert.
Data "Hahn", "Katze", "Hund", "Esel" ;Die Werte werden hier aufgelistet.

For I = 0 To 3
   Read Musikanten[I] ;und hier werden sie, mithilfe einer Schleife, eingelesen.
Next
 
For I = 0 To 3         ;\
   Print Musikanten[I] ; | Hier werden die Werte nach der Reihe ausgegeben.
Next                   ;/

Waitkey

Wie man sieht, exakt das gleiche Ergebnis. Diese Art des Dateneinlesens kann man auch bei normalen Arrays verwenden.

Es gibt noch zwei weitere Gründe, die Blitz-Arrays interessant machen:
Zum 1.: Man kann es als Parameter einer Funktion übergeben.
Zum 2.: Man kann es als Element eines Type-Feldes verwenden. (Zu den Types im nächsten Abschnitt gleich mehr.)

[Bearbeiten] Types

Type-Befehlssatz ist eine sehr wichtige Erweiterung des BlitzBasic-Dialekts. Jedoch ist es ein sehr komplexes Thema und besonders Anfänger haben große Schwierigkeiten bei Verwendung von Types. Oft liegt es an anderer Vorstellung wie Types überhaupt funktionieren. Ich selbst war zuerst etwas verwirrt, da Types in anderen Programmiersprachen ganz anders funktionierten. Selbst jetzt gibt es wahrscheinlich nur wenige BB-Programmierer, die alle Tricks kennen. Dieses Tutorial soll endlich Schluss damit machen und alle Möglichkeiten erklären.

Zuerst soll noch gesagt werden, dass Types keine Universallösung darstellen. Es gibt einige Bereiche wo Types vollkommen ungeeignet sind. Meistens kann man etwas sogar ganz ohne Types lösen. Manchmal sind Types jedoch unvermeidbar und manchmal machen Types die Programmierung deutlich einfacher.

Wozu überhaupt Types? Wenn man große Projekte hat, dann benötigt man auch sehr viele Variablen. Oft hat man sogar viele ähnliche Variablengruppen, die man bei großen Programmen aber nur schwer in DIM-Feldern zusammenhalten kann. Types fassen wiederholende Variablen zusammen.


Hier wird die Benutzung empfohlen:

1) Die Anzahl der Objekte ist nicht bekannt oder variiert ständig. Beispielhaft dafür ist z.B. ein Alien-Shooter, wo dauernd Aliens abgeknallt werden. Manchmal sind nur wenige Aliens auf dem Bildschirm, manchmal kommen viele dazu.

2) Bei Verwendung von Objekten, wo man direkten Zugriff auf Eigenschaften haben möchte. Beispielhaft hierfür ist ein GUI mit Fenstern, Buttons, Checkboxen. Dabei möchte man z.B. den Status von Checkbox abfragen oder die Position der Objekte.


Hier wird die Benutzung nicht empfohlen:

1) Bei hoher Objektanzahl (über 10.000) wird die Benutzung nicht empfohlen. Dies wirkt sich sehr negativ auf den Speicher aus (dazu später mehr). Hier wird entweder DIM oder BANK empfohlen.

2) Bei statischer Objektanzahl, wo während des Programmablaufs niemals die Anzahl geändert wird. Hier wird auch entweder DIM oder BANK empfohlen. Ein großer Fehler ist z.B. die Verwaltung von Karteninformationen (Map) mit Types.


#1 Definition
Der Befehl TYPE definiert eine neue Typekollektion mit einem bestimmten Namen. Mit END TYPE wird diese Definition abgeschlossen. So sieht eine leere Definition aus, die in BlitzBasic sogar zugelassen wird:

TYPE person
END TYPE

Zwischen diesen Zeilen lassen sich beliebig viele unterschiedliche Variablen (Eigenschaften) mit FIELD notieren. Es können auch beliebige Variablenarten sein (Integer, Float, String). Dies könnte so aussehen:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

Achtung! Diese Typedefinition kann nur im Hauptprogramm stehen. Dabei spielt es aber keine Rolle wo. Man kann es sogar in eine Includedatei auslagern. Zugriffe auf diese Typekollektion sind auch aus Funktionen erlaubt. Besser gesagt: die Typekollektion ist immer global verfügbar.

#2 Variablen
Bisher haben wir nur eine Typedefinition. Damit lässt sich aber noch gar nichts machen. Um Zugriff auf Objekte zu erhalten, werden ganz spezielle Variablen (Containervariablen) benötigt. Ich kann nur empfehlen solche Variablen mit LOCAL/GLOBAL zu deklarieren. Diese Variablen unterscheiden sich nur wenig von normalen Variablen - es wird lediglich ein Punkt mit Typebezeichnung dazugeschrieben:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

LOCAL mutter.person
GLOBAL vater.person

Dieser Anhang (Punkt mit Typebezeichnung) ist sehr wichtig. BB muss ja wissen zu welchem Type so eine Variable zugeordnet wurde. Übrigens kann dieser Anhang bei späterer Verwendung sogar ganz entfallen. Dies funktioniert ja auch mit normalen Variablen. So kann man in dem Beispiel ja auch den Anhang # weglassen:

LOCAL float#
float=0.1

Alle Type-Variablen haben direkt nach der Definition keinen Wert. Dies kann man sich schwer vorstellen, wenn man z.B. mit QuickBasic programmiert hat. Dort konnte man direkt nach der Definition bereits auf TYPE-Eigenschaften zugreifen - es existierte bereits ein Objekt. In BlitzBasic wurde aber lediglich eine Variable ohne Inhalt angelegt. Ich hoffe, dieses Beispiel kann das etwas verständlicher machen:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

LOCAL integer% ;Wert ist gleich 0
LOCAL vater.person ;Wert ist gleich NULL

In BlitzBasic gibt es keinen Zwang eine Variable mit LOCAL/GLOBAL du deklarieren. So kann man ja "on-the-fly" beliebige Variablen anlegen. Dies funktioniert aber auch mit Type-Variablen:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE
integer%=0
vater.person=NULL

Achtung! Während Zugriffe auf eine Typekollektion auch aus Funktionen erlaubt sind, kann eine Type-Variable global oder lokal sein. Somit kann man Zugriffe von Funktionen aus, auf solche Variablen erlauben oder verweigern.


#3 Erstellen
Bisher hat sich noch gar nichts getan. Wir haben eine Typedefinition und eine Variable für einen Zugriff angelegt. Jetzt fangen wir endlich an Objekte anzulegen. Dazu gibt es den Befehl NEW. Damit reservieren wir Speicher und legen somit ein Objekt an:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE
LOCAL vater.person
LOCAL mutter.person
vater.person=NEW person
mutter.person=NEW person

Dieser Code ist auch erlaubt (wir erinnern uns: Anhang kann nach einer Definition ausgelassen werden):

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

LOCAL vater.person
LOCAL mutter.person

vater=NEW person
mutter=NEW person

Dieser Code funktioniert auch (man kann ja Werte direkt zuweisen):

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

LOCAL vater.person=NEW person
LOCAL mutter.person=NEW person

Das geht ebenfalls (ist ja standardmäßig lokal):

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

vater.person=NEW person
mutter.person=NEW person

Beachte: Jedes Mal, wenn ein Objekt erstellt wird, wird dieses Objekt ganz hinten an unsere Typekollektion angehängt.

#4 Verlinkung
Endlich existiert ein Objekt. Dieses Objekt wird in eine Liste der Typekollektion eingefügt. Die Type-Variable "vater" wurde mit einem Objekt verbunden und die Type-Variable "mutter" wurde mit einem zweiten Objekt verbunden. Diese Verbindungen können aber auch wieder aufgehoben werden:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

LOCAL vater.person
LOCAL mutter.person

vater=NEW person
mutter=NEW person
mutter=NULL

In diesem oberen Beispiel wurden zwei Objekte erstellt und Type-Variable "mutter" wurde wieder von dem Objekt abgekoppelt. Das eigentliche Objekt existiert aber weiterhin in der Liste. Es wurde nicht gelöscht!!! Man hat nur keinen Zugriff mehr auf dieses Objekt. Wie man dennoch eine Verlinkung zu einem Objekt aufbaut, werde ich etwas später erklären. Ich möchte euch jetzt besser auf eine kleine Falle hinweisen, die bei Funktionen auftreten kann:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

FUNCTION test()
 LOCAL vater.person
 vater=NEW person
END FUNCTION

test()

Hier wird ein neues Objekt in der Funktion erstellt. Es wird beim Beenden aber nicht freigegeben. Ist ja auch klar: Typekollektionen sind ja immer global. Lediglich die Type-Variable "vater" wird beim Beenden vernichtet. Wenn ihr jetzt die Funktionsweise verstanden habt, dann sollte dieser Code für euch einleuchtend sein:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

LOCAL vater.person
LOCAL mutter.person

vater=NEW person
mutter=NEW person
mutter=vater

Ich glaube, jetzt werden einige aber einen Aufstand machen: Mutter soll gleich Vater sein??? Ja, denn wir verlinken Mutter-Variable einfach mit dem Objekt von Vater-Variable. So wie bei dem oberen NULL-Beispiel geht auch hier ein direkter Zugriff auf Mutter-Objekt verloren. Mutter-Objekt bleibt aber weiterhin bestehen - nur haben wir wieder keinen Zugriff drauf. Für alle, die das schwer verdauen können: Auf Vater-Objekt kann man jetzt mit zwei Variablen zugreifen, auf Mutter-Objekt aber überhaupt nicht mehr. Somit kann man auch viele Spielereien machen - Z.B. kann man die Verlinkungen zwischen Vater und Mutter vertauschen:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

LOCAL vater.person
LOCAL mutter.person
LOCAL tmp.person

vater=NEW person
mutter=NEW person

tmp=vater
vater=mutter
mutter=tmp

#5 Zugriff Jetzt können wir neue Objekte erstellen und damit wie die Profis hantieren. Allerdings konnten wir bisher noch nicht auf Eigenschaften zugreifen und Werte manipulieren. Das ist aber kinderleicht - es funktioniert fast wie in zich anderen Programmiersprachen auch:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

LOCAL vater.person

vater=NEW person
vater\name$="Homer"
vater\adresse$="Springfield"
vater\alter%=40
vater\einkommen#=2345.67

Wie man sieht, wird lediglich Type-Variable angegeben. Nach einem Schrägstrich folgt Name der Eigenschaft und Wertzuweisung. In vielen anderen Programmiersprachen wird statt Schrägstrich ein Punkt verwendet. Warum wurde das nicht auch in BB so gemacht? Dies kommt daher, weil unser Punkt schon für die Kennzeichnung einer Type-Variable vergeben wurde. Besser gesagt: Der obere Code müsste eigentlich so aussehen (dies ist aber länger und unüblich):

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

LOCAL vater.person

vater.person=NEW person
vater.person\name$="Homer"
vater.person\adresse$="Springfield"
vater.person\alter%=40
vater.person\einkommen#=2345.67

In dem oberen Beispiel haben wir Werte zugewiesen. Irgendwie müssen wir diese Werte nun auslesen. Das geht ebenfalls sehr einfach:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

LOCAL vater.person

vater=NEW person
vater\name$="Homer"
vater\adresse$="Springfield"
vater\alter%=40
vater\einkommen#=2345.67

PRINT vater\name$
PRINT vater\adresse$
PRINT vater\alter%
PRINT vater\einkommen#

Beachte: Man kann nur Werte der Eigenschaften ausgeben. Jedoch kann man nicht einfach "PRINT vater" schreiben. Dies wäre ein Fehler und Programm würde nicht starten.

Achtung! Unsere Variable "vater" muss natürlich existieren (z.B. LOCAL vater.person). Zudem muss diese Variable mit einem Objekt verbunden sein (hier passierte das über NEW). In diesem Code sind darum gleich zwei Fehler:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

vater\name$="Homer"
vater\adresse$="Springfield"
vater\alter%=40
vater\einkommen#=2345.67

Wer seine Programme auf maximale Sicherheit trimmen möchte, der prüft vorher, ob eine Variable mit einem Objekt verbunden ist. Dies geschieht so (Ausschnitt):

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE
LOCAL vater.person

...
IF vater<>NULL THEN
 vater\name$="Homer"
 vater\adresse$="Springfield"
 vater\alter%=40
 vater\einkommen#=2345.67
ENDIF

#6 Löschen Nun wissen wir endlich wie wir ein Objekt erzeugen können. Aber es war bisher nicht entfernbar. Tja, dazu gibt es wieder einen neuen Befehl DELETE. Als Parameter wird lediglich eine Variable angegeben:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

LOCAL vater.person

vater=NEW person
DELETE vater

In diesem Beispiel wurde ein Objekt aus unserer Typekollektion komplett entfernt. Die Variable "vater" bleibt aber weiterhin bestehen. Diese bekommt jetzt aber den Wert NULL. Somit haben wir auch den Zugriff auf unser Objekt verloren.

Oft möchte man jedoch gleich alle Objekte einer Typekollektion löschen. Dies geht ebenfalls sehr sehr einfach. Beachte: Jetzt wird aber nicht mehr eine Variable, sondern Typename als Parameter mit DELETE angegeben:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

LOCAL vater.person
LOCAL mutter.person

vater=NEW person
mutter=NEW person
DELETE EACH person

Achtung! Es gibt eine große Falle, die recht unbekannt ist. Ein Objekt wird nicht wirklich aus dem Speicher gelöscht. Intern wird es lediglich als ungültig markiert und der Speicher nicht freigegeben.

Es basiert an der Annahme: Wenn X Objekte verwendet wurden, dann werden irgendwann später erneut mindestens so viele Objekte benötigt. Die Erstellung von vielen Objekten auf einen Schlag dauert lange, dafür bleibt es später aber konstant. Der einzige Grund war die Erhöhung der Geschwindigkeit. Nach dem Beenden des Programms wird aber natürlich der komplette Speicher freigegeben.

Falls dieser Effekt zu negativ auf den Speicher auswirkt (bei vielen Objekten), dann schlage ich vor entweder DIM oder BANK zu benutzen. Nach der Verwendung von DIM oder BANK wird der Speicher natürlich wieder freigegeben.

#7 Linked List Bisher hatten wir nur einzelne Objekte. BlitzBasic-Types sind aber viel trickreicher. Wir erinnern uns: Einzelne Objekte werden immer ganz hinten in eine globale Typekollektion eingefügt. Dies kann man sich wie eine einfache Liste vorstellen. Warum fügen wir dann nicht einfach zich solche Objekte ein?

TYPE alien
 FIELD x
 FIELD y
END TYPE

LOCAL a.alien
FOR i=1 TO 10
 a=NEW alien
 a\x=RAND(0,640)
 a\y=RAND(0,480)
NEXT

Die Variable "a" wurde nach dem Befehl NEW immer wieder mit einem neuen Objekt verlinkt. Und dann wurde den Eigenschaften "x" und "y" ein Zufallswert zugewiesen. Alle 10 Objekte bleiben aber erhalten - nur wir haben keinen direkten Zugriff mehr auf alle Objekte. Jetzt kommt aber der Trick: Alle Objekte in dieser Typekollektion sind über unsichtbare Fäden nacheinander miteinander verbunden. Und mit FOR...EACH-Schleife kann man abwechselnd alle Objekte abarbeiten:

TYPE alien
 FIELD x
 FIELD y
END TYPE
LOCAL a.alien

FOR i=1 TO 10
 a=NEW alien
 a\x=RAND(0,640)
 a\y=RAND(0,480)
NEXT

FOR a=EACH alien
 PRINT a\x
 PRINT a\y
NEXT

Beachte: Zuerst wird das erste Objekt ausgewählt, die Schleife abgearbeitet und dann das nächste Objekt aus der Liste ausgewählt. Dies dauert solange, bis keine Objekte mehr vorhanden sind. Wenn die Schleife abgearbeitet wurde, dann bekommt unsere Variable "a" den Wert NULL zugewiesen. Wir können aber auch jederzeit die Schleife mit EXIT verlassen (dann bleibt die letzte Verlinkung erhalten):

TYPE alien
 FIELD x
 FIELD y
END TYPE

LOCAL a.alien

FOR i=1 TO 10
 a=NEW alien
 a\x=RAND(0,640)
 a\y=RAND(0,480)
NEXT

FOR a=EACH alien
 IF a\x<10 THEN EXIT
NEXT

Achtung! In BlitzBasic gibt es keine Type-Instanzen. Es gibt nur eine einzige Type-Kollektion, auch wenn man viele verschiedene Type-Variablen verwendet. Darum funktioniert dieser Code nicht so wie man das auf den ersten Blick vermuten möchte:

TYPE alien
 FIELD x
 FIELD y
END TYPE
LOCAL a.alien
LOCAL b.alien

FOR i=1 TO 10
 a=NEW alien
NEXT

FOR i=1 TO 10
 b=NEW alien
NEXT

FOR a=EACH alien
 ...
NEXT

FOR b=EACH alien
 ...
NEXT

In dem Beispiel wurden 20 Objekte erstellt. Egal über welche Variable man nun alle Objekte abarbeiten möchte, es werden immer die 20 Objekte abgearbeitet. Ich denke das ist auch logisch, da unsere Variable dauernd nur zu einem Objekt gelinkt werden kann.

#8 Selektion Wow - diese neuen Möglichkeiten ermöglichen jetzt schon so viel. Aber es ist noch nicht vorbei! Wir haben bisher nur automatisch alle Objekte mit FOR...EACH abgearbeitet. Kurz zur Erinnerung: Wir konnten ja eine Verbindung zu einem Objekt mit NULL aufheben:

TYPE person
 FIELD name$
 FIELD adresse$
 FIELD alter%
 FIELD einkommen#
END TYPE

LOCAL vater.person
LOCAL mutter.person 

vater=NEW person
mutter=NEW person
mutter=NULL

Wir können aber auch mit speziellen Befehlen erneut Verbindungen aufbauen und sogar eine Typekollektion rauf und runterlaufen. Zuerst die einfacheren Befehle:

TYPE alien
 FIELD x
 FIELD y
END TYPE

LOCAL a.alien

FOR i=1 TO 10
 a=NEW alien
 a\x=RAND(0,640)
 a\y=RAND(0,480)
NEXT

a=FIRST alien

In diesem Beispiel wurden 10 Objekte erstellt. Danach wurde aber eine Verbindung mit dem ersten Objekt in der Liste "alien" aufgebaut. Wir haben also manuell unsere Variable "a" mit dem ersten Objekt verbunden. Dies funktioniert aber auch umgekehrt - man kann den letzten Eintrag auswählen:

TYPE alien
 FIELD x
 FIELD y
END TYPE

LOCAL a.alien

FOR i=1 TO 10
 a=NEW alien
 a\x=RAND(0,640)
 a\y=RAND(0,480)
NEXT

a=LAST alien

Hier gibt es aber auch eine Falle! Was ist, wenn wir gar keine Objekte in der Liste haben - zu was bauen wir dann eine Verbindung auf? Ganz einfach: sollten keine Objekte existieren, dann bekommt unsere Variable "a" den Wert NULL zugewiesen. Dies kann man auch ausnutzen:

TYPE alien
 FIELD x
 FIELD y
END TYPE

IF FIRST alien=NULL THEN keineobjekte

Erste oder letzte Objekte auswählen ist ja nicht gerade spannend. Darum kann man noch manuell vorherige oder nachfolgende Objekte auswählen. Hier ein Beispiel, um einen nachfolgenden Eintrag auszuwählen (AFTER) und einen vorherigen Eintrag auszuwählen (BEFORE):

TYPE alien
 FIELD x
 FIELD y
END TYPE

LOCAL a.alien
LOCAl b.alien

FOR i=1 TO 10
 a=NEW alien
 a\x=RAND(0,640)
 a\y=RAND(0,480)
NEXT

a=FIRST alien
a=AFTER a

b=LAST alien
b=BEFORE b

Hier gibt es schon kleine Unterschiede. Anders als bei LAST/FIRST wird nicht mehr Typename, sondern Variablename bei AFTER/BEFORE angegeben. Es gibt aber auch noch paar Tricks:

TYPE alien
 FIELD x
 FIELD y
END TYPE

LOCAL a.alien
LOCAl b.alien
FOR i=1 TO 10
 a=NEW alien
 a\x=RAND(0,640)
 a\y=RAND(0,480)
NEXT
a=AFTER FIRST alien
a=AFTER AFTER a

b=BEFORE LAST alien
b=BEFORE BEFORE b

Bei BEFORE und AFTER muss man ebenfalls aufpassen! Was ist, wenn man bereits den ersten Eintrag ausgewählt hat und nun plötzlich einen vorherigen auswählen will. Oder was ist, wenn man bereits den letzten Eintrag ausgewählt hat und nun plötzlich einen nachfolgenden auswählen will. Tja, in dem Fall wird unser Variable der Wert NULL zugewiesen. Vielleicht werden sich jetzt viele fragen: Was bringen diese Befehle? Tja, deshalb habe ich ein kleines Beispiel, in dem alle Objekte in umgekehrter Reihenfolge abgearbeitet werden:

TYPE alien
 FIELD x
 FIELD y
END TYPE

LOCAL a.alien
FOR i=1 TO 10
 a=NEW alien
 a\x=i
 a\y=i
NEXT

a=LAST alien
WHILE a<>NULL
 PRINT a\x
 a=BEFORE a
WEND

[Bearbeiten] Mathematische und Sonderfunktionen

Folgendes ist für die Spieleprogrammierung nicht zwingend nötig aber ganz nützlich:

Es gibt auch eine Anzahl von Mathefunktionen. Am besten sollte man sich die deutsche Onlinehilfe von der Seite www.Blitzbase.de besorgen, in der noch einmal die Funktion eines jeden Befehls aufgeführt wird. Ich liefere hier einen Überblick über die wichtigsten:


 Wurzel=Sqr(5)

Diese Funktion ermittelt die Wurzel einer Zahl.

Zahl=8
Wurzel=Sqr(Zahl)

Die geht auch!

B=Log10(10)

Diese Funktion errechnet den 10er-Logarithmus der angegeben Zahl, auch hier kann, wie oben, eine Variable verwandt werden.

Zahl#=Pi
Print Zahl

Pi symbolisiert die Zahl 3,14.

Zahl=5
Zahl xor 10

Xor verschiebt bitweise.

[Bearbeiten] Wir programmieren unser erstes Spiel (Textadventure)

[Bearbeiten] Allgemein

[Bearbeiten] Mini Textadventure

Der Mini Textadventure besteht aus mehrenen Räumen. Jeder Raum hat eine Raumnr und einem Namen.


Type RaumTyp
 Field Name$
 Field Nr
 Field Richtung[3]
End Type 
Type SpielerTyp
 Field Name$
 Field Ort
 Field Leben
End Type 
Type GegenstandTyp
 Field Name$
 Field Nr
 Field Ort
End Type 


Const Nord=0
Const Sued=1
Const West=2 
Const Ost =3
 
Const AdvKennung$="MiniAdvSpiel"

Global RichtungsName$[3]
RichtungsName[0]=" Norden"
RichtungsName[1]=" Süden"
RichtungsName[2]=" Westen"
RichtungsName[3]=" Osten"

Type RaumTyp
 Field Name$
 Field Nr
 Field Richtung[3]
End Type 

Type SpielerTyp
 Field Name$
 Field Ort
 Field Leben
End Type 

Type GegenstandTyp
 Field Name$
 Field Nr
 Field Ort
End Type 
 
Global Raum.RaumTyp
Global Spieler.SpielerTyp
Global Gegenstand.GegenstandTyp
 
Const Gegenstand_Hat_Spieler=0
Const Keine_Tuer=0
 
;          Name$        Nr ,N ,S ,W ,O

CreateRaum "Ziel"      ,-1 , 0, 0, 0, 0
CreateRaum "Küche"     , 4 , 0, 3, 0,-1
CreateRaum "Stube"     , 3 , 4, 0, 0, 2
CreateRaum "BadeZimmer", 2 , 0, 0, 3, 1
CreateRaum "Keller"    , 1 ,-2, 0, 2,-3
CreateRaum "Abgrund"   ,-2 , 0, 0, 0, 0
CreateRaum "Teufel"    ,-3 , 0, 0, 0, 0

CreateGegenstand "Schweizer Taschenmesser",1,Gegenstand_Hat_Spieler
CreateGegenstand "Kochtopf"               ,2,4
CreateGegenstand "Foto"                   ,3,3

SpielerName$=Input("Name: ")

CreateSpieler SpielerName$,2,3

Graphics 640,480,0,2

Spiel

Function Spiel()
 Repeat
  SpielerInfo

  Eingabe$=Input$()

  verb$=Mid$(Eingabe$,1,Instr(Eingabe$+" ", " ")-1)
  objekt$=Mid$(Eingabe$,Instr(Eingabe$+" ", " ")+1)

  Select Lower$(verb$)
   Case "n","8"
    SpielerGeheNach Nord
   Case "s","2"
    SpielerGeheNach Sued
   Case "w","4"
    SpielerGeheNach West
   Case "o","6"
    SpielerGeheNach Ost

   Case "gehe"
    Select Lower$(objekt$)
     Case "nord"       
      SpielerGeheNach Nord
     Case "süd", "sued" 
      SpielerGeheNach Sued
     Case "west"        
      SpielerGeheNach West
     Case "ost"       
      SpielerGeheNach Ost
    End Select 

   Case "save","speichern","speicher"
    SpielSpeichern objekt$
    
   Case "speichereals"
    If Len(objekt$)>0 Then
     SpielSpeichern objekt$
    EndIf 

   Case "load","laden","lade"
    SpielLaden objekt$

   Case "nimm"
    Nimm objekt$

   Case "leg"   
    Leg objekt$

   Case "inventar"
    inventar
   Case "end","ende"
   Case "help","hilfe","?"
  End Select 
 Forever 
End Function 

Function Hilfe()
 Print "Mein Befehlswortschatz umfasst folgende Kommandos:"
 Print "gehe nord|süd|west|ost  oder Abkürzung n|s|w|o"
 Print "nimm|leg <Gegenstand>"
 Print "inventar|hilfe"
 Print "speichereals|lade <dateiname.erw>"
End Function 

Function SpielLaden(objekt$)
 If Len(objekt$)>0 Then
  If Instr(objekt$,".")>0 Then
   FileNr=ReadFile(objekt$)
  Else 
   FileNr=ReadFile(objekt$+".dat")
  EndIf 
 EndIf    

 If FileNr=0 Then 
  FileNr=ReadFile("Save.dat")
 EndIf 
      
 If FileNr<>0 Then     
  If Not Eof(FileNr) Then
   Kennung$=ReadLine(FileNr)
  EndIf 

  If Kennung$=AdvKennung$ Then
   If Not Eof(FileNr) Then
    SpeicherArt$=Upper$(ReadLine(FileNr))
   EndIf
 
   If SpeicherArt$="BIN" Or SpeicherArt$="ASC" Then

    For Raum =Each RaumTyp
     Delete Raum
    Next

    For Gegenstand=Each GegenstandTyp
     Delete Gegenstand
    Next

    For Spieler=Each SpielerTyp
     Delete Spieler
    Next  

    RaumName$=ReadLine(FileNr) 

    While Len(RaumName$)>0  
     Raum =New RaumTyp
     Raum\Name$      = RaumName$ 

     Select SpeicherArt$
      Case "BIN"
       Raum\Nr         = ReadInt(FileNr)
       Raum\Richtung[0]= ReadInt(FileNr)
       Raum\Richtung[1]= ReadInt(FileNr)
       Raum\Richtung[2]= ReadInt(FileNr)
       Raum\Richtung[3]= ReadInt(FileNr) 
      Case "ASC"
       Raum\Nr         = ReadLine(FileNr)
       Raum\Richtung[0]= ReadLine(FileNr)
       Raum\Richtung[1]= ReadLine(FileNr)
       Raum\Richtung[2]= ReadLine(FileNr)
       Raum\Richtung[3]= ReadLine(FileNr) 
     End Select

     RaumName$       = ReadLine(FileNr) 
    Wend    

    GegenstandName$=ReadLine(FileNr) 
    While Len(GegenstandName$)>0
     Gegenstand=New GegenstandTyp

     Gegenstand\Name$= GegenstandName$

     Select SpeicherArt$
      Case "BIN"
       Gegenstand\Nr   = ReadInt(FileNr)
       Gegenstand\Ort  = ReadInt(FileNr)
      Case "ASC"
       Gegenstand\Nr   = ReadLine(FileNr)
       Gegenstand\Ort  = ReadLine(FileNr)
     End Select 

     GegenstandName$ = ReadLine(FileNr) 
    Wend       

    SpielerName$=ReadLine(FileNr) 
    While Len(SpielerName$)>0
     Spieler= New SpielerTyp

     Spieler\Name$=SpielerName$
     
     Select SpeicherArt$
      Case "BIN"
       Spieler\Ort  = ReadInt(FileNr)
       Spieler\Leben= ReadInt(FileNr)
      Case "ASC"
       Spieler\Ort  = ReadLine(FileNr)
       Spieler\Leben= ReadLine(FileNr)
     End Select 

     SpielerName$ = ReadLine(FileNr) 
    Wend 
    Spieler=First SpielerTyp
   EndIf
   Cls
   Locate 0,0 
   Print "Spielstand geladen!" 
   Print ""
  EndIf
  CloseFile FileNr 
 EndIf 
End Function 

Function SpielSpeichern(objekt$)
 If Len(objekt$)>0 Then
  If Instr(objekt$,".")>0 Then
   FileNr=WriteFile(objekt$)
  Else 
   FileNr=WriteFile(objekt$+".dat")
  EndIf 
 EndIf 

 If FileNr=0 Then 
  FileNr=WriteFile("Save.dat")
 EndIf    

 If FileNr<>0 Then 
  WriteLine FileNr,AdvKennung$
  WriteLine FileNr,"BIN"

  For Raum =Each RaumTyp
   WriteLine FileNr, Raum\Name$
   WriteInt  FileNr, Raum\Nr
   WriteInt  FileNr, Raum\Richtung[0]
   WriteInt  FileNr, Raum\Richtung[1]
   WriteInt  FileNr, Raum\Richtung[2]
   WriteInt  FileNr, Raum\Richtung[3]
  Next
  WriteLine   FileNr,"" 

  For Gegenstand=Each GegenstandTyp
   WriteLine  FileNr, Gegenstand\Name$
   WriteInt   FileNr, Gegenstand\Nr
   WriteInt   FileNr, Gegenstand\Ort
  Next
  WriteLine   FileNr,"" 

  For Spieler=Each SpielerTyp
   If Spieler\Name$="" Then 
    WriteLine  FileNr, "Spieler"
   Else 
    WriteLine  FileNr, Spieler\Name$
   EndIf 

   WriteInt FileNr, Spieler\Ort
   WriteInt FileNr, Spieler\Leben
  Next
  WriteLine   FileNr,""   

  Spieler=First SpielerTyp  
  CloseFile FileNr 

  Print "Spielstand gespeichert!"
  Print ""
 EndIf 
End Function 

Function SpielerInfo()
 For Raum=Each RaumTyp
  If Raum\Nr=Spieler\Ort Then
   Print "Sie befinden sich im/in der "+Raum\Name$ 
   Print "Mögliche Gehrichtungen:"

   For Richtung=0 To 3
    If Raum\Richtung[Richtung]<>Keine_Tuer Then
     Print RichtungsName[Richtung]
    End If 
   Next 

   Print
   Print "Gegenstände in diesem Raum:";

   For  Gegenstand=Each GegenstandTyp
    If Spieler\Ort=Gegenstand\Ort Then 
     Print " "+ Gegenstand\Name$
     GegenstandZahl = GegenstandZahl+1
    End If 
   Next 

   If GegenstandZahl = 0 Then
    Print " keine"
   End If 

  End If 
 Next 
End Function 

Function Nimm(objekt$)
 For Gegenstand=Each GegenstandTyp
  If Lower$(Gegenstand\Name$)=Lower$(objekt$) Then
   If Gegenstand\Ort=Spieler\Ort Then
    Gegenstand\Ort=Gegenstand_Hat_Spieler
    Print Gegenstand\Name$+ " aufgelesen"
   End If 
  End If 
 Next
End Function 

Function Leg(objekt$)
 For Gegenstand=Each GegenstandTyp
  If Lower$(Gegenstand\Name$)=Lower$(objekt$) Then
   If Gegenstand\Ort=Gegenstand_Hat_Spieler Then
    Gegenstand\Ort=Spieler\Ort
    Print Gegenstand\Name$+ " abgelegt"
   End If 
  End If 
 Next 
End Function 

Function inventar() 
 For Gegenstand=Each GegenstandTyp
  If Gegenstand_Hat_Spieler=Gegenstand\Ort Then 
   Print " "+ Gegenstand\Name$
   GegenstandZahl = GegenstandZahl+1
  End If 
  If GegenstandZahl = 0 Then
   Print " keine"
  End If 
 Next 
End Function 

Function SpielerGeheNach(Richtung)
 For Raum=Each RaumTyp
  If Raum\Nr=Spieler\Ort Then
   If Raum\Richtung[Richtung]<>Keine_Tuer Then 
    Spieler\Ort=Raum\Richtung[Richtung]
   End If 
  End If   
 Next 
End Function 

Function DeleteGegenstand(Name$)
 For Gegenstand=Each GegenstandTyp
  If Gegenstand\Name$=Name$ Then
   Delete Gegenstand
   Exit 
  EndIf  
 Next 
End Function 

Function CreateGegenstand(Name$,Nr,Ort)
 Gegenstand=New GegenstandTyp

 Gegenstand\Name$=Name$
 Gegenstand\Nr   =Nr
 Gegenstand\Ort  =Ort
End Function 
 
Function CreateSpieler(Name$,Ort,Leben)
 Spieler=New SpielerTyp

 Spieler\Name$=Name$
 Spieler\Ort  =Ort
 Spieler\Leben=Leben
End Function 
 
Function CreateRaum(Name$,Nr,Richtung_Nord,Richtung_Sued,Richtung_West,Richtung_Ost)
 Raum=New RaumTyp
 Raum\Name$          =Name$
 Raum\Nr             =Nr 
 Raum\Richtung[Nord] =Richtung_Nord
 Raum\Richtung[Sued] =Richtung_Sued
 Raum\Richtung[West] =Richtung_West
 Raum\Richtung[Ost ] =Richtung_Ost
End Function

[Bearbeiten] Die heiß ersehnte Spiele-Programmierung

[Bearbeiten] Einleitung

[Bearbeiten] Die ersten Schritte

Um die ganzen Grafikfunktionen verwenden zu können, muss man zuerst in den Grafikmodus schalten. Dies wird uns mit BB ziemlich leicht gemacht, denn man muss nicht, wie in anderen Sprachen üblich, eine Halbseite Quellcode für dieses Thema opfern. Eigentlich steckt alles in einer Zeile:

Graphics Breite,Höhe,Farbtiefe,Fenstermodus

Das wäre es schon. In den ersten zwei Spalten trägt man die Auflösung ein. Gängige Werte sind 640/480, 800/600, 1024/768 oder 1280/1024. Allerdings sind je nach Bildschirm auch andere Kombinationen denkbar. Die Farbtiefe kann 16, 24 oder 32 Bit betragen. Der letzte Parameter gibt an, ob es ein Fenster oder Vollbild sein soll (1=Vollbild, 2=Fenster). 0 bedeutet an jeder Position, dass die Windows-Grundeinstellungen benutzt werden.

Im 3D-Bereich gelten die selben Parameter, nur der Befehl ändert sich:

Graphics3D Breite,Höhe,Farbtiefe,Fenstermodus (z.B Graphics3D 600,800,32,1) (1=Vollbild)

Aber davon erst später.

[Bearbeiten] Das Geheimnis der Buffer

Den Backbuffer kann man sich als rückwärtigen Teil des Bildschirms vorstellen; stimmt zwar nicht, aber so kann man die Funktionsweise am Besten verstehen. In Wirklichkeit werden die Daten im Backbuffer von der Grafikkarte berechnet, aber erst später ausgegeben. BB arbeitet standardmäßig mit dem Frontbuffer. Möchte man den Backbuffer verwenden, muss man dies BB mitteilen. Diese Technik ist bei Spielen mit vielen Elementen zwingend nötig, da sonst der Bildaufbau ewig dauert und man nichts sieht (nur sehr sporadisch). Es verhindert also das Flackern.

Setbuffer Backbuffer()
;Hier kommt dann der Mainloop etc...

Jetzt wird alles in den Backbuffer gezeichnet. Diese Technik ist unter dem Namen "double buffering" bekannt.

Setbuffer Backbuffer()
;Hier kommt dann der Mainloop (wird später erklärt)
Flip

Das Flip bringt BB dazu die Objekte aus dem Backbuffer auf dem Frontbuffer darzustellen (nicht vergessen, sonst bleibt der Bildschirm schwarz!). Manchmal ist es auch nötig Flip an mehreren Positionen zu setzen.

Anmerkung: Mit BlitzPlus wurde das Doublebuffering standarisiert. Das heißt, dass man nicht mehr "Setbuffer backbuffer()" einsetzen muss. Flip muss allerdings weiterhin eingesetzt werden!!!

[Bearbeiten] Mainloop, KoordinatenSystem und RGB-Werte

[Bearbeiten] Einzelne Pixel

Durch einen Befehl names Plot kann man einzelne Pixel anzeigen lassen.

Plot X,Y

Mit Plot und anderen Befehlen, lassen sich auch andere schöne Sachen basteln:

Graphics 640,480,16,2
SetBuffer BackBuffer()
SeedRnd MilliSecs()

For zaehler = 1 To 1000
x = Rnd(1,640)
y = Rnd(1,480)
Plot x,y
Next

Flip
WaitKey()
End

Allerdings ist es sehr mühesam bestimmte Formen wie Rechtecke etc. mit einzelen Plots zu zeichnen. Eine Alternative gibt es mit Rect, Oval und Line...

[Bearbeiten] Rechtecke und Kreise

Man kann einfache Sprites wie Rechtecke, Kreise oder Linien per BB-Befehl zeichnen lassen.

Rect PositionX,PositionY,Breite,Höhe,Füllung
Oval PositionX,PositionY,Breite,Höhe,Füllung

Dies ist sehr einfach zu verstehen. Der Befehl "Rect" zeichnet ein Rechteck oder Quadrat und der Befehl Oval ein Kreis oder eine Elipse. Die ersten 2 Parameter geben immer die Position auf dem Bildschirm in Pixeln an (Achtung! Bei Änderung der Auflösung muss man manchmal korrigieren). Parameter 3 und 4 gibt die Breite und die Höhe des Kreises oder des Rechteckes an (natürlich auch in Pixeln). Der letzte Parameter kann nur 1 oder 0 sein (wahlweise kann in solchen Situationen die 1 durch true und 0 durch false substituiert werden). 1 heißt, dass das Innere ausgefüllt wird, 0 das eben nicht ausgefüllt wird. Mit Color vor dem Zeichenbefehl lässt sich die Farbe festlegen.

Color R(Rot),G(Grün),B(Blau) ;immer ein Wert von 0-255 angeben 

Linien kann man etwas anders zeichnen!

Line X1,Y1,X2,Y2

X1 gibt die horizontale Startpostion in Pixeln an, Y1 die vertikale Startposition in Pixeln. Die beiden anderen Werte sind die Endwerte.

Beispiel:

Graphics 800,600,0,1
Line 150,80,350,80
waitkey() 
End

[Bearbeiten] Bilder

Bilder werden aus externen Quellen geladen, danach kann man sie mit entsprechenden Befehlen behandeln.

Bild = LoadImage("Pfad")

Durch diese Programmzeile wird ein Bild in den Hauptspeicher geladen, die Speicheradresse ist in der Variablen "Bild" gespeichert, diese benötigen wir zur Weiterverwendung dieses Sprites.

DrawImage Bild,300,250

Dies sagt BlitzBasic, dass das Bild an den Koordinaten x = 300 und y = 250 Pixel gezeichnet werden soll. Ein Sprite ist immer ein Viereck,schwarze Flächen werden nicht dargestellt, sie sind Transparent.

DrawBlock Bild,300,250

Hier wird der schwarze Hintergrund mit gezeichnet.

Graphics 1024,768,0,1
Setbuffer Backbuffer()
y = 250
x = 300
Bild = LoadImage("Irgend ein direkter oder relativer Pfad")

Repeat
Cls 
  DrawImage Bild,x,y
  If Keydown(203) then x=x-1
  if Keydown(205) then x=x+1
Flip
Until Keydown(1)
End

Wenn man jetzt die linke Pfeiltaste betätigt bewegt sich das Bild nach Links,wenn man die Rechte Pfeiltaste drückt,analog dazu nach Rechts.So werden dann auch die Spielfiguren in Spielen gesteuert.

Zur Ergänzung, um die Figur sich noch auf der Y-Achse bewegen zu lassen fügt diesen Befehl ein.

 If KeyDown(208) Then y=y+1
 If KeyDown(200) Then y=y-1

Relativer Pfad:

Bild = LoadImage("Irgend ein Bild.png")

Direkter Pfad:

Bild = LoadImage("Laufwerkskennung(z.B C:\):\irgend ein Bild.png")

Bei einem relativen Pfad muss die bezeichnete Datei nur in der Nähe der .exe Datei sein.Beim direkten Pfad muss sich die Datei zwingend im angebenen Ordner und Laufwerk befinden.

[Bearbeiten] Kollisionen

[Bearbeiten] Dateien

[Bearbeiten] Ordner

[Bearbeiten] Ordner lesen

Mit dem Befehl Readdir(pfad$) kann man den Inhalt eines Ordners auslesen. Das Handle, das dieser Befehl zurückliefert, kann dann genutzt werden, um die einzelnen Einträge (Dateien und Unterordner) im Ordner zu finden. z.B.:

dir=readdir("C:\Programme")
repeat
filename$=nextfile$(dir)
print filename$
until filename$=""

[Bearbeiten] Ordner erstellen

In BlitzBasic kann man ganz einfach mit

  CreateDir der_pfad$

einen Ordner erstellen.

Zu beachten, ist der Pfad, der entweder Global(C:/Blitz/Samples/Gfx/) oder Relativ (Samples/Gfx/) zur .bb/.exe Datei (hier müsste sich dann die .bb/.exe Datei im C:/Blitz/Samples/Gfx/ Ordner befinden).

[Bearbeiten] Ordner löschen

In BlitzBasic kann man ganz einfach mit

DeleteDir der_pfad$

einen Ordner löschen.

Zu beachten, ist der Pfad, der entweder Global(C:/Blitz/Samples/Gfx/) oder Relativ (Samples/Gfx/) zur .bb/.exe Datei (hier müsste sich dann die .bb/.exe Datei im C:/Blitz/Samples/Gfx/ Ordner befinden).

[Bearbeiten] Ordner wechseln

Mit

ChangeDir 

wechselt man den Ordner.

Mit

ChangeDir ".."

wechselt man zum Übergeordneten Ordner.

[Bearbeiten] Dateien

[Bearbeiten] Dateien erstellen

Um eine Datei zu erstellen muss zuerst ein Stream geöffnet werden

stream=WriteFile("Test.txt")

nun ist die Datei "Test.txt" zum schreiben geöffnet. Um Inhalt hinzuzufügen nutzt man folgende Befehle:

WriteByte stream, wert ;Ganzzahl von 0 bis 255
WriteShort stream, wert ;Ganzzahl von -32768 bis 32767
WriteInt stream, wert ;Ganzzahl von -2147483648 bis 2147483647
WriteFloat stream, wert ;Gleitkommazahl von -2 Mrd bis +2 Mrd
WriteLine stream, wert ;Text
WriteString stream,wert ;Text

Anschließend muss die Datei mit

CloseFile stream

geschlossen werden.

[Bearbeiten] Dateien öffnen

Um eine Datei zu öffnen ist nicht viel notwendig. Es reicht folgende Zeile zu schreiben:

file = ReadFile("text.txt")


Dabei kann man auch Zeichenkettenvariablen (Strings) als Angabe zur Datei verwenden:


filename$ = input("Name der Datei: ") ;Hier wird der Name der Datei eingegeben
file = ReadFile(filename$)


Man kann auch eine Datei zum Schreiben UND Lesen öffnen, das geht mit OpenFile:


file = OpenFile("text.txt") ;Die Datei wird hier mit Lese- UND Schreibzugriff geöffnet


OpenFile sollte man nur verwenden wenn in die Datei schreiben UND von der Datei lesen kann (zum Beispiel von der Festplatte), denn sonst funktioniert er nicht (zum Beispiel von einer CD).

[Bearbeiten] Dateien lesen

DateiNr=Readfile(NameDerDatei$)

Mit den folgenen Befehlen kann gelesen werden:

Liest eine Zeile ein.

Wert$=ReadLine$(DateiNr)

Liest einen Datensatz ein.

Wert$=ReadString$(DateiNr)

Liest einen Integerwert ein.

Wert%=ReadInt%(DateiNr)
Wert%=ReadByte%(DateiNr)
Wert%=ReadShort%(DateiNr)
Wert#=ReadFloat#(DateiNr)

[Bearbeiten] Dateien umbennen

Um eine Datei umzubennen muss man in BB sich eine Funktion selber schreiben.

  Function RenameFile(oldname$,newname$)
       Copyfile( oldname$, newname$ )
       DeleteFile oldname$
  End Function 


Anwendung:

RenameFile("alter name.txt","neuer name.txt")

[Bearbeiten] Dateien ausführen

Um eine Datei, wie zum Beispiel ein externes Programm zu starten, genügt:

EXECFILE "C:\WINDOWS\system32\cmd.exe"

Wenn das Blitz-Programm im Vollbild läuft, wird es minimiert und gestoppt. Außerdem fährt das Programm danach fort, egal ob das aufgerufene Programm noch läuft oder nicht. Es können auch Dateien angeben werden, die mit einem Programm verknüpft sind (*.TXT,*.BMP,*.DOC,...).

Beispiel:

EXECFILE "MeinDokument.TXT"

Desweiteren kann man auch noch den Browser öffnen und eine URL aufrufen lassen:

Beispiel:

EXECFILE "http://www.blitzforum.de"

[Bearbeiten] Dateien schreiben

Um Daten in einer Datei schreiben zu können, muss man sie erstmal für einen schreibenden Zugriff öffnen. Dabei hat man zwei Möglichkeiten:

Erstellen einer neuen Datei (eine evt. vorhandene Datei wird überschrieben):

DateiNr=WriteFile(Dateipfad$)

Daten an eine Datei anhängen:

DateiNr=OpenFile(Dateipfad$)

Wenn DateiNr jetzt ungleich Null ist, konnte die Datei erstellt bzw. geöffnet werden.

Zum Schreiben der Daten in eine Datei stehen die gleichen Befehle wie beim Lesen zur Verfügung nur nicht mit dem Namen read*** sondern write***

Wert$=ReadLine$(DateiNr)

Schreibt eine Textzeile.

Wert$=ReadString$(DateiNr)

Schreibt einen Datensatz ohne abschließenden Zeilenumbruch.

Wert%=ReadInt%(DateiNr)

Schreibt einen Integerwert.

Wert%=ReadByte%(DateiNr)

Schreibt ein Byte.

Wert%=ReadShort%(DateiNr)

Schreibt einen Shortwert.

Wert#=ReadFloat#(DateiNr)

Schreibt eine Kommazahl
Wenn man alle Daten geschrieben hat, muss man die Datei noch schließen:

CloseFile DateiNr

[Bearbeiten] Wir programmieren unser zweites Spiel (Pong)

[Bearbeiten] Einleitung

[Bearbeiten] Was soll es denn werden?

Natürlich ein Spiel! Also soll es Spaß machen, es zu spielen. Doch auch das Programmieren soll Spaß bereiten. Bevor man wild drauf los programmiert, sollte man sich eine Liste machen, was man programmieren will:
Es soll ein einfaches Ping- Pong- Spiel werden. Ein Spieler wird oben (Spieler2) und einer unten (Spieler1) sein. Es gibt einen Spielball, der von den Spielern hin und her gespielt wird. An den Seiten gibt es eine Wand, an der der Ball abprallt. Um das Ganze interessanter zu gestalten, sind die Spieler wie ein Halbkreis geformt. Dadurch wird die Flugbahn der Kugel verändert. Ziel ist es, die Kugel so zu bewegen, dass der Gegenspieler sie nicht mehr erreicht.

Nun hätten wir erst mal den Grundaufbau. Doch so macht es noch nicht wirklich viel Spaß beim Spielen, deshalb kannst du einige Besonderheiten hineinprogrammieren:

  • Der Hintergrund könnte ständig seine Farbe wechseln(Farbverlauf).
  • Der Spielstand soll anhand der durchgelassenen Bälle gezählt und angezeigt werden.
  • Die Anzahl der Punkte, die man braucht, um ein Spiel zu gewinnen, soll einstellbar sein
  • Auch soll ein Endlosspiel möglich sein.
  • Als Letztes wird es eine Pausenfunktion geben.

Das hört sich jetzt zunächst recht kompliziert an, doch es liegt alles im Bereich des Machbaren. Zuerst werden wir das Grundgerüst bauen, in das der Rest später eingebaut wird. Ich werde mich bemühen, alles möglichst verständlich zu schreiben, jedoch soll dies bereits das 2. Spiel werden. Ich werde deswegen nicht jeden Befehl so genau beschreiben. Wenn du einige Befehle nicht kennst, kannst du sie sicher in diesem Buch nachschlagen. Wenn du sie nicht findest, kannst du gerne auf der Diskussionsseite danach fragen. Bei Fragen oder Verbesserungsvorschlägen, bin ich auf der Disskusionsseite zu erreichen oder ihr könnt mir eine Mail schicken (axe.site@gmail.de). Außerdem ist dieses Tutorial auch auf meiner Homepage (Adresse ist auf der Diskussionsseite) zu erreichen.

Da wir für dieses Projekt einige Bilder, Dateien usw. benötigen, lege bitte irgendwo einen Ordner mit dem Namen "Ping Pong" (oder so ähnlich) an, wo du die Programme speichern kannst und dort einen Unterordner "gfx", dort werden wir später die ganzen Dateien unterbringen.


Als Struktur sieht das dann so aus:

  • Ping Pong (für die Programme)
    • gfx (für Bilder, Dateien)

[Bearbeiten] Grundgerüst

[Bearbeiten] Die ersten Zeilen

Am Anfang benötigen wir natürlich erst mal ein Grafikfenster:

Graphics 1024,768,32,1

Damit wir später einmal die Auflösung ändern können bzw. nicht immer diese Zahlen wissen müssen, kann man auch folgendes schreiben:

global xmax=1024,ymax=768 ;damit definieren wir die Auflösungs-Variablen für das gesamte Programm
Graphics xmax,ymax,32,1  ;die Variablen werden nur eingesetzt, funktioniert wie oben

damit wir später nicht laufend den Bildaufbau sehen, werden wir den Backbuffer benutzen:

SetBuffer BackBuffer()

Nun brauchen wir noch eine Framebegrenzung, denn das Spiel soll ja immer gleich schnell ablaufen:

Global frametimer = CreateTimer(60)

Und zum Schluss des Anfangs wollen wir dem Zufall noch auf die Sprünge helfen:

SeedRnd MilliSecs() ; Damit initialisieren wir den Zufall mit der Anzahl an Millisek. seit dem Starten von Windows

Zusammengefasst ergibt sich also folgender Quelltext für die ersten Zeilen:

Global xmax=1024,ymax=768 
Graphics xmax,ymax,32,1 
SetBuffer BackBuffer() 
Global frametimer = CreateTimer(60) 
SeedRnd MilliSecs()

[Bearbeiten] Die Hauptschleife

Als unsere Hauptschleife werden wir eine für Spiele typische "Repeat - Until"-Schleife verwenden. Diese Schleifenart funktioniert so, dass am Ende der Schleife(until) eine Bedingung abgefragt wird. In unserem Fall soll festgestellt werden, ob Escape gedrückt wurde. Nachdem die Bedingung (Escape drücken) erfüllt wurde wird das Programm nach der Schleife vorgesetzt, in unserem Fall soll das Programm beendet werden:

Repeat;wiederhole
;hier wird gleich der Inhalt eingesetzt
Until Keyhit(1);bis Escape gedrückt wurde
End

Eigentlich doch nicht so schwer, oder?

[Bearbeiten] Das erste kleine Testprogramm

Nun wird es etwas komplizierter. Doch keine Angst wir werden das schon schaffen!
Wie die Überschrift schon sagt wollen wir nun schon mal ein kleines (lauffähiges) Programm erstellen. Dazu setzten wir die ersten Zeilen und die Hauptschleife zusammen:

global xmax=1024,ymax=768 
Graphics xmax,ymax,32,1 
SetBuffer BackBuffer() 
Global frametimer = CreateTimer(60) 
SeedRnd MilliSecs()

Repeat
 Waittimer (frametimer);damit nutzen wir den oben erstellten Frametimer

 ;hier kommt später noch mehr rein

 Text xmax/2,ymax/2,"Das erste kleine Testprogramm zu Ping Pong!";das gibt einen Text ausgehend vom Mittelpunkt aus.
 Flip ;da wir den Hintergrundpuffer aktiviert haben, wechseln wir jetzt die Seiten, damit wir was sehen
 Cls ;der nichtsichtbare Bereich wird gelöscht
Until Keyhit(1)
End ;damit wird unser Programm beendet

Um dieses Wunderwerk auszuprobieren einfach kopieren und in ein leeres BB Fenster einfügen!

Das ist doch schon sehr gut, nicht wahr? Um alles richtig zu verstehen, könnt ihr gerne ein bisschen damit herum experimentieren (z.b. könnte man das Programm doch auch über eine andere Taste beenden).

[Bearbeiten] Die Grafik kommt ins Spiel

Nun wollen wir uns mal um die Grafiken kümmern, denn was ist ein Spiel schon ohne Grafik?
Zuerst wolen wir uns mal überlegen, was wir so an Grafiken brauchen:

  • die Kugel
  • die Spieler (unten und oben)
  • die Seitenwände (Stahlträger)

[Bearbeiten] Die einzelnen Bilddateien erstellen

Die benötigten Bilder habe ich bereits gemalt. Um sie hier zur Verfügung zu Stellen, musste ich sie jedoch in das PNG-Format umwandeln. Um die Bilder aber auch in der Demoversion von Bltiz Basic verwenden zu können, musst du sie in BMP Grafiken umwandeln. Das sollte eigentlich nicht schwer sein, denn das kann jedes Bildbearbeitungsprogramm. Die Bilder speicherst du bitte in den Unterordner "gfx", den wir ja schon erstellt haben. Der Dateiname steht gleich bei den Dateien. Wenn dir meine Bilder nicht gefallen kannst du gerne eigene verwenden, die entsprechenden Abmessungen stehen ebenfalls unten.

Du kannst die Bilder auch auf meiner Homepage (Adresse ist auf der Diskussionsseite) downloaden. Dann entfällt das Konvertieren.

[Bearbeiten] Die Kugel

Die Kugel ist bei mir einfach nur ein weißer Kreis auf schwarzem Hintergrund. Natürlich könnte man in Blitz Basic auch einen Kreis malen, jedoch wollen wir eine Kollisionsabfrage benutzen, da ist es so praktischer.(Dateiname:"kugel.bmp" Abmessung 20*20 Pixel)
Weiße Kugel

[Bearbeiten] Die Spieler

Für die Spieler habe ich einfach einen Halbkreis gemalt, später kann man durch die Krümmung die Flugbahn der Kugel beeinflussen. (Dateiname:"pongspieler1.bmp" Abmessung 80*20 Pixel). Um die Grafik für den 2. Spieler zu erstellen, musst du dieses Bild einfach um 180 Grad drehen und erneut abspeichern (Dateiname: "pongspieler2.bmp").
Pong Spieler1

[Bearbeiten] Die Seitenwände

Die Seitenwände sehen bei mir wie Stahlträger aus, sie werden aus einer einzelnen Bilddatei zusammengesetzt. Wie man diese im Programm zusammensetzt, kommt später.(Dateiname:"seitenwand.bmp" Abmessung 50*50 Pixel).
Stahträgerfragment

[Bearbeiten] Die Einbindung der Bilddateien ins Programm

Nun kommen wir wieder zu unserem eigentlichen Programm zurück.

Da wir dabei sind ein Spiel zu programmieren, werden wir die verschiedenen Grafikbefehle in BB ausnutzen. Wir werden also für jedes Bild den Transparentmodus verwenden. Wie du vieleicht schon bemerkt hast, haben die Grafiken verschiedene Hintergrundfarben. Diese müssen wir beachten, damit der Transparentmodus wirklich funktioniert.

Der nun folgende Code muss direkt oberhalb (vor) der Hauptschleife eingebaut werden:
Hinweis: Die Pfadangaben sind relativ, d.h. man muss nicht den gesamten Pfad (C:\...) angeben, sondern nur den Pfad ausgehend von dem Speicherort des Programmes.

In der Praxis bedeutet das:

  • Ordner, wo die Programmdatei liegt(bei uns "Ping Pong")
    • Unterordner, der relative Pfad würde nur "Unterordner" heißen(bei uns also nur "gfx")
;...

Global kugel=LoadImage("gfx\kugel.bmp");damit wird die Kugel in eine Variable geladen
MidHandle kugel ;der Urspung wird auf den Mittelpunkt gelegt, das macht sich bei Berechnungen besser
;auf das Einstellen der Tranzparenzfarbe kann verzichtet werden, weil sie Standard mäßig schwarz ist.

Global spieler1=LoadImage("gfx\pongspieler1.bmp");damit ist der Untere gemeint
MidHandle spieler1;auch dieser bekommt seine Ursprung in die Mitte (s.Kugel)
MaskImage spieler1,255,255,255;hier müssen wir die Tranzparenzfarbe angeben, weil sie vom Standard abweicht

Global spieler2=LoadImage("gfx\pongspieler2.bmp");damit ist der Obere gemeint
MidHandle spieler2;auch dieser bekommt seine Ursprung in die Mitte (s.Kugel)
MaskImage spieler2,255,