BlitzBasic-Community-Tutorial

Aus Wikibooks

Wechseln zu: Navigation, Suche
QSicon Formatierung Blue.svg 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.


Gnome-applications-office.svg 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. Einen ersten Einblick in die Sprache gibt www.blitzbasic.de. 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://blitzbasic.de/download.htm

Ich empfehle zum Ausprobieren Blitz3D.

[Bearbeiten] Grundlagen

[Bearbeiten] Print und Kommentare

Fangen wir mit unserem ersten Programm an. Tippe folgendes in die BlitzBasic-IDE 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":

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



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.

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","?"
    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 800,600,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()
; Mainloop Beginn(wird später erklärt)
Flip
; Mainloop Ende

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 standardisiert. 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 800,600,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 nur die Umrisse gezeichnet werden. 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

Kollisionen sind vonnöten, wenn z.B.ein Raumschiff gegen einen Asteroiden prallen und dann explodieren soll.

Hier der Code:

  If imagescollide(bild1, x, y, ramen, bild2, x, y, ramen) then /aktion\
  

oder

  If imagesoverlap(bild1, x, y, ramen, bild2, x, y, ramen) then /aktion\

Man beachte hierbei, dass für ramen eine 1 für ja steht und eine 0 für nein. Und der Unterschied zwischen collide und overlap ist folgender: collide ist pixelgenaue Kollision und overlap ist einfache Kollision.

Also hier ein Beispiel:

   graphics 800, 600
   setbuffer backbuffer()

   variable = loadimage("Bildpfad")
   variable2 = loadimage("Bildpfad")

   repeat
   cls   

   drawimage variable, mousex(), mousey()
   drawimage variable2, x, y
   
   if imagescollide(variable, mousex(), mousey(), 0, variable2, x, y, 0) then
   print "Kollision!!!"

   flip
   until keyhit(1)

   end

Hier ein kleines Beipiel mit .bmp-Bildern (geht auch mit demo): Collidedemo.zip

So, das wars schon fast.

Aber was ist, wenn wir jetzt ein Rechteck mit einem Bild kollidieren lassen wollen?

Ganz einfach:

  If imagerectcollide(bild, x, y, 0, rechteck, x, y, breite, höhe) then /aktion\

oder

  if imagerectoverlap(bild, x, y, 0, rechteck, x, y, breite, höhe) then /aktion\

Hierzu noch ein Beispiel:

   graphics 800, 600
   setbuffer backbuffer()

   variable = loadimage("Bildpfad")

   repeat
   cls

   drawimage variable, mousex(), mousey()
   drawimage variable2, x, y
   
   if imagerectcollide(variable, mousex(), mousey(), 0, rechteck, x, y, breite, höhe) then 
   print "Kollision!!!"

   flip
   until keyhit(1)

   end

[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$=WriteLine$(DateiNr)

Schreibt eine Textzeile.

Wert$=WriteString$(DateiNr)

Schreibt einen Datensatz ohne abschließenden Zeilenumbruch.

Wert%=WriteInt%(DateiNr)

Schreibt einen Integerwert.

Wert%=WriteByte%(DateiNr)

Schreibt ein Byte.

Wert%=WriteShort%(DateiNr)

Schreibt einen Shortwert.

Wert#=WriteFloat#(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 fortgesetzt, 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 wollen 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,255,255;hier müssen wir die Tranzparenzfarbe angeben, weil sie vom Standard abweicht

Global begrenzung=LoadImage("gfx\seitenwand.bmp");damit wird die Seitenwand geladen
MaskImage begrenzung,255,255,255;hier müssen wir die Tranzparenzfarbe angeben, weil sie vom Standard abweicht

Repeat
;...

[Bearbeiten] Die Seitenwände

Nun haben wir die Seitenwände ja bereits geladen, doch da dieses Bild ja nur 50*50 Pixel groß ist, müssen wir es mehrmals untereinander setzten, damit erhalten wir dann auch einen richtigen Stahlträger.

Doch nun zum Code:
Ich habe es mir ursprünglich so gedacht, dass die Positionen vor der Hauptschleife in einem Dim Feld berechnet und gespeichert werden.
Jedoch ist es einfacher, wenn man die Positionen erst in der Hauptschleife berechnet, denn dabei benötigt man kein Dim-Feld. Deshalb musst du den folgenden Code in die Hauptschleife einbauen:

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

For z2=0 To 1;also wird das ganze 2x durchlaufen (links und rechts)
 If z2=1 Then x=xmax-50 Else x=0 ;wenn die Schleife zum 2. Mal durchlaufen wird, werden die Steine rechts gemalt
 For z= 0 To ymax/50;diese Schleife sorgt für die Verschiebung auf der Y-Achse
  DrawImage begrenzung,x,z*50;x ist der Wert aus der 1. Schleife, y wird durch die Schleife immer um 50 erhöht
 Next
Next

Zugegeben ist diese Schleifenkonstruktion etwas kompliziert, deshalb pflücken wir sie noch mal auseinander:

Die erste Schleife (for z2=0 to 1) wird sichergestellt, dass die Reihe 2x gemalt wird: Das erste Mal links (x=0), das zweite Mal rechts (x=xmax-50 (1024-50=974)). Diese X Werte werden mit der If Konstruktion berechnet.
Nun die 2. Schleife: Diese hat eigentlich die Funktion, die Y-Werte zu berechnen. Jedoch nur solange sie wirklich auf dem Bildschirm sichtbar sind. 1024/50=20 Die Schleife wird also 20 mal durchlaufen.
Jetzt sind wir im inneren der beiden Schleifen, dort werden schließlich die Begrenzungsstein gemalt. Hierzu wird DrawImage verwendet, damit der Transparenzmodus genutzt wird(s.oben). Die Parameter bedeuten folgendes:

  • begrenzung: das ist die Variable in der wir die Grafik der Seitenbegrenzung geladen haben
  • x: gibt die X-Position an, wird durch die If Anweisung gesteuert, ist entweder 0 (links) oder xmax-50 (1024-50=974) (rechts)
  • z*50: gibt die Y-Position an, wird durch die 2. Schleife gesteuert, wird durch die Multiplikation mit 50 jewals um 50 erhöht (Werte:0,50,100,...,700,750)

Ich hoffe das ich das jetzt so erklärt habe, das du es verstanden hast, wenn nicht schreibe es bitte auf der Disskusionsseite.

[Bearbeiten] Zusammenfassung Grafik

Dieses Kaptitel war nun schon etwas anspruchsvoller. Wir haben hier die Grundlagen für das Aussehen des Spiels gelegt. Damit man davon mal etwas sieht, folgt nun das 2.Testprogramm:

[Bearbeiten] Das 2.Testprogramm

Um dieses Programm zum Laufen zu bringen, müssen die Grafiken im Ordner "gfx" sein und die jeweils richtigen Namen haben.

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

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,255,255;hier müssen wir die Tranzparenzfarbe angeben, weil sie vom Standard abweicht

Global begrenzung=LoadImage("gfx\seitenwand.bmp");damit wird die Seitenwand geladen
MaskImage begrenzung,255,255,255;hier müssen wir die Tranzparenzfarbe angeben, weil sie vom Standard abweicht

ClsColor 255,0,0;damit wird der Hintergrund rot, du kanst sonst die Spieler und Seitenwände nicht sehen

Repeat
Waittimer (frametimer)

For z2=0 To 1
 If  z2=1 Then x=xmax-50 Else x=0
 For z=0 To ymax/50
  DrawImage begrenzung,x,z*50
 Next
Next


Text xmax/2,ymax/2,"Das zweite Testprogramm zu Ping Pong!";das gibt einen Text ausgehend vom Mittelpunkt aus.

DrawImage kugel,400,400;nur damit man sieht, das sie existiert
DrawImage spieler1,400,600
DrawImage spieler2,400,100

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

[Bearbeiten] Jetzt kommt Bewegung ins Spiel

Wie die Überschrift schon sagt, werden wir uns jetzt mal darum kümmern, das sich die Sachen bewegen und du die Spieler steuern kanst.

[Bearbeiten] Die Spieler

Als erstes kümmern wir uns mal um die Steuerung der Spieler:

Um die Y-Position brauchen wir uns eigentlich nicht kümmern, denn da Spieler immer oben bzw. unten sind, ist die Y-Position immer gleich: Wir müssen nur beachten, dass die Y-Pos. des Oberen nicht 0 ist, denn wir haben seinen Ursprung ja auf den Mittelpunkt gesetzt (s. oben). Um die Y-Position zu bekommen, müssen wir also die Höhe der Spieler grafik durch 2 teilen: 20/2=10 Also ist die Y-Pos. des Oberen immer 10. Da der 1.Spieler ja unten ist, bedeutet das, dass er dort 10 Pixel vom Rand entfernt ist also ymax-10 (768-10=758).

Nun brauchen wir noch die Bewegung der Spieler:

Damit du später das Spiel richtig steuern kanst, bzw. auch alleine spielen kannst, wird es also mehrere Arten der Steuerung geben:

  • Computer
  • Tastatursteuerung
  • Maussteuerung

Damit wir diese im Programm leicht verändern können, werden wir also Variablen vergeben:

Global sp1st ;bedeutet: Spieler 1 Steuerung
Global sp2st
Global xsp1 ;für die X-Position des 1. Spielers
Global xsp2 ;für die X-Position des 2. Spielers

Dieser Code muss vor der Hauptschleife eingebaut werden.

Damit auch etwas passiert, müssen wir in der Hauptschleife also einige Tasten abfragen:
Dafür brauchen wir die Scancodes, die man eigentlich überall findet, ich werde trotzdem immer mit angeben, welche Taste damit gemeint ist.

If KeyDown(203) And sp1st=1 Then xsp1=xsp1-beweglichkeit;linke Pfeiltaste
If KeyDown(205) And sp1st=1 Then xsp1=xsp1+beweglichkeit;rechte Pfeiltaste

Im Klartext bedeutet das:
Wenn die linke Pfeiltaste gedrückt ist und die Steuerungsvariable 1 ist(was für Tastatursteuerung stehen soll) dann verringer die X-Pos des 1. Spielers um die Beweglichkeit. Die Beweglichkeit wird später behandelt.
Um den 2. Spieler bewegen zu können, brauchen wir natürlich 2 andere Tasten, ich habe mir Y und X gedacht:

If KeyDown(44) And sp2st=1 Then xsp2=xsp2-beweglichkeit;Y Taste
If KeyDown(45) And sp2st=1 Then xsp2=xsp2+beweglichkeit;X Taste

Nun könne wir schon unsere beiden Spieler bewegen, doch sie können auch außerhalb des Bildschrims sein, das darf es eigentlich nicht geben. Deswegen machen wir eine einfache If Abfrage ob die Position unzulässig ist:

If xsp1<70 Then xsp1=70
If xsp1>xmax-70 Then xsp1=xmax-70
If xsp2<70 Then xsp2=70
If xsp2>xmax-70 Then xsp2=xmax-70

Wie komme ich jetzt wieder auf die 70? Da wir später die Flugbahn der Kugel durch die Krümmung verändern wollen, soll es auch möglich sein, die Kugel knapp am Rand gerade zurückzubewegen. Wenn das jetzt noch nicht einleuchtet, wirst du das spätestens beim ersten richtigen spielen merken.

Wie oben geschrieben, wollen wir ja nicht nur die Tastatursteuerung sondern auch eine Maussteuerung ermöglichen:

If sp1st=2 Then xsp1=xsp1+MouseXSpeed()
If sp2st=2 then xsp2=xsp2+MouseXSpeed()

Diese Abfrage ist eigentlich recht einfach: Wenn die Steuerungvariable 2 ist (was für Maus stehen soll), dann wird zu der X-Pos. einfach der Unterschied der X- Mauskoordinaten addiert. Dies ist ein sehr nützlicher Befehl der uns von Blitz Basic zur Verfügung gestellt wird.

Als letztes fehlt uns noch die automatische Steuerung der Spieler:

If xsp1>xkugel And sp1st=0 Then xsp1=xsp1-beweglichkeit
If xsp1<xkugel And sp1st=0 Then xsp1=xsp1+beweglichkeit

If xsp2>xkugel And sp2st=0 Then xsp2=xsp2-beweglichkeit
If xsp2<xkugel And sp2st=0 Then xsp2=xsp2+beweglichkeit

Dises Abfrage sollte eigentlich nicht so schwer sein: xkugel steht für die X-Position der Kugel, die wir später noch brauchen. Diese Steuerung ist noch etwas unvollständig, wir werden sie später noch ergänzen.
Als Letztes brauchen wir natürlich noch Umschaltmöglichkeit für die Steuerungsarten: Dafür fragen wir die Tasten 1 und 2 ab:

If KeyHit(2) Then sp1st=sp1st+1;die Zahlentaste 1
If KeyHit(3) Then sp2st=sp2st+1;die Zahlentaste 2

If sp1st>2 then sp1st=0
If sp2st>2 then sp2st=0

Natürlich dürfen die Steuerungsvariablen nur im Bereich 0-2 liegen, deswegen werden sie noch überprüft.

Das war die Steuerung der Spieler. Hier noch mal eine Zusammenfassung: Der Code gehört direkt unter das Malen der Seitenbegrenzung.

;...
;Seitenbegrenzung malen

;als erstes die Abfrage zur Steuerungsart
If KeyHit(2) Then sp1st=sp1st+1;die Zahlentaste 1
If KeyHit(3) Then sp2st=sp2st+1;die Zahlentaste 2

If sp1st>2 then sp1st=0
If sp2st>2 then sp2st=0

;die automatische Steuerung, wenn die Steuerungsvariable 0 ist:
If xsp1>xkugel And sp1st=0 Then xsp1=xsp1-beweglichkeit
If xsp1<xkugel And sp1st=0 Then xsp1=xsp1+beweglichkeit

If xsp2>xkugel And sp2st=0 Then xsp2=xsp2-beweglichkeit
If xsp2<xkugel And sp2st=0 Then xsp2=xsp2+beweglichkeit

;die Tastatursteuerung, wenn die Steuerungsvariable 1 ist:
If KeyDown(203) And sp1st=1 Then xsp1=xsp1-beweglichkeit;linke Pfeiltaste
If KeyDown(205) And sp1st=1 Then xsp1=xsp1+beweglichkeit;rechte Pfeiltaste

If KeyDown(44) And sp2st=1 Then xsp2=xsp2-beweglichkeit;Y Taste
If KeyDown(45) And sp2st=1 Then xsp2=xsp2+beweglichkeit;X Taste

;die Maussteuerung, wenn die Steuerungsvariable 2 ist:
If sp1st=2 Then xsp1=xsp1+MouseXSpeed()
If sp2st=2 then xsp2=xsp2+MouseXSpeed()

;die Überprüfung:
If xsp1<70 Then xsp1=70
If xsp1>xmax-70 Then xsp1=xmax-70
If xsp2<70 Then xsp2=70
If xsp2>xmax-70 Then xsp2=xmax-70

;...

[Bearbeiten] Die Kugel

Zu der Kugel brauch ich eigentlich nicht wirklich viel sagen.

Als erstes brauchen wir wieder ein paar Variablen: Diese können an die Variablen der Spieler angehangen werden.

Global xkugel# ;X-Pos. der Kugel als Kommazahl
Global ykugel# ;Y-Pos. der Kugel als Kommazahl
Global winkel  ;gibt den Flugwinkel der Kugel an 
Global geschwindigkeit ;die Fluggeschwindgikeit der Kugel

Wir haben die Koorodinaten der Kugel als Kommazahlen definiert, weil sich dadurch später eine weicher Bewegung ergibt.

Die eigentliche Bewegung:

Mithilfe der in Blitzbasic intigrierten Mathematik-Befehle lässt sich die Winkelbewegung mit Sinus und Cosinus recht einfach zusammenbauen.

Zu dem X und Y Koordinaten wird der Cosinus bzw. Sinus des Winkels addiert bzw. subtrahiert. Die Summe wird mit der Geschwindigkeit multipliziert.

xkugel=xkugel+Cos(winkel)*geschwindigkeit
ykugel=ykugel-Sin(winkel)*geschwindigkeit

Beispiel: Wenn xkugel 100, ykugel 90, der Winkel 120 und die Geschwindigkeit 3 wären, dann würde die Rechnung etwa so aussehen:

xkugel = 100 + (-0,5) * 3
ykugel = 90 - etwa (0,86) * 3

Die eigentliche Steuerung der Kugel wird ja nicht durch Tasten gemacht, sondern anhand von Kollisionen:

[Bearbeiten] Die Kollisionsabfragen

In diesem Kaptiel geht es eigentlich nur um das Steuern der Kugel. Zuerst müssen wir uns Überlegen, wo die Kugel überall kollidieren kann:

  • mit den Spielern
  • mit der Seitenbegrenzung

Damit es nicht zu kompliziert wird habe ich nicht überall echte Kollisionsabfragen eingebaut, deshalb fangen wir mit den Seitenwänden an:

[Bearbeiten] Die Seitenwände

Da die Seitenwänd ja immer an der gleichen Position sind, habe ich das ganze hier stark vereinfacht. Denn ich überprüfe nicht ob die Kugel mit der Wand kollidiert, sondern ob die Kugel einfach nur im Bereich ist, wo sie kollidieren müsste:

If xkugel<60 Then
 winkel=180-winkel ;damit prallt die Kugel ab
EndIf

If xkugel>xmax-60 Then
 winkel=180-winkel
EndIf

Wo kommt jetzt wieder die 60 her? Ganz einfach: Die Steine haben ja eine Breite von 50, die Kugel einen Radius von 10 Pixeln, das addiert ergibt 60.

[Bearbeiten] Die Spieler

Bei den Spielern können wir unseren Trick, wie bei den Seitenwänden, nicht anwenden. Trotzdem ist es hoffentlich nicht zu schwer:

If ImagesCollide(spieler1,xsp1,ymax-10,0,kugel,xkugel,ykugel,0) Then;wenn eine Kollision ist
 winkel=360-winkel ;prallt die Kugel ab
 abweichung=2*(xsp1-xkugel) ;damit kannst du die Richtung der Kugel steuern
 winkel=winkel+abweichung
EndIf

If ImagesCollide(spieler2,xsp2,10,0,kugel,xkugel,ykugel,0) Then
 winkel=360-winkel
 abweichung=2*(xsp2-xkugel)
 winkel=winkel-abweichung
EndIf

;damit der Winkel nicht über den Bereich von 0-360 geht:
If winkel>360 Then winkel=winkel-360
If winkel<0 Then winkel=winkel+360

Diese Steuerung must du nicht auf Anhieb verstehen, wie sie funktioniert, kannst du die am besten in der Praxis angucken und ausprobieren.

[Bearbeiten] Wenn ein Spieler die Kugel durchgelassen hat

...dann freut sich der andere!

Doch woher wissen wir ob er die Kugel durchgelassen hat? Mal wieder brauchen wir eine If Abfrage:

If ykugel<-10 Then ;oben
  fehlersp2=fehlersp2+1 ;der Obere bekommt einen Fehlerpunkt dazu
  resetkugel ;ein Funktionsaufruf
EndIf

If ykugel>ymax+10 Then ;unten
  fehlersp1=fehlersp1+1 ;der Untere bekommt einen Fehlerpunkt dazu
  resetkugel ;ein Funktionsaufruf
EndIf

Das ist schon alles! fehlersp1 und fehlersp2 sollen einfach nur der Fehler- oder Punktezähler sein. Es fehlt natürlich noch die Funktion "resetkugel":

Function resetkugel()
xkugel=xmax/2:ykugel=ymax/2
If Rand(0,1)=1 Then winkel=Rand(105,45) Else winkel=Rand(315,225);der Winkel wird durch Zufall ermittelt
End Function

Was zum Teufel soll das mit dem Winkel? Da hast du recht, das sieht irgendwie komisch aus. Ich könnte natürlich auch schreiben winkel=Rand(0,360), aber da existiert ein Unterschied: Bei der einfachen Version kann es vorkommen, dass die Kugel genau so nach links oder rechts fliegt, das sie nur zwischen den Seiten hin- und herprallt. Wenn das eben unverständlich war, dann probier das doch einfach mal aus, dann wirst du schnell wissen was ich meine.

So, jetzt haben wir eigentlich allen Code um alles zu bewegen und zu steuern. Es fehlen zwar noch ein paar Sachen, doch die sind nicht so lebenswichtig.

Deshalb kommt hier die erste Version des funktionstüchtigen Spiels:

[Bearbeiten] Die erste spielbare Version

Damit du eventuelle Fehler abfangen kannst, habe ich noch eine Rücksetzten Taste eingebaut, der Kommentar sollte als Erklärung reichen.

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

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,255,255;hier müssen wir die Tranzparenzfarbe angeben, weil sie vom Standard abweicht

Global begrenzung=LoadImage("gfx\seitenwand.bmp");damit wird die Seitenwand geladen
MaskImage begrenzung,255,255,255;hier müssen wir die Tranzparenzfarbe angeben, weil sie vom Standard abweicht

Global xsp1=xmax/2;also in der Mitte
Global xsp2=xmax/2
Global sp1st
Global sp2st
Global xkugel#
Global ykugel#
Global winkel
Global geschwindigkeit=5 ;wird mit Wert zugewiesen, weil man sie (noch) nicht ändern kann
Global fehlersp1
Global fehlersp2
Global beweglichkeit


ClsColor 255,0,0;damit wird der Hintergrund rot, du kanst sonst die Spieler und Seitenwände nicht sehen

resetkugel;damit sie an der richtigen Position ist

Repeat
Waittimer (frametimer)

beweglichkeit=geschwindigkeit

For z2=0 To 1
 If  z2=1 Then x=xmax-50 Else x=0
 For z=0 To ymax/50
  DrawImage begrenzung,x,z*50
 Next
Next

If KeyHit(14) Then resetkugel;wenn die Löschen Taste gedrückt wurde wird die Kugel zurück gesetzt

;die Spielersteuerung
If KeyHit(2) Then sp1st=sp1st+1;die Zahlentaste 1
If KeyHit(3) Then sp2st=sp2st+1;die Zahlentaste 2

If sp1st>2 then sp1st=0
If sp2st>2 then sp2st=0

;die automatische Steuerung, wenn die Steuerungsvariable 0 ist:
If xsp1>xkugel And sp1st=0 Then xsp1=xsp1-beweglichkeit
If xsp1<xkugel And sp1st=0 Then xsp1=xsp1+beweglichkeit

If xsp2>xkugel And sp2st=0 Then xsp2=xsp2-beweglichkeit
If xsp2<xkugel And sp2st=0 Then xsp2=xsp2+beweglichkeit

;die Tastatursteuerung, wenn die Steuerungsvariable 1 ist:
If KeyDown(203) And sp1st=1 Then xsp1=xsp1-beweglichkeit;linke Pfeiltaste
If KeyDown(205) And sp1st=1 Then xsp1=xsp1+beweglichkeit;rechte Pfeiltaste

If KeyDown(44) And sp2st=1 Then xsp2=xsp2-beweglichkeit;Y Taste
If KeyDown(45) And sp2st=1 Then xsp2=xsp2+beweglichkeit;X Taste

;die Maussteuerung, wenn die Steuerungsvariable 2 ist:
If sp1st=2 Then xsp1=xsp1+MouseXSpeed()
If sp2st=2 then xsp2=xsp2+MouseXSpeed()

;die Überprüfung:
If xsp1<70 Then xsp1=70
If xsp1>xmax-70 Then xsp1=xmax-70
If xsp2<70 Then xsp2=70
If xsp2>xmax-70 Then xsp2=xmax-70

;die Kollisionsabfrage der Spieler
If ImagesCollide(spieler1,xsp1,ymax-10,0,kugel,xkugel,ykugel,0) Then;wenn eine Kollision ist
 winkel=360-winkel ;prallt die Kugel ab
 abweichung=2*(xsp1-xkugel) ;damit kannst du die Richtung der Kugel steuern
 winkel=winkel+abweichung
EndIf

If ImagesCollide(spieler2,xsp2,10,0,kugel,xkugel,ykugel,0) Then
 winkel=360-winkel
 abweichung=2*(xsp2-xkugel)
 winkel=winkel-abweichung
EndIf

;damit der Winkel nicht über den Bereich von 0-360 geht:
If winkel>360 Then winkel=winkel-360
If winkel<0 Then winkel=winkel+360


;Kollisionsabfrage Seitenwände
If xkugel<60 Then
 winkel=180-winkel ;damit prallt die Kugel ab
EndIf

If xkugel>xmax-60 Then
 winkel=180-winkel
EndIf

;wenn die Kugel durchgelassen wurde
If ykugel<-10 Then ;oben
 fehlersp2=fehlersp2+1 ;der Obere bekommt einen Fehlerpunkt dazu
 resetkugel ;ein Funktionsaufruf
EndIf

If ykugel>ymax+10 Then ;unten
  fehlersp1=fehlersp1+1 ;der Untere bekommt einen Fehlerpunkt dazu
  resetkugel ;ein Funktionsaufruf
EndIf



;das Berechnen der Kugelkoordinaten
xkugel=xkugel+Cos(winkel)*geschwindigkeit
ykugel=ykugel-Sin(winkel)*geschwindigkeit

;einige Informationen werden auf den Bildschirm gemalt
Text 200,300,"Geschwindigkeit:" + geschwindigkeit
Text 500,300,"Spielstand:  "+fehlersp2 + "       :       " + fehlersp1
;damit du die Steuerungsart sehen kannst
Text 55,ymax-25,"Spieler1" 
If sp1st>0 Then
	If sp1st=2 Then Text 120,ymax-25,"Maussteuerung" Else Text 120,ymax-25,"Tastatursteuerung"
	Else Text 120,ymax-25,"Computer"
EndIf
;If spieler1man>0 Then Text 250,ymax-25,scorep1

Text 55,5,"Spieler2" 
If sp2st>0 Then 
 If sp2st=2 Then
  Text 120,5,"Maussteuerung"
 Else
  Text 120,5,"Tastatursteuerung"
 Endif
Else
  Text 120,5,"Computer"
EndIf


DrawImage kugel,xkugel,ykugel;nur damit man sieht, das sie existiert
DrawImage spieler1,xsp1,ymax-10
DrawImage spieler2,xsp2,10

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 

 
Function resetkugel()
xkugel=xmax/2:ykugel=ymax/2
If Rand(0,1)=1 Then winkel=Rand(105,45) Else winkel=Rand(315,225);der Winkel wird durch Zufall ermittelt
End Function

Damit kann man doch schon halbwegs spielen, oder? Natürlich gibt es noch einige Fehler und um ein komfortables Spielen zu ermöglichen fehlen noch ein paar Funktionen. Zuerst hatte ich eigentlich vor, den Quellcode weiter zu veröffentlichen, aber ich habe mich umentschlossen. Ich werde das Tutorial hier nicht mehr fortführen. Die Ideen für die Weiterentwicklung kannst du dann selbst umsetzen. Momentan programmiere ich eine 3d Version von Ping Pong. Diese werde ich dann als closed Code veröffentlichen.

[Bearbeiten] Sound

Beim Sound kann man zwei unterschiedliche Arten unterscheiden: Zum Ersten die meist nur kurzen Sounddaten die als Efekte abgespielt (z.B. wenn man ein PowerUp einsammelt) und zum Zweiten die Hintergrundmusik.

[Bearbeiten] Sounds Laden & Abspielen

Bevor man einen Sound abspielen kann, muss er in den Speicher geladen werden. Das sollte man am Besten am Anfang des Programmes machen. Die möglichen Dateiendungen sind bei der Demo nur .wav. In der Vollversion kommen noch .mp3 und .raw dazu.

SoundId=LoadSound(Soundpfad$)

Später kann man dann den Sound mit PlaySound abspielen:

ChannelId=PlaySound(SoundId)

Mit Hilfe der ChannelId kann man den Sound während der Wiedergabe verändern.(siehe unten)

Während der Wiedergabe eines Sounds kann man die Wiedergabe mit

PauseChannel ChannelId

unterbrechen. Den unterbrochenen Sound kann man mit

ResumeChannel ChannelId

fortsetzten. Wenn man einen Sound nicht unterbrechen sondern richtig Stopen will, sollte man

StopChannel ChannelId

benutzen.
Um einen Sound aus dem Speicher zu löschen kann man

FreeSound SoundId

verwenden (die Wiedergabe muss vorher beendet werden.)

[Bearbeiten] Musik Laden & Abspielen

Das Laden und Abspielen von Musik ist einfacher als das von normalen Sounds. Man benötigt nur einen Befehle um die Musikwiedergabe zu starten:

ChannelId=PlayMusic(Musikpfad$)

Als Dateiformate stehen folgende Endungen zur Verfügung: raw,mod,s3m,xm,it,mid,rmi,wav,mp2,mp3,ogg,wma,asf.

Da man danach auch einen Verweis auf den Wiedergabekanal (ChannelId) hat, kann mit die Befehle von oben zum Anhalten,Fortsetzen usw. verwenden.

[Bearbeiten] Audio CDs abspielen

Die Wiedergabe von Audio Cds ähnelt die Wiedergabe von "normaler" Musik:

ChannelId=PlayCdTrack(LiedNr[,Modus])

LiedNr und ChannelId sollten eigentlich klar sein. Für die Variable Modus gibt es folgende Möglichkeiten:
1 - nur diesen Track einmal abspielen (Standard)
2 - nur diesen Track ständig spielen
3 - bis CD-Ende abspielen

[Bearbeiten] Erweiterte Musikbefehle

Mit Hilfe der erweiterten Musikbefehle hat man die Möglichkeit, auf Parameter des Sounds Einfluss zu nehmen. Dabei kann man zwischen 2 Arten unterscheiden: Ändern der Parameter vor der Wiedergabe und Verändern von Parametern während der Wiedergabe.

[Bearbeiten] Vor der Wiedergabe

Alle nachfolgenden Befehle können erst verwendet werden, wenn ein Sound erfolgreich geladen wurde. (siehe oben) Als Variable für die Id wird in diesen Beispielen immer "SoundId" verwendet.

Ändern der Lautstärke:

SoundVolume SoundId,Volume#

In Volume# gibt man die Lautstärke eines Sounds an. Die möglichen Werte gehen von 0(still)-1(laut)

Ändern der Balance:

SoundPan SoundId,Balance#

In Balance# gibt man die Ausrichtung (Balance) eines Sounds an. Die möglichen Werte gehen von -1(ganz links) über 0(mitte) bis 1(ganz rechts).

Ändern der Sample-Frequenz:

SoundPitch SoundId,Frequenz

Mit Frequenz kann man die Wiedergabegeschwindikeit festlegen. Ist sie kleiner als die Orginalgeschwindigkeit klingt der Sound tiefer. Ist sie größer klingt er höher. Damit kann man einen coolen Efekt für eine gruselige Hintergrundmusik erschaffen: Man nimmt irgendeinen Sound (evt. Stimmen) und spielt den ganz langsam ab.

Einen Sound in die Dauerwiedergabe schalten:

LoopSound SoundId

Wenn man danach den Sound abspielt, wird er immer wiederholt.

[Bearbeiten] Während der Wiedergabe

Alle nachfolgenden Befehle können erst verwendet werden, wenn die Wiedergabe gestartet ist. Viele Befehle ähneln den Befehlen von oben. Jedoch sind sie universeller, da man während der Wiedergabe laufend etwas verändern kann. Als Variable für die Id des Abspielkanals (s.oben) wird in diesen Beispielen immer "ChannelId" verwendet.

Ändern der Lautstärke:

CahnnelVolume ChannelId,Volume#

In Volume# gibt man die Lautstärke eines Sounds an. Die möglichen Werte gehen von 0(still)-1(laut)

Ändern der Balance:

ChannelPan ChannelId,Balance#

In Balance# gibt man die Ausrichtung (Balance) eines Sounds an. Die möglichen Werte gehen von -1(ganz links) über 0(mitte) bis 1(ganz rechts).

Ändern der Sample-Frequenz:

ChannelPitch ChannelId,Frequenz

Mit Frequenz kann man die Wiedergabegeschwindikeit festlegen. Ist sie kleiner als die Orginalgeschwindigkeit klingt der Sound tiefer. Ist sie größer klingt er höher. Dieser Befehle funktioniert nicht bei der Wiedergabe von CDs.

Feststellen ob eine Wiedergabe (noch) läuft:

ergebniss=ChannelPlaying (ChannelId)

Ergebiss ist entweder 0 (keine Wiedergabe) oder 1 (Wiedergabe läuft). Dieser Befehle funktioniert nicht bei der Wiedergabe von CDs.

[Bearbeiten] Fortgeschrittene Grafik

[Bearbeiten] Beweglicher Hintergrund

-by andieo, verbessert durch Kurzer-

als erstes brauchen wir ein Bild, zum Beispiel: hintergrund = loadimage("C:\bild.bmp") und jetzt die Bewegung: If KeyDown(203) Then imgx = imgx+2 If KeyDown(205) Then imgx = imgx-2 jetz müssen wir das Bild noch zeichnen: drawimage hintergrund, imgx, 0 Das alles Packen wir einfach in eine Hauptschleife, der funktionierende Code sieht dann so aus:

graphics(1024,768,8,0)
hintergrund = loadimage("C:\bild.bmp")
while i = 0 ;Hauptschleife...
cls ; Bild wird vorher "geleert"
If KeyDown(45) then i = 1
if KeyDown(203) Then imgx = imgx-2 ; Nach links
If KeyDown(205) Then imgx = imgx+2 ; Nach rechts
setbuffer backbuffer()
drawimage hintergrund, imgx, 0
flip
wend 

Das wars auch schon... hätte man sich doch eigentlich fast schon von alle denken können oder???

[Bearbeiten] Drehung um die eigene Achse

[Bearbeiten] Multiplayer

[Bearbeiten] Allgemein

Netzwerkprogrammierung gehört zu den komplexeren Gebieten der Spieleentwicklung. Dies liegt hauptsächlich daran, dass, sobald man damit beginnt, die Fehlerzahl und -komplexität immens zunimmt. Bei den meisten Fehlern im Netzwerkbereich ist standardmäßiges Debuggen quasi sinnlos oder gar nicht möglich, da beim Debuggen das Programm angehalten werden muss, während die Gegenstelle noch munter weiterläuft, was leicht Timeouts usw. auslösen kann. Zusätzlich erfordert fortgeschrittenes Netzwerkprogrammieren eine neue Sichtweise. Das Spiel läuf im Mehrspielermodus nicht mehr einfach linear ab. Im Einzelspielermodus weiß man immer, dass man alle nötigen Daten und Informationen über das Spielgeschehen hat. Im Mehrspielermodus kann es passieren, dass irgendwelche Informationen fehlen, wenn sie dringend gebraucht werden.

[Bearbeiten] UDP, TCP oder DirectPlay?

Eine große Frage, die sich jedem stellt, der ein neues Netzwerkspiel schreiben will, ist, auf welcher Protokollbasis es aufgebaut sein soll. Blitzbasic unterstützt standardmäßig 3 verschiedene Arten. DirectPlay, TCP und UDP. Im Gegensatz zu TCP und UDP, die als Protokolle bezeichnet werden, ist DirectPlay eine Engine, die zu Microsofts DirectX gehört. Diese unterstützt schon von Grunde auf TCP, UDP, Seriell und IPX. Der Programmierer muss sich nicht direkt mit den Protokollen befassen, da das von DirectPlay gehandhabt wird. Dies macht DirectPlay vor allem für Anfänger interessant, da es mit Abstand der leichteste Weg ist, ein Netzwerkspiel/Applikation zu verwirklichen. Für fortgeschrittene Netzwerkprogrammierer ist es allerdings zu unflexibel und hat auch andere Nachteile. Es ist langsamer, als wenn man direkt mit TCP oder UDP arbeitet und es braucht eine Menge an Ports, was störend ist wenn man im Internet hosten will. TCP steht für "Transmission Control Protocol". Es ist ein Verbindungsorientiertes Protokoll, das heißt, dass immer eine stehende Leitung (Stream) aufgebaut wird. Dadurch wird es Router und Firewall freundlicher und man braucht außer als Host selten eine spezielle Anpassung. Außerdem wird durch die feste Verbindung auch garantiert, dass alle Pakete, die man losschickt, auch sicher und in der selben Reihenfolge ankommen. Dies hat allerdings auch seinen Preis. Die Latenzzeit von TCP ist ungefähr 3-mal so lang wie die bei UDP. UDP steht für User Datagram Protocol. Es ist im Gegensatz zu TCP ein Verbindungsloses Protokoll, was es einerseits schneller macht als TCP, aber auch mehr Probleme mit Routern und Firewalls auslöst. Zusätzlich ist bei UDP nicht garantiert, dass Pakete auch wirklich ankommen.

Auch wenn TCP mit nur 8 und UDP mit 9 Befehlen nach wenig Stoff aussieht, ist es trotzdem für Anfänger empfehlenswert mit DirectPlay anzufangen. Die 8/9 Befehle sind zwar leicht auswendig gelernt aber die Anwendung jener ist vergleichsweise viel komplizierter, als die von DirectPlay. Wenn man DirectPlay nicht verwenden will, muss man sich entscheiden auf welchem Protokoll man sein Spiel aufbauen will. TCP ist aber generell UDP vorzuziehen da UDP außer Geschwindigkeit keine Vorteile sondern nur Nachteile bringt. Aber in manchen Spielegenren (Egoshootern und andere Actionspiele) wird diese Geschwindigkeit benötigt. Dann sollten beide Protokolle parallel benutzt werden. Wenn es aber möglich ist nur TCP zu verwenden, dann sollte dies aber auch getan werden.

[Bearbeiten] DirectPlay

[Bearbeiten] UDP

Wie oben schon erwähnt wurde, ist UDP(User Datagram Protocol) am besten für Schnelle Genres geeignet. Da wir uns noch in der 2.ten Dimension befinden, dient uns als Beispielprogramm das eben gecodete Pong Spiel. Aber erstmal die Grundbefehle: Die wichtigsten UDP Befehle sind natürlich folgende:

CreateUdpStream()
CloseUDPStream()
RecvUDPMsg()
SendUDPMsg()
;Sämtliche Read & Write Befehle 


stream = CreateUDPStream( [portnum%] )

Mit CreateUDPStream erstellt man einen UDP Stream über den optionalen, als Parameter, angegebenen Port. Wenn man keinen Port angibt, sucht sich BlitzBasic automatisch einen freien Port, den man mit dem Befehl UdpStreamPort() rausfinden kann. Der Rückgabewert stream liefert die Indendität des erstellten Streams in einem Integer Wert zurück. Falls er 0 zurückliefert, konnte die Netzwerkverbindung nicht erstellt werden.

CloseUDPStream( stream )

Mit dem Befehl CloseUDPStream schliesst man einen UDP Stream , der zuvor mit CreateUDPStream geöffnet wurde. Der Parameter stream ist die Indendität des Streams.

RecvUDPMsg()

[Bearbeiten] TCP

[Bearbeiten] Allgemein

Das Transmission Control Protocol (TCP) (zu dt. Übertragungssteuerungsprotokoll) ist vor allem für rundenbasierte (also nicht actionlastige) Spiele geeignet, da es nicht ganz so schnell wie das UDP Protokoll ist. Der Vorteil in TCP liegt daran das alle Daten auch an kommen, und nicht wie bei UDP verloren gehen können. Solte ein Datenpacket nicht den Clienten erreichen, wird dieses automatisch nocheinmal gesendet.

Alle TCP-Befehle in der Übersicht:

CreateTCPServer( [port] ) Dieser Befehl wird benötigt, um einen Server zu erstellen, zu den Verbindungen hergestellt werden können. Der Rückgabewert ist ein Integer, der benötigt wird, um fest zu stellen, ob eine Verbindung hergestellt wurde. Wird kein Port angegeben, sucht sie BB den 1. freinen Port selbst. Ich empfehle euch einen port der über 5000 liegt, damit ihr keine anderen Prozesse stört! Wird eine 0 zurück geliefert, konte der Server nicht erstellt werden, das kann folgende gründe haben: -der Port ist blockiert -eine Firewall blockiert den Port, oder das Programm

[Bearbeiten] CloseTCPServer( server )

Dieser Befehl schließt beendet einen Server wieder. Es kommt zu einem fehler, solltet ihr einen falschen wert übergeben. Achtung: der Befehl setzt die Variable nicht wieder auf 0, das müsst ihr selbst machen. Ich empfehle diese Funktion zu verwenden: FunctionNewCloseTCPServer(server)Ifserver = 0thenReturn(server)CloseTCPServer(server)server = 0Return(server)EndFunction Diese Funktin prüft ob der Server bereits geschlossen wurde, um einen Fehler zu vermeiden. Außdem setzt es die Server Variable wieder auf 0 Ihr solltet die Funktion wie folgt verwenden: Localtcpserver = CreateTCPServer(8080)tcpserver = NewCloseTCPSServer(tcpserver)FunctionNewCloseTCPServer(server)Ifserver = 0thenReturn(server)CloseTCPServer(server)server = 0Return(server)EndFunction

--Pummelie 13:38, 11. Jan. 2010 (CET)

[Bearbeiten] Multiplayermodus für unser Pong-Spiel

[Bearbeiten] Wir programmieren einen SpaceShooter

[Bearbeiten] Die Grundstruktur

[Bearbeiten] Der Player

[Bearbeiten] Gegner kommen ins Spiel

Gegner definieren Für Gegner sollte man am besten Types verwenden da man bei einem Type sehr einfach viele Gegner erstellen kann.

TYPE enemy
   FIELD xpos
   FIELD ypos 
   FIELD health 
   FIELD name 
END TYPE

Mit diesem Code wird ein neuer Type erstellt mit dem namen Enemy und den ganzen Daten die ein Gegner haben sollte.

Natürlich könnte man auch einen Array anlegen und darin die Daten lagern aber sowas geht vielleicht bei einem Pac-Man spiel wo es immer 4 gegner gibt. Bei unserem Shooter jedoch sollen es viele sein und mit array ist das schwer zu machen deswegen werde ich auch nicht weiter auf diesen Lösungsweg weiter eingehen.

Gegner anzeigen und dann wieder löschen

Natürlich muss der Gegner sich bewegen, und dazu immer als erst gezeichnet,dann wieder gelöscht und dann wieder gezeichnet usw. werden.

Dazu sollte man sich eine extra Funktion Schreiben in der alle Gegner angezeigt werden. Das Löschen macht man am Besten in einer For: Next Schleife in der auch Sachen wie die Kollision überprüft werden.

Eine recht simple Funktion zum Anzeigen aller Gegner sieht wie folgt aus:

FUNCTION enemy_draw
FOR enemy.enemy = EACH enemy
 DRAWIMAGE gegnergrafik,enemy\xpos,enemy\ypos 
NEXT
END FUNCTION

[Bearbeiten] Schüsse

Schüsse Definieren

Zuerst müssen wir einen neuen Type erstellen wo wir die Daten unserer Schüsse Speichern. Hier sollte man auf keinen Fall einen Array verwenden da man in den meisten Shootern immer ziemlich viele Schüsse hat und man alles mit types besser machen kann

TYPE schuss
 FIELD xpos
 FIELD ypos
END TYPE

Dieser Type reicht eigentlich schon aus. Natürlich kann man später noch andere Sachen einbauen wie z.B. ein neues feld das ID heißt und in dem Gespeichert wird was für ein Schuss das ist.

[Bearbeiten] Videos

[Bearbeiten] Videos öffnen

Um ein Video abspielen zu können, muss es erst einmal geladen werden. Dies geht mit OpenMovie()

 Video = OpenMovie("credits.avi")

[Bearbeiten] Videos abspielen

Das Video muss bereits geöffnet sein.

DRAWMOVIE Video, X, Y [,Breite] [,Höhe]

[Bearbeiten] Größe eines Videos bestimmen

[Bearbeiten] Speicherbänke

[Bearbeiten] Speicherbänke erstellen

Eine Speicherbank kann mit CreateBank erstellt werden.

Bank=CreateBank(Bytes)

[Bearbeiten] Speicherbänke auslesen

Wert =PeekByte  (Bank, Pos) 
Wert =PeekShort (Bank, Pos)
Wert =PeekInt   (Bank, Pos)
Wert#=PeekFloat#(Bank, Pos)

[Bearbeiten] Speicherbänke beschreiben

PokeByte  Bank,Pos,Wert
PokeShort Bank,Pos,Wert
PokeInt   Bank,Pos,Wert
PokeFloat Bank,Pos,Wert#

[Bearbeiten] Speicherbänke kopieren

[Bearbeiten] Größe einer Speicherbank verändern

RESIZEBANK Bank, Byte

[Bearbeiten] Größe einer Speicherbank ermitteln

Byte=BANKSIZE (Bank)

[Bearbeiten] Speicherbänke löschen

FreeBank Bank

[Bearbeiten] DLLs

Dlls kann man mit

CallDll

aufrufen.

[Bearbeiten] Einführung in BB3D

[Bearbeiten] Was ändert sich in BB3D

[Bearbeiten] Graphics3d, RenderWorld

Bei BlitzBasic stand am Anfang immer Graphics und in einer Mainloop immer Cls und Flip. Bei Blitz3d kommen noch UpdateWorld und RenderWorld hinzu, das Cls kann weggelassen werden. Der 3d-Modus wird durch Graphics3d initialisiert. Seine anderen Parameter sind wie schon von Graphics bekannt.

 Graphics3D 800,600,16,2
 SetBuffer BackBuffer()
 Repeat
  
  ;Animationen updaten
  UpdateWorld
  
  ;Szene rendern
  RenderWorld
  
  Flip
 Until KeyHit(1)
 End

Updateworld aktuallisiert Kollisionen und Animationen, die einem Blitz vollständig berechnet, Renderworld zeichnet die gesamte 3dSzene. Man zeichnet nicht mehr manuell einzelne Bilder, sondern RenderWorld zeichnet alle vorher definierten Entities.

[Bearbeiten] Entities?!

Eine der größten Neuheiten von Blitz3d gegenüber BlitzBasic ist das Entitysystem. Entity ist ein englisches Wort für Ding, Dasein, Gebilde, Einheit, Objekt. Die gesamte 3dWelt besteht aus Entities. Egal ob Boden, Figur oder Sonne, jedes Objekt ist eine Entity. Entites haben alle dieselben Grundeigenschaften, wie Position, Drehung, Größe und Bezugssystem.

Anders als in BlitzBasic, wo man Bilder immer wieder von neuem Zeichnen muss, bleiben einmal erstellte 3d-Objekte solange bestehen, bis man sie löscht. Das ist nach BlitzBasic vielleicht etwas gewöhnungsbedürftig, aber schlussendlich doch recht logisch: Man baut einmal eine Welt und manipuliert sie dann, anstatt sie jedes Frame von vorne aufzubauen.

[Bearbeiten] Kamera

Wie auch im Film braucht man für eine ordentliche Szene nicht nur eine Szene, sondern vor allen Dingen eine Kamera. Ohne Kamera kein Film. Ohne Kamera auch kein PC-Spiel. Kameras sind Entities, die angeben, wo und wie man auf die Welt schaut. Erstellt werden sie mit CreateCamera() vor der Hauptschleife. Der Befehl liefert das Handle der Kamera zurück. Das ist eine Zahl, mit der man wieder auf das Entity zugreifen kann. Man behandelt die Kamera dann wie alle anderen Entities. Zur Entitybehandlung kommen wir gleich.

 Graphics3d 800,600,16,2
 SetBuffer Backbuffer()
 camera = CreateCamera()
 Repeat
  Cls 
  ;Aktuallisieren
  UpdateWorld
  Renderworld
  Flip
 Until KeyHit(1)
 End

Nichts zu sehen? Tja, was auch?

[Bearbeiten] Würfel, Kugeln, Zylinder, Kegel

In BlitzBasic kann man zum Testen von 2d-Dingen Grundformen mit Oval und Rect Grundformen zeichnen (in Blitz3D kann man das natürlich auch, man muss die Befehle aber nach RenderWorld und vor Flip setzen). Auch in Blitz3d gibt es solche Grundformen, beziehungsweise natürlich -körper: die aus Mathe und vielen 3d-Programmen bekannten Grundfiguren Würfel, Kugeln, Zylinder und Kegel. Man nennt sie Primitves. Und die brauchen wir jetzt, damit wir etwas zum Anschauen und Testen haben.

Würfel werden mit CreateCube() erstellt. Kugeln mit CreateSphere(), Zylinder mit CreateCylinder(), Kegel mit CreateCone().

[Bearbeiten] Mehr von den Entitäten

Jippy, endlich kann es losgehen! Zeit für unsere erste Szene. Es werden ein paar Primitives erstellt und vor der Kamera angeordnet.

Graphics3D 800,600,32,2
SetBuffer BackBuffer()

camera = CreateCamera()

cube = CreateCube()
PositionEntity cube,-2,0,5

sphere = CreateSphere()
PositionEntity sphere,0,2,5

cylinder = CreateCylinder()
PositionEntity cylinder,1,5,0,3

cone = CreateCone()
PositionEntity cone,0,-2,5

Repeat
 Cls
 RenderWorld
 Flip
Until KeyHit(1)
End

Zum positionieren der Entities nutzt man PositionEntity. Der erste Parameter gibt das zu positionierende Entity an, die drei Zahlen danach geben die X-, Y- und Z-Koordinaten an.

[Bearbeiten] X,Y und Z

Eixos.jpg

Koordinaten werden in 3d nicht ganz so angegeben wie in 2d. Der auffälligste Unterschied ist wohl, dass es nicht mehr nur X und Y, sondern auch noch einen dritten Bestandteil, Z, gibt. Z steht für die Tiefe.

Der Urprung der Welt liegt an 0,0,0. Objekte werden normalerweise am Ursprung erstellt und zeigen entlang der Z-Achse. So auch die Kamera. 0,0,0 ist bei einer unbewegten Kamera immer genau der Mittelpunkt des Bildschirms auf der Kameralinse.

Schauen wir uns einmal das letzte Beispiel an. Der Würfel/Cube wird mit PositionEntity an -2,0,5 positioniert. -2 als X-Wert bedeutet, dass der Würfel zwei Einheiten links von der Bildschirmmitte liegt. Ist er positiv, so liegt das Objekt weiter rechts. Der Z-Wert von 5 verschiebt das Objekt nach hinten. Wenn man ihn weiter erhöht, wird das Objekt nach hinten verschoben, also kleiner und bewegt sich auf den Fluchtpunkt zu.

Mit dem Y-Wert verhält es sich genau wie in Mathe: Je größer der Wert, desto weiter oben der Punkt. Also nicht mehr wie im 2d-Modus umgekehrt.

[Bearbeiten] Licht

Natürlich bietet Blitz3D weit mehr als nur den Umgang mit Entities. Man kann z.B. eine Lichtquelle erstellen und diese frei im Raum positionieren. Dies erreicht man mit dem Befehl CreateLight(). CreateLight() erzeugt eine Lichtquelle, die fast genauso wie alle anderen Entities behandelt wird: Man kann sie genau wie alle anderen Entities drehen, verschieben und positionieren.

light = CreateLight(1)

[Bearbeiten] Texturen

[Bearbeiten] LoadMesh

[Bearbeiten] Wir programmieren einen 3D SpaceShooter

[Bearbeiten] Allgemeines

Einen Space Shooter zu coden, ist an sich nicht so schwer. Die eigenen Vorstellungen sind oft das größere Hindernis. Wie bei allen Spielen werden auch hier Dinge benötigt, die aus verschiedenen Disziplinen stammen. Code, Musik, Grafik, Modelle, Geschichte. Da selten eine Person alle Disziplinen beherrscht, sind die zu erwartenden Ergebnisse von vornerein erheblich tiefer anzusetzen. Ausnahmen sind natürlich Teams. Aber davon gibt es nicht viele, die es über einen längeren Zeitraum zusammen schaffen. Ich gehe hier davon aus, dass der Leser schon ein wenig Erfahrung mit Blitz3D hat und alleine arbeitet. Es ist außerdem nützlich, über ein aktuelles Original zu verfügen.

Eine der wichtigsten Dinge, die man beim Coden berücksichtigen muss, ist die Disziplin. Bestimmte Dinge muss man sich einfach von vorne herein angewöhnen.

1. Erstellung einer Variablenliste mit Kommentaren zur Funktion einer jeden Variable.
2. Erstellung einer Liste, die Erklärungen zu Funktionen enthält.
3. Ein Konzept für das Spiel (obwohl ein solches vor allem von Hobbyentwicklern selten erstellt wird).

[Bearbeiten] Was soll es denn werden?

Diese Frage muss man sich in jedem Fall stellen. Ansonsten wird es aus dem Projekt ganz schnell ein unendlich andauerndes Unterfangen.

Wir brauchen:
- einen Ort in Form eines Sonnensystems
- eine Station, in der man Parken kann
- Gegner, die einen angreifen (z.B. Piraten)
- Waffen und Raketen
- Tolle Schiffe
- variablen Schwierigkeitsgrad
- eine Kollisionserkennung
- fetzigen Sound und GFX
- ein Startintro
- eine Cockpit-Ansicht
- Radar und sonstige Kontrollen

Sieht ganz nach einem ELITE-Klon aus... Aber eigentlich sind fast alle 3D-Weltraum-Spiele daran angelehnt.

Na dann mal los...

[Bearbeiten] Programme, die wir brauchen

Da ich davon ausgehe, dass der Leser alleine codet, sind hier vor allem Programme gefragt die man schnell und leicht bekommen und benutzen kann.

PaintShopPro 4       zum Zeichnen von Texturen und Sprites
DOGA L2              zum Erzeugen von 3D Objekten nach dem Lego Prinzip
Blitz3D              um alles als Code zusammenzubinden
Edit                 zum erstellen von Notizen , !!! ganz wichtig !!!

[Bearbeiten] Die Grundstruktur

Grundsätzlich sind diese bei allen Spielen gleich.

1. Initialisierung
2. Bildschirm öffnen
3. 3D Umgebung einrichten
4. Hauptschleife
5. Funktionen
6. Datas

Eine mögliche Grundstruktur könnte so aussehen:

;(1)-----------------------------------------------------------------
sx=600				; Screen x
sy=400				; Screen y
;(2)-----------------------------------------------------------------
Graphics3D sx,sy,32,2		; Bildschirm im Fenstermodus öffnen
SetBuffer BackBuffer()		; Das Doublebuffering aktivieren
;(3)-----------------------------------------------------------------
camp=CreatePivot()		; Ein Drehpunkt für die Kamera
cam=CreateCamera(camp)		; Eine Kamera einrichten
MoveEntity cam,0,0,-5		; Die Kamera vom Nullpunkt wegbewegen 
light=CreateLight(1)		; Eine Lichtquelle erzeugen
obj=CreateCube()		; Ein Objekt erzeugen
;(4)-----------------------------------------------------------------
Repeat				; die Hauptschleife

 If KeyHit(1) Then en=1	; auf ESC-Taste reagieren = Exit
	
 TurnEntity camp,.5,0,0	; Den Drehpunkt der Kamera drehen 	
 RenderWorld() 		; Szene zeichen
 Flip  			; Bildschirm wechseln mit WaitSysnc
	
Until en=1			; Exit wenn Taste ESC gedrückt wurde

End				; Programm Ende
;(5)----------------------------------------------------------------
;(6)----------------------------------------------------------------

[Bearbeiten] Der Spieler

[Bearbeiten] Gegner kommen ins Spiel

[Bearbeiten] Schüsse

So... Wer noch keine Ahnung von Types hat, sollte sich erst einmal ein kleines Tutorial dazu anschauen, oder sich einfach hieran versuchen (Grundwissen wird hier vorrausgesetzt). Schüsse sollten immer mit Types (oder zur Not auch Dims) realisiert werden und ungefähr so aussehen:

Type Schuss        ; unser Type heißt "Schuss", so kann man auf ihn zugreifen
 Field Photon      ; Spaceshooter-Schiffe schießen immer mit Photonen ;-)
 Field objektdauer ; wie lange unsere Schüsse im Spiel sein sollen
End Type           ; "beendet" den Type

... Bravo! Unser Type ist im Spiel. Er bringt nur noch nicht viel! Wie wäre es denn, wenn man durch Drücken der linken Maustaste den Schuss abfeuern könnte? Dafür setzen wir Folgendes in die Hauptschleife:

If MouseHit(1) Then FeuerSchuss()

... FeuerSchuss() ist eine Funktion (siehe dazu andere Tutorials), die wir jetzt benennen müssen (natürlich außerhalb der Schleife ;-) ):

Function FeuerSchuss()      ;benennt unsere Funktion

 s.Schuss = New Schuss                  ;ein neuer Type wird mit allem drum und dran erstellt
 s\photon = CreateSphere()              ;s\ zeigt, im welchem Type wir uns befinden

 EntityParent Photon,0          ;kein Parameter
 PositionEntity s\photon,EntityX(Spieler),EntityY(Spieler),EntityZ(Spieler)+3 ;sollte klar sein
 s\objektdauer = 100
End Function 


Super! Unser Photon existiert nun! Nun soll es etwas machen. Funktionen helfen uns dabei:

In der Schleife:

For s.Schuss = Each Schuss       ; für jeden Schuss mach das und das...
 UpdateSchuss(s)
Next

Außerhalb der Schleife:

Function UpdateSchuss(s.Schuss)
 MoveEntity p\obj,0,0,100 
 RotateSprite p\obj,Rnd(310)
 If CountCollisions(p\obj) Then 
  If EntityCollided(p\obj, TYPE_TERRAIN) Then
  EmitSound Explo, p\obj
  einschlagX# = EntityX(p\obj)
  einschlagY# = EntityY(p\obj)
  einschlagZ# = EntityZ(p\obj)
  TFormPoint(einschlagX#, EinschlagY#, EinschlagZ#,0,terrain)
  hoehe = TerrainHeight( terrain, TFormedY(),TFormedZ() ) 
  If hoehe>0
   hoehe=hoehe-2:If hoehe<0 Then hi=0
   ModifyTerrain terrain,TFormedY(),TFormedZ(),hoehe,True
  EndIf
 Explosion ( p )

 FreeEntity p\obj
 Delete p
 Return 
  End If 
 End If 
  
End Function

[Bearbeiten] Einführung in BMax

[Bearbeiten] Was ist OOP?

OOP steht für Objekt Orientierte Programmierung.

Objekte könnt ihr euch als Variablen vorstellen, nur dass ihr viel mehr mit ihnen anfangen könnt. In BMax sind sie fast genau wie Types, nur mit dem Unterschied, dass ihr Methoden definieren könnt. Methoden sind nichts weiter als Funktionen Innerhalb dieses Types. Diese gelten auch jeweils für eine Instanz dieses Types. Diese speziellen Types nennt man Klassen, Instanzen dieser nennt man Objekte.

[Bearbeiten] Zusatzprogramme

Sicher, ein Spiel funktioniert auch wenn man eine Meute brauner einfarbiger Ovale über den Schirm wetzen lässt und an jeden "Keks" dranschreibt. Aber zufriedenstellend ist das für die meisten Programmierer nicht. Deshalb hier eine Zusammenstellung praktischer Zusatzprogramme:

[Bearbeiten] 2D-Programme-Bildbearbeitungsprogramme

Mit ihnen lassen sich Bilder und Texturen erstellen:

Paint:
Das Einfachste unter den Programmen. Standardmäßig bei Windows dabei. Eigentlich nur geeignet zum sogenanten "pixeln".

Photo Impact:
Umfangreiches Bildbearbeitungsprogramm. Kann zusätzlich Pfadobjekte mit 3D-Effekten versehen und Webseiten erstellen. Ältere Versionen sind sehr günstig zu erwerben, teilweise unter 10 Euro.

Paint.NET:
Die moderne Version von Paint. Die Software ist schnell geladen und bietet neben Ebenen genug Funktion professioneller zu zeichnen. Kostenlos unter: http://www.getpaint.net/

GIMP:
Ein sehr mächtiges Malprogramm, was schon sehr nah an kommerzielle Programme wie Photoshop heranreicht. Die Windowsversion findet man unter: http://gimp.org/windows/

[Bearbeiten] 3D-Programme

Wings 3D
Kosteloser und einfach zu benutzender 3D-Modeller. Gerade für Anfänger ist dieses Programm ein Segen, da es auf den ersten Blick nicht so überladen ist und trotzdem viele wichtige Funktionen bereit stellt, so zum Beispiel auch einen UV-Editor. Die Engine basiert auf dem 3D-Modeller Nendo und kann sich daher gur sehen lassen, allerding hat Wings 3D schwächen im Bereich Rendering und Animation. Wings 3D ist besonders gut für Low-Poly Modelle geeignet. Herunter zu laden auf Herstellerseite.


Milkshape
3D-Modeller, der auf das Wesentliche reduziert ist. Glänzt vor allem durch haufenweise Import- und Exportformate, speichert auch in ".b3d". Die Sofware muss von der Herstellerseite heruntergeladen werden, nach 30 Tagen kostet die weitere Nutzung der Speicherfunktion von Milkshape 25 Euro.


Blender
Recht leistungsstarkes, kostenloses (Open Source) 3D Programm. Es werden viele gängigeN 3D-Formate und Betriebssysteme (u.a. Windows9x/ME/2000/XP/Vista/Mobile, Linux, MacOS X) unterstützt. Die Software kann u.a. auf der Herstellerseite bezogen werden. Es ist vor allem für Anfänger empfehlenswert, da es viele kostenlose Anleitungen gibt.


Cinema 4D
Auch als "6CE" Version erhältlich, welche aber nur sehr selten auf Heft-DVDs/CDs erscheint. Kostet allerdings dann auch nicht mehr als 10 Euro. Ansonsten ist es in der oberen Preisklasse angesiedelt, bietet aber auch dementsprechend viel Professionalität.


3D Studio Max
Die Königsklasse der 3D-Editoren. Durch Plugins lässt sich so ziemlich alles machen, was man sich denken kann. Allerdings ist 3D Max auch das wohl teuerste Programm (Einzellizenz ca. 7000 €) Es spricht den Profi an und benötigt auf Grund des Funktionsumfangs monatelange Einarbeitung und Schulung.

[Bearbeiten] sonstige-Programme

UPX:
Sehr nützlich zur Komprimierung fertig kompilierter EXE-Dateien.

Ressource-Hacker:
Programm zum Ändern der Ressourcen ausführbarer Dateien, u.a. die Icons von EXE-Dateien. Ist entgegen dem Namen völlig legal im Einsatz.

Persönliche Werkzeuge