| |||
Dieses Tutorium soll grundlegende Kenntnisse in der Verwendung von Grafik in Blitz Basic vermitteln. Es werden die gängigsten Arten von Grafikanwendung beschrieben und einige lauffähige Beispiele präsentiert. Das wichtigste und grundlegendste bevor auch nur der erste Punkt auf dem Bildschirm zu sehen sein wir, ist den Grafikmodus zu initiieren. Das geht in Blitz Basic denkbar einfach: Graphics 640, 480, 16, 1 setzt den Grafikmodus auf eine Länge von 640 Pixel und eine Breite von 480 Pixel. Die Farbtiefe ist 16 Bit (auch 32 Bit sind ohne weiteres möglich) und der Modus ist 1. Der Modus wird in allen Beispielen 1 sein, da die anderen Modi zur Spieleprogrammierung nicht geeignet sind. Der Modus 1 ist Vollbild, denn eigentlich alle Spiele benutzen sollen. In allen Beispielen wird der eben definierte Bildschirmmodus verwendet. Mit dem Befehl EndGraphics wird der Grafikmodus beendet. Dieser muss nicht am Ende eines Programmes ausgeführt werden, da DirectX das selbständig erledigt. Verwendet man dieses Befehl dennoch, sieht der Spieler kurzfristig ein Windows Fenster aufflackern. Ich bin kein Fan von so etwas... Nachdem der Grafikmodus nun gesetzt ist, können wir zu den einfachen Grafikbefehlen kommen. Für die Standartbefehle ist eines wichtig zu wissen: Egal welche Malhandlung auf dem Bildschirm ausgeführt wird, sie wird immer in der aktuellen Malfarbe gesetzt (das gilt auch für alle Text Befehle)! Color rot, grün, blau legt diese Malfarbe fest. Die Werte rot, grün und blau geben dabei die Intensität der drei Mischfarben an. Aus diesen drei Farben lässt sich alles wie in einem Lichtspektrum zusammenmischen. Der maximale Wert den man von einer Farbe angeben kann ist 255, das Minimum ist 0. Möchte man ein knalliges Rot haben so sollte man Color 255, 0, 0 aufrufen. Für ein dunkleres Rot Color 128, 0, 0 oder für schwarz Color 0, 0, 0. Das sieht dann so aus: Graphics 640, 480, 16, 1 color 255, 0, 0 print "sehr rot" color 128, 0, 0 print "nicht ganz so rot" color 0, 0, 0 print "schwarz" delay 2500 Der Befehl delay wartet 2,5 Sekunden (2500 Millisekunden), damit auch zu sehen ist, was wir gerade auf den Bildschirm geschrieben haben. Wenn sie sich jetzt fragen warum die Schrift "schwarz" nicht zu sehen ist sollten sie noch einmal darüber nachdenken - schließlich ist der Hintergrund ja schwarz... Ach ja, hatte ich nicht im vorherigen Kapitel etwas davon gesagt das man Print vermeiden sollte? Na gut, dann kommen wir jetzt zu dem Befehl den ich Print vorziehe: Text. Das Beispiel von eben, etwas abgewandelt mit dem Text Befehl, sieht so aus: Graphics 640, 480, 16, 1 color 255, 0, 0 text 100, 100, "sehr rot" color 128, 0, 0 text 100, 120, "nicht ganz so rot" color 0, 0, 0 text 100, 140, "schwarz" delay 2500 Der Vorteil von Text ist, dass man den Text frei auf dem Bildschirm positionieren kann. Man ist also nicht daran gebunden ständig der Cursor durch die Gegend zu schieben, sondern gibt einfach die obere linke Ecke an, von der an der Text geschrieben werden soll und los geht´s. Da inzwischen alle das schreiben von Texten auf dem Bildschirm satt sein sollten, kommen wir jetzt endlich mal zu etwas Grafik. Und zwar zu Linien, Vierecken und Kreisen. An das Ende der meisten Programme setzt ich ein delay 2500 damit wir die Ergebnisse unserer Arbeit auch auf dem Bildschirm bewundern können. Doch bevor es weiter geht noch zu einer grundlegenden Information über den Bildschirm. Ein Bildschirm besteht aus vielen Tausenden von Pixeln (einzelne Punkte). Jeder Punkt kann nur eine Farbe gleichzeitig haben. Diese Punkte können in Blitz Basic wie in den meisten anderen Programmiersprachen über ihre X und Y Position angesteuert werden. So wie in einem mathematischen Koordinatensystem. Allerdings liegt der Punkt (0; 0) oben links und nicht unten links. Je höher die X Koordinate ist, des so weiter liegt der Punkt rechts, je höher die Y Koordinate ist, desto weiter liegt der Punkt unten. Da der Bildschirm in unseren Beispielen 640 x 480 Punkt groß ist, ist die äußerste X Position also 639 (die Punkte gehen von 0 – 639, nicht von 1 bis 640). Gut, nun können wir ja zu unserer ersten Grafik kommen. Einem einfachen Strich. Graphics 640, 480, 16, 1 color 255, 0, 0 line 100, 100, 200, 200 delay 2500 Der Befehl Line zieht von der X, Y Position die durch die ersten beiden Zahlen bestimmt ist zu der X, Y Position die durch die letzten beiden Zahlen bestimmt ist eine Linie. Einfach, oder? Gut, dann gleich weiter zu den geometrischen Formen: Graphics 640, 480, 16, 1 color 255, 0, 0 Rect 100, 100, 100, 100, 0 Rect 300, 100, 100, 100, 1 Oval 100, 300, 100, 100, 0 Oval 300, 300, 100, 100, 1 delay 2500 Dieses Beispiel malt zwei Vierecke und zwei Kreise. Rect malt ein Rechteck von der oberen linken Position, die durch die ersten beiden Zahlen bestimmt wird. Danach wird die Länge nach rechts und dann die Höhe nach unten mit den nächsten beiden Parametern bestimmt. Bei einer ovalen Form funktioniert es ähnlich. Man stelle sich ein Rechteck vor, genau wie das durch Rect gezeichnete. Oval zeichnet nun eine Ellipse, die genau in dieses Rechteck passen würde, und jede der Kanten genau in Ihrem Mittelpunkt berührt. Wer das nicht versteht kann sich ja ein Beispiel schreiben in dem er ein Rechteck und einen Kreis von den gleichen Positionen mit der gleichen Ausdehnung malt. Es empfiehlt sich sowieso etwas damit zu experimentieren damit man es im „Ernstfall“ später gleich richtig macht. Die letzte Zahl schließlich gibt an ob die gezeichnete Form ausgemalt sein soll oder nicht. 0 bedeutet sie ist nicht ausgefüllt, 1 das sie ausgefüllt ist. Die einfachsten Grafikbefehle überhaupt habe ich noch nicht erwähnt und tue das auch nur am Rande: Plot x, y setzt einfach einen Punkt auf die X, Y Position und GetColor x, y wandelt die aktuelle Malfarbe in die Farbe die sich am Punkt x, y befindet um. Nun können wir endlich zu den etwas komplexeren Grafiken kommen, zu gemalten Bitmaps! Ein Bitmap ist eine im Speicher festgelegte Folge von Punkten in verschiedenen Farben die ein ganzes Bild ergeben (können...). Außerdem werden wir in diesem Teil des Tutoriums etwas sehr wichtiges über viele Blitz Basic Routinen allgemein lernen. Deshalb sollte dieser Abschnitt auf keinen Fall übersprungen werden! Fangen wir doch gleich mit dem wichtigen Teil an. Wann immer an Direct X ein Befehl übergeben wird, der eine Grafik lädt, eine Datei öffnet, Musik spielt oder sonst etwas tut, führt Direct X diesen Befehl intern durch und der Programmierer hat damit nur noch sehr wenig zu tun. Dennoch muss es eine Schnittstelle zwischen dem Programmierer und Direct X geben, damit dieser die geladenen Grafiken auch benutzen kann, bzw. die Musik abspielen kann usw. Deshalb gibt Direct X bei jedem Aufruf eines solchen Befehls einen Wert (Typ Integer) zurück, ein sogenanntes „Handle“. Diesen Wert muss der Programmier in einer Integervariable abspeichern. Diese Variable kann er dann später verwenden um das geladene Objekt anzusprechen (das heißt zu setzen, zu löschen, etc.). Man muss diese Variable im Programm behalten, solange man das Objekt verwenden will. Wird die Variable aus irgend einem Grund überschrieben oder gelöscht, kann auf das Objekt nicht mehr zugegriffen werden. Und das ist schlecht. Dies werde ich jetzt am Beispiel von Grafiken demonstrieren. Wichtig! Wer dieses Prinzip nicht versteht kann gleich nach Hause gehen, also aufpassen!!! (So schwer ist es aber gar nicht...) Nun ein einfaches Beispiel mit dem ein beliebiges Bitmap auf den Bildschirm gesetzt wird: Global Grafik Graphics 640, 480, 16, 1 Grafik = LoadImage ("Hero.bmp") DrawBlock Grafik, 100, 100 DrawBlock Grafik, 200, 200 Delay 2500 Was genau macht dieses Programm? Nun, es wird zuerst eine Grafik geladen, die sich als Beispiel in dem Tutoriums-Verzeichnis 2 befindet (Hero.bmp). Direct X lädt die Grafik und gibt an die Variable mit dem Namen Grafik einen Wert, der für das weitere Arbeiten mit der Grafik verwendet wird. Wer neugierig ist kann sich diesen Wert ruhig einmal ausgeben lassen. Ist der Wert 0, so gab es beim dem Laden der Grafik einen Fehler. Das Programm wird spätestens bei dem ersten verwenden der Grafik abstürzen. Um bei dem Fehlen von Grafiken nicht mitten im Spiel zu crashen, wird häufig nach jedem Laden einer Grafik überprüft ob das laden erfolgreich war. Das tut man, indem man prüft ob der von Direct X zurückgegebene Wert nicht gleich 0 ist, etwa so: If Grafik = 0 then Print "Grafikfehler": Delay 2000: End In unserem Beispiel machen wir das nicht, aber wer möchte kann es natürlich testen, auch indem er zum Beispiel mal den Namen der Grafik ändert und so einen Fehler provoziert. Hat aber alles geklappt malt der Befehl DrawBlock die Grafik auf den Bildschirm. Wer das bis jetzt verstanden hat, beherrscht bereits die wichtigsten Programmiererfertigkeiten, die für das programmieren eines Spieles erforderlich sind! Doch gleich geht es weiter. Als nächstes bringen wir Bewegung in das Ganze. Und zwar bewegen wir zuerst einmal die Grafik von links nach rechts: Global Grafik Graphics 640, 480, 16, 1 Grafik = LoadImage ("Hero.bmp") for x = 1 to 400 DrawBlock Grafik, x, 100 next Delay 2500 Gut oder? Was, nichts gesehen? Tja, das zeigt welche Leistung eigentlich in Blitz Basic steckt. Die Grafik wurde gerade von links nach rechts bewegt und dabei 400 mal gesetzt und dennoch ging es so schnell, das es gar nicht zu sehen war. Falls ihr eine sehr alte Grafikkarte habt, könnte es sein das ihr etwas aufflimmern saht. Doch das ist ja nicht das was wir wollen. Deshalb werde ich gleich ein wenig was zum sogenannten „Page Flipping“ erzählen. Diese Technik wird aus zwei Gründen verwendet: Erstens, damit Programme wie das von eben nicht zu schnell laufen, und zweitens, damit man nicht irgend ein komisches Geflimmere auf dem Bildschirm hat. Dazu einmal etwas zu Bildschirmen allgemein: Ein normaler Bildschirm baut das sichtbare Bild in einem Bruchteil einer Sekunde auf, und zwar von oben nach unten, Linie für Linie. Grafik setzen kann man jedoch immer, auch wenn der Bildschirm gerade mitten dabei ist ein Bild aufzubauen. Wenn man nun ein Bitmap setzt, das den ganzen Bildschirm einnimmt, und der Bildschirm das Bild bereits bis zur Hälfte aufgebaut hat, so kann der Bildschirm nur noch die untere Hälfte des gesetzten Bitmaps aufbauen. Der Rest der Bildes wird dann erst im nächsten Bildschirmaufbau („frame“) gesetzt. Das Ergebnis: Der obere Teil des Bildes flimmert zuerst ! Das wollen wir jedoch genau so wenig wie das man die ersten 400 Bilder einer Animation gar nicht zu Gesicht bekommt da sie schon längst wieder verschwunden sind bevor der Bildschirm aufgebaut ist. Schließlich wird nur ein vollständig aufgebautes Bild gesehen und meistens nicht einmal alle Bilder die aufgebaut wurden (ein moderner Bildschirm schafft zwischen 85-100 Bilder pro Sekunde, das menschliche Auge nimmt jedoch weniger als ein Drittel so viele war). Also, was tun? Die Antwort ist einfach, man wartet mit dem Aufbau des Bildes, bis der Bildschirm das nächste Mal oben angekommen ist. Da der Aufbau von Grafiken jedoch auch Zeit kostet, könnte der Bildschirm eventuell mal mit dem Bildaufbau weiter sein als die Grafik und das würde wieder zu Flimmern führen. Um das zu verhindern verwendet man Page Flipping. Man baut den Bildschirm so wie er aussehen soll im Grafikkartenspeicher auf, und wenn man fertig ist wartet man darauf, das der Bildschirm wieder in der ersten Zeile des Bildschirms anfängt das Bild aufzubauen. Dann sagt man dem Bildschirm, dass er das von einem fertig gesetzten Bild aus dem Videospeicher aufbauen soll. Ergebnis: Kein Flimmern! Das klingt vielleicht für einige kompliziert, und wenn man das selber in Assembler zaubern möchte ist es das auch, doch glücklicherweise nimmt Blitz Basic einem fast die ganze Arbeit ab! Hier das Beispiel von eben, allerdings mit Page Flipping: Global Grafik Graphics 640, 480, 16, 1 SetBuffer BackBuffer () Grafik = LoadImage ("Hero.bmp") for x = 1 to 400 DrawBlock Grafik, x, 100 flip next Delay 2500 Und schau da, die Figur bewegt sich fließend wie eine Fee über den Bildschirm (Entschuldigung an alle Feen, mir ist klar das kein Mann der Welt sie so fließend wie eine Fee bewegen kann). Der Befehl SetBuffer BackBuffer () legt dabei fest, dass von nun an alle Grafiken nicht mehr auf die aktuell sichtbare Bildschirmseite (dh dem Bildschirm) sondern immer auf eine unsichtbare Bildschirmseite (dh im Videospeicher der Grafikkarte) gesetzt werden sollen. Flip tauscht die sichtbare und die unsichtbare Seite aus. Dabei wartet flip schon automatisch darauf, dass der Bildschirm am Anfang eines Aufbaus angekommen ist! Sehr praktisch. Wichtig bei dieser Art von Bildschirmaufbau ist auch zu wissen, das nur zwei Bildschirmspeicherseiten verwendet werden. Das heißt, wenn man zum zweiten mal flip benutzt, hat man den ganzen alten Müll des letzten Aufrufes wieder auf dem Bildschirm. In unserem Beispiel sieht man diesen Müll aber nicht, da der Holger schwarz umrandet ist. Dieser Schwarze Rand löscht die Überstehenden Teile von Holger die noch zu sehen waren. Da das jetzt sicher nicht jedem klar ist hier ein Beispiel um das Problem genauer zu demonstrieren: Global Grafik Graphics 640, 480, 16, 1 SetBuffer BackBuffer () Grafik = LoadImage ("Hero.bmp") for x = 1 to 400 DrawImage Grafik, x, 100 flip next Delay 2500 Der einzige Unterschied ist, dass wird statt DrawBlock DrawImage verwenden. DrawImage macht im Grunde genommen das gleiche wie DrawBlock, allerdings zeichnet es eine Farbe, die so genante „Mask Color“ nicht mit auf den Bildschirm. Die Mask Color dient dazu, das Figuren die sich auf einem Hintergrund bewegen, keinen schwarzen Kasten um sich herum haben. Standardmäßig ist die Mask Color die Farbe schwarz (0, 0, 0). Mit dem Befehl MaskImage kann man diese Farbe aber auch neu definieren. Viele Programmierer verwenden gerne (255, 0, 255), ein Rosa, dass aufgrund seiner Aufdringlichkeit so gut wie nie in Bildern vorkommt. Der Grund dafür ist in unserem Beispiel auch gut zu sehen: Teile des Bildes die völlig schwarz sind, wie die Augen der Figur, werden nicht mit gesetzt. Und das ist meistens ungewollt. Hätten wir Rosa als Hintergrundfarbe für unsere Figur genommen und Mask Color auf Rosa gesetzt hätten wir solche Probleme nicht. Doch zuerst: Wie verhindert man jetzt, das die Figuren solche Spuren hinter sich herziehen? In einem vollständigen Spiel das wirklich mit Page Flipping arbeitet müssen wir uns eigentlich gar nicht darum kümmern. Es wird ohnehin jeden Frame der ganze Bildschirm mit Grafiken vollgesetzt, also wird der Rest vom letzten Grafik setzen einfach übergeschrieben. Hier könnte man das Problem einfach lösen, indem man den Bildschirm vor dem setzten der Figur mit einer schwarzen Farbe ausmahlt. Dafür ist zum Beispiel der Befehl Rect, den wir bereits kennen gelernt haben, geeignet. Doch das möchte ich jetzt nicht weiter ausführen. Kommen wir zum nächsten Schritt. Animierte Grafiken. Theoretisch hat Blitz Basic dafür einen eigenen Befehlssatz, doch mit dem werde ich mich nicht beschäftigen. Die Nachteile bei dieser Art von Animation sind vielfältig. Erstens müssen alle Grafiken der Animation gleich groß sein, was bei Explosionen nicht unbedingt sehr Festplattenplatz sparend ist. Zweitens werden alle Grafiken einer Animation in einer Datei gespeichert und das macht es schwer einzelne Teile davon nachzubearbeiten. Deshalb werden ich in diesem Tutorium alle Animationen in Arrays speichern. Wer die andere Variante verwenden möchte kann das auch tun, beides funktioniert im Prinzip gleich gut, es ist Geschmackssache. Oder auch Gewohnheit, denn so wie ich es jetzt beschreibe werden es zum Beispiel Quick-Basic Programmierer schon kennen. Bei dem Einbinden einer Animation in ein Programm ist es sinnvoll alle Grafiken systematisch zu benennen. Damit meine ich nicht HeldObenEins.bmp, HeldUntenDrei.bmp, sondern das alle Grafiken einer Animation den gleichen Anfang haben und dann mit einer Zahl enden. Dadurch kann man die Grafiken einfach mit einer for / next Schleife in ein Array laden. Das ganze demonstriere ich höchst dramatisch an einem explodierendem Fass: Dim Fass(10) Graphics 640, 480, 16, 1 SetBuffer BackBuffer() for x = 1 to 10 Fass(x) = LoadImage("Fass\Fass" + str$(x) + ".bmp") next color 0,0,0 for x = 1 to 10 rect 300, 200, 40, 40, 1 DrawImage Fass(x), 300, 200 flip if x = 1 then delay 1000 delay 250 next Sehen wir uns das Programm einmal genauer an. Zuerst werden die Grafiken geladen. Dabei lade ich die Grafiken 1 bis 10 der Fassanimation der Reihe nach in das Array Fass(10). Nun setzt ich noch die Malfarbe auf Schwarz. Dann beginnt die Animation. Zuerst lösche ich das, was auch immer vorher da war, wo das Fass hin soll. Ich hätte Auch DrawBlock statt DrawImage verwenden können und mir somit rect gespart. Doch wenn man sich einfach vorstellt, dass da, wo jetzt rect steht, später einmal der ganze Bildschirm aufgebaut wird, und unter das Fass die Grafik irgendeiner Wiese gesetzt wird, macht das rect gleich viel mehr Sinn. Bei der ersten Animationsstufe soll das Programm eine Sekunde extra warten, damit man sich das Fass erst einmal in voller Größe ansehen kann. Danach wird jeden Durchlauf 0, 25 Sekunden gewartet, damit die Animation nicht sofort wieder vorbei ist. Später in Echtzeitspielen kann man das nicht so billig lösen, da das Ganze Spiel ja nicht bei jeder Animation anhalten soll. Doch vorerst sollte uns das genügen. Bevor wir den Abschnitt Grafik nun getrost hinter uns lassen können, kommen wir noch einmal auf die Schriftbefehle zurück. Wer sowieso keinen Text in seine Spiele einbauen will kann diesen Abschnitt einfach überspringen. Alle anderen lernen hier etwas über den Einsatz von Vektorschriften. Was ist eine Vektorschrift? Eine Vektorschrift ist definiert über Eckpunkte und das Aussehen der Verbindungslinien. In Windows sind diese Schriften Standart und heißen „True Type“. Das hat den Vorteil, dass Vektorschrift praktisch beliebig in der Größe verändert werden können, ohne dass sie anfängt eckig oder auszusehen. Der Nachteil ist, das Vektorschriften nicht allzu schnell sind, weil Rechenaufwand erforderlich ist um sie in der gewünschten Größe zu berechnen. Außerdem muss der Spieler des Spiels die Vektorschrift besitzen damit sie bei ihm angezeigt werden kann. Mit Arial, Courier und Times New Roman ist man dabei (fast) immer auf der sichern Seite. Mit allen anderen nicht, ganz besonders nicht mit irgendwelchem coolen futuristischen oder mittelalterlichen Schriftarten (Fonts), die sollte man mit dem Spiel mitliefern, was zu diversen Problemen führen kann. Doch kommen wir lieber zur Anwendung von Vektorschriften in Blitz Basic. Der wichtigste Befehl für das Verwenden einer Vektorschrift ist der Befehl LoadFont. Dieser Befehl lädt eine Font, ähnlich wie LoadImage eine Grafik lädt. Auch dieser Befehl gibt an eine Variable einen Wert zurück, der später für das benutzen der Vektorschrift verwendet wird. Wer das im letzten Kapitel übersprungen oder nicht verstanden hat, sollte es sich genau jetzt noch einmal durchlesen. Mit dem Befehl SetFont setzt man eine solche Schriftart und kann sie dann verwenden. Das nächste Programm zeigt dies: Dim Schrift(3) Graphics 640, 480, 16, 1 Schrift(1) = LoadFont("Arial", 30, 1, 1, 1) Schrift(2) = LoadFont("Arial", 50, 0, 0, 0) Schrift(3) = LoadFont("Arial", 10, 1, 0, 0) SetFont Schrift(1) Text 0, 0, "Arial in Grösse 30" SetFont Schrift(2) Text 0, 100, "Arial in Grösse 50" SetFont Schrift(3) Text 0, 200, "Arial in Grösse 10" Delay 2500 Die Zahl hinter der Schrift gibt die Höhe des größten Buchstaben in Pixel an und die drei Zahlen dahinter geben jeweils an ob... Ich finde das guckt jeder jetzt mal in der Befehlübersicht nach. Irgendwann müsst ihr es ja mal lernen ;-). Gut, damit ist das Grafiktutorium beendet. Ihr seid auf dem besten Wege ganz tolle Spieleprogrammierer zu werden. Doch das wichtigste ist und bleibt euer Phantasie und euer Durchhaltevermögen. Egal wie toll ihr programmiert, es nützt gar nichts wenn ihr keine Ideen habt. Und wenn ihr gute Ideen habt bringt das nichts wenn ihr nicht bereit seid genug Zeit in euer Projekte zu investieren und sie bis zum möglicherweise bitteren Ende durchzuziehen. Jeder, der bis hierher gekommen ist sollte erst einmal ein paar kleinere Programme mit BlitzBasic schreiben. Danach könnt ihr dann weiterlesen. Sowieso bringt euch die ganze Theorie nichts, wenn ihr sie nicht einmal selbst eingesetzt habt. Niemand kann euch Programmiererfahrung beibringen! Weiter mit Teil 3... Quelle: www.BlitzBasic.de |