![]() |
![]() |
Wo wollen wir hin? Laden Sie einmal folgendes Programm herunter und lassen es laufen (Entpacken in ein beliebiges Verzeichnis und demo36.exe laufen lassen). Sie sollten dazu aber über eine Auflösung von mindestens 1024x768 (XGA) verfügen. Mit mehrmaligem Drücken der ESC-Taste verlassen Sie es. Diese Demo gewinnt vielleicht nicht den nächsten Schönheitswettbewerb, aber man kann folgendes daran entdecken:
Nun, trauen Sie sich es zu, das gleich nachzubauen? Dann mal gleich ran ans Werk!
Ansonsten werden wir jetzt hier Schritt für Schritt die Entwicklung eines solchen Programms durchgehen.
Wenn Sie die folgenden Beispiele einfach nach FBIDE in eine leere Datei kopieren und ausführen, werden Sie Fehlermeldungen a la "Datei nicht gefunden" bekommen. Der Grund dafür ist, dass beim "Quick run" eine temporäre Datei in einem speziellen Arbeitsverzeichnis erstellt wird. Dort werden sich aber in der Regel nicht die Dateien befinden, die das Programm einlesen will. Daher ist es zu empfehlen, in den Beispielen die relativen Pfade (z.B. "spritered.bmp") durch absolute Pfadangaben zu ersetzen.
Wir hatten uns in Kapitel 2 schon einmal mit Sprites befasst. Damals waren sie noch sehr primitiv ausgefallen, weil man sie mittels BASIC-Befehlen "zeichnen" musste. Keine so geniale Methode, um etwas zu produzieren, was einigermassen gut ausschaut. Sehr viel besser ist die Benutzung eines Malprogramms wie Gimp, Paint Shop oder wenigstens das einfache MSPaint, das bei Windows dabei ist. Aber wie bekommen wir das Malergebnis in QBasic, bzw. jetzt in Freebasic? Bei QBasic ist das schwierig, da es nur ein Grafikspeicherformat beherrscht - sein eigenes. Von dem weiss wiederum kein Malprogramm etwas. Natürlich gibt es einen Weg: Man liest die Datei, die das Malprogramm generiert hat, als Binary File ein, zeichnet jedes Pixel auf den Bildschirm und speichert das Ergebnis im QBASIC-eigenen Format wieder ab. Bleibt die nicht ganz unwesentliche Frage: Welche Daten stehen wo in dieser Malprogramm-Datei?
Bei Freebasic sieht es schon besser aus. Das Standard-Bitmap-Format auf Windows ist "bmp". Dies beherrscht jedes Malprogramm. Und Freebasic kennt es auch.
Zum Anzeigen eines bmp-Files auf dem Bildschirm brauchen wir vier Befehle. IMAGECREATE, BLOAD, PUT und IMAGEDESTROY. Und einen neuen Datentyp: ANY PTR
Schauen wir uns zunächst ein kleines Beispielprogramm an. Damit es läuft, müssen Sie die obige Demo installiert haben. Dann befindet sich die Testdatei "scblue_do.bmp" im Demoverzeichnis.
SUB test1 DIM img1 AS ANY PTR SCREEN 18,24 img1 = IMAGECREATE(68, 87) BLOAD "scblue_do.bmp",img1 PUT (200,200),img1,PSET SLEEP IMAGEDESTROY img1 END SUB
Was passiert hier? Zuerst wird eine Variable vom Typ "ANY PTR" deklariert. Das ist ein Zeiger. Dieser zeigt allerdings nicht auf ein File oder auf ein Array, sondern auf eine Stelle im Hauptspeicher. Der Hauptspeicher, in dem alle Programme, Variablen und sonstige Daten der laufenden Programme gerade gehalten werden, ist praktisch eine unstrukturierte Menge von Bytes. Jedes Bytes trägt eine Adresse, genau wie die Bytes in einem File. Ein "Heap Pointer", ein Speicherzeiger ist nichts anderes als ein 32-bit-Integer mit einer solchen Adresse. Wir deklarieren hier also einen solchen Zeiger und nennen ihn img1. Zu diesem Zeitpunkt ist noch nicht klar, auf welche Adresse img1 zeigt.
Das nächste ist die SCREEN-Anweisung, das kennen wir schon. Dann kommt IMAGECREATE. Das fragt beim Betriebssystem freien Platz im Hauptspeicher an. Und zwar soviel, wie ein bmp-Bild mit 68x87 Pixeln braucht. 68x87 Pixel hat unsere Testbitmap. Wir könnten hier aber auch mehr angeben, z.B. 100x100. Nur nicht weniger. Das würde später eventuell zu einem Crash unseres Programms führen.
So. Hat die Sache geklappt, gibt IMAGECREATE() einen Zeiger auf den freien Speicherplatz zurück, den wir für das Bild brauchen. Und die Adresse dieses freien Speicherplatzes kommt in img1.
Wenn Sie richtig hinschauen, haben wir hier natürlich einen Fehler gemacht: Wir haben nicht geprüft, ob die Sache geklappt hat. Das ist allerdings für diese Demozwecke verzeihlich, da im Zeitalter von Hauptspeichern mit mehreren Hundert Megabyte es nicht wahrscheinlich ist, dass keine 2 oder 3 Kilobyte mehr für unser Bild übrig sind. Sollten Sie allerdings variable Bilder zulassen, dann muss diese Prüfung unbedingt erfolgen. Hat das Betriebssystem keinen Speicher, dann gibt IMAGECREATE die Adresse 0 zurück.
So, nun kommt der dritte Befehl, BLOAD. Dieser lädt nun die bmp-Datei in den reservierten Speicher img1. Das dürfte selbsterklärend sein. Die Frage bleibt jetzt noch, wo und wann die Grafik auf dem Bildschirm angezeigt werden soll. Das erledigt der Befehl "PUT": PUT (x,y),img,relation. (x,y) ist die Bildschirmkoordinate der linken oberen Ecke. "img" ist der Zeiger auf den Speicherbereich der Grafik. "relation" kann ziemlich verschiedene Werte annehmen, Details können Sie beim Befehl PUT(Grafik) in der unteren Tabelle abrufen. Der Wert PSET sorgt dafür, dass die Pixel der Grafik die sonst schon vorhandenen Pixel auf dem Bildschirm einfach überschreiben. Möglich sind aber auch OR, das eine bitweise OR-Verknüpfung zwischen Bildschirm und Grafik durchführt. Oder AND. Bis hin zu recht komplizierten Sachen mit dem s.g. Alpha-Kanal und benutezrdefinierten Filtern. Aber uns reicht hier ersteinmal PSET.
Damit sollte das Bild auf dem Bildschirm aufgetaucht sein. Benötigen Sie den Grafikspeicher nicht mehr, ist es nett, diesen beim Betriebssystem wieder zur Verfügung zu stellen. Dies geschieht mit IMAGEDESTROY und Angabe des Zeigers. Wieviel Speicher dort reserviert war, weiss das Betriebssystem selbst. Schlaues Kerlchen, das...
Das Ergebnis ist ganz nett, aber noch verbesserungsfähig. Nehmen wir an, das blaue Dingsda ist so etwas wie ein Raumschiff. Dann wäre es wünschenswert, wenn der weisse Kasten drumherum verschwinden würde. Aber wie dies anstellen? Nun gut, man kann im Malprogramm die Aussenfläche schwarz malen. Aber was ist, wenn der Hintergrund auf dem Bildschirm dann plötzlich rot ist? Der Kasten sollte durchsichtig sein. Das wär's. Freebasic hat mit solchen Durchsichtigkeiten keine grossen Probleme, aber die Grafik selbst darauf vorzubereiten, ist nicht ganz so einfach. Damit beschäftigen wir uns bald.
Eigentlich wollen wir die Bitmaps ja mit einem Malprogramm erzeugen. Aber für Testzwecke ist die Erzeugung via Programm einfacher. Wie bekommen wir die Bitmap vom Schirm in eine bmp-Datei?
Übung:
Zeichne ein Schachbrettmuster, 300 x 300 Pixel, immer abwechselnd ein Pixel rot und eines schwarz.
Lösung:
SUB testbmp 'plottet eine Test-bmp-Grafik und speichert sie DIM ix,iy DIM img1 AS ANY PTR SCREEN 18,24 'Plotte Testgrafik: Karo FOR ix=0 TO 299 FOR iy=0 TO 399 PSET (ix,iy),RGB(((ix+iy) MOD 2)*255,0,0) NEXT iy NEXT ix 'Speichere die ersten 10 x 10 Pixel links oben img1 = IMAGECREATE(40, 40) GET (0,0)-(39,39),img1 BSAVE "test1.bmp",img1,30000 'Scheint ziemlich egal zu sein, was man hier 'als Groesse angibt IMAGEDESTROY img1 SLEEP END SUB
Die ersten Zeilen plotten eine Rastergrafik - das Testsprite der Demo am Anfang. Dann wird wieder ein Speicherbereich angelegt, wie auch schon beim letzten Beispiel. Mit GET (x1,y1)-(x2,y2),img wird der Bildschirmausschnitt zwischen (x1,y1) links oben und (x2,y2) rechts unten in den Speicherbereich img gelegt. Dann wird mit BSAVE dieser Bereich im bmp-Format in eine Datei geschrieben: BSAVE name,img,size. name=Dateiname, img=Zeiger auf die Grafik, size=Grösse in Bytes. size scheint aber irgendwie egal zu sein; freebasic nimmt kurzerhand die Grösse der Grafik, wie sie aus GET resultiert - das ist praktisch.
So, und nun kommen wir zum entscheidenden Problem: Wir wollen die schwarz gezeichneten Pixel durchsichtig haben. So dass der Huntergrund durch unser Sprite immer dort durchschimmert, wo zur Zeit Schwarz ist. Zum Glück nimmt uns dabei Freebasic die Hauptarbeit ab. Es kennt nämlich den PUT-Modus TRANS. Er funktioniert wie PSET, nur dass eine ganz bestimmte Farbe, die Farbe RGB(&HFF,0,&HFF), als "Maskenfarbe" für das "Durchsichtige" dient. Überall, wo diese Maskenfarbe ist, scheint später der Hintergrund durch. &HFF steht für Hex FF. Unsere ganze Arbeit besteht also darin, die Farbe Schwarz in der Bitmap gegen diese Farbe auszutauschen.
Die einfachste Variante wäre in unserem Fall natürlich, das Karo gleich als Mischung aus RGB(&HFF,0,0) und RGB(&HFF,0,&HFF) zu plotten. Aber das ginge am Ziel vorbei: Wir wollen ja soweit kommen, eine extern gemalte bmp-Grafik als Sprite verwenden zu können. Also müssen wir in der Lage sein, in einer externen bmp-Grafik alle Pixel einer bestimmten Farbe, z.B. Schwarz oder Weiss, in diese Transparentfarbe umzuwandeln. Das kann man im externen Malprogramm natürlich von Hand machen und man kann dabei auch sehr gut verrückt werden. Besser ist es, das per Programm zu machen. Und so eins fertigen wir uns jetzt an.
Die Schwierigkeit ist, das Format von bmp-Files zu kennen. Nun, da gibt es zwei Wege. Der erste ist, eine eminent wichtige Webseite für Programmierer zu kennen und dort das Format nachzuschauen. Der zweite Weg ist, per Experiment und Hex-Editor herauszufinden, wie es aufgebaut ist. Das bmp-Format ist einfach genug, dass das nicht zu schwer ist, vor allem bei True-Color-Bitmaps nicht. Ah ja: Sie müssen natürlich darauf achten, im externen Zeichenprogramm die bmp-Grafik mit 24-Bit Farbtiefe abzuspeichern, alles andere würde nicht funktionieren.
Egal, welchen Weg Sie einschlagen, nach kurzer Zeit werden Sie folgendes Ergebnis haben:
Eine 24-Bit-bmp-Datei besteht aus einem Header und den eigentlichen Grafikdaten. Die Grafikdaten bestehen im Wesentlichen aus den RGB-Farbcodes der einzelnen Bildpunkte, die einfach aneinandergereiht sind. Na ja, fast einfach. Auf einen kleinen Haken kommen wir gleich noch. Der Header ist folgendermassen aufgebaut:
Start | Länge | Bedeutung |
---|---|---|
&H12 | 4 | Breite in Pixel |
&H16 | 4 | Höhe in Pixel |
Lediglich dies und dass der gesamte Header 54 Bytes lang ist, müssen Sie wissen.
Der Haken im Grafikteil ist die Zeilenlänge. Jede Zeile muss nämlich ein Vielfaches von 4-Byte als Länge haben. Wird diese Länge nicht von der Bildinformation benötigt, muss sie aufgefüllt werden. Ist z.B. die Grafik 10 Pixel breit, sind dies 3 x 10 = 30 Bytes. Das nächste 4-Vielfache ist aber 32 Bytes. Also muss man jeweils ncoh zwei Nullbytes ans Zeilenende schreiben.
Für die folgenden Übungen noch ein Tipp: Fertigen Sie sich zwei Routinen an. Eine, die einen RGB-Wert in einen 3-Byte-String übersetzt. Und eine Routine, die das Umgekehrte macht. Ich habe diese rgb2str() und str2rgb() genannt.
Und noch ein Tipp: Benutzen Sie zur Betrachtung Ihrer Programmergebnisse einen guten Bildbetrachter, wie ACDSee oder Irfanview. Auch beim Totalcommander ist ein bmp-Betrachter im Lister dabei. Nur ein solcher Betrachter (am Besten zoomfähig) zeigt Ihnen schnell Ihre eventuellen Fehler auf.
Übung 2:
Schreiben Sie eine Routine zum Einlesen einer bmp-Grafik auf den Bildschirm - ohne Hilfe von BLOAD.
Übung 3:
Schreiben Sie eine Routine zum Rausschreiben einer bmp-Grafik auf den Bildschirm - ohne Hilfe von BSAVE.
Übung 4:
Schreiben Sie auf Basis des Codes der anderen beiden Übungen eine Routine, die eine bestimmte Farbe in einer bmp-Grafik gegen eine andere austauscht.
Meine Lösung von Übung3:
'----------------------------------------------------- FUNCTION STR2RGB(s AS STRING) AS INTEGER DIM retval AS INTEGER=-1 DIM i AS INTEGER, sum AS INTEGER, ix AS INTEGER IF (LEN(s)=3) THEN sum=0 FOR i=1 TO 3 ix=ASC(mid$(s,i,1))*(256^(3-i)) 'PRINT i,ix 'sleep sum=sum+ix NEXT i retval=sum END IF STR2RGB=retval END FUNCTION '----------------------------------------------------- FUNCTION RGB2STR(ix AS INTEGER) AS STRING DIM retval AS STRING="xxx" 'Dummy-Wert DIM i AS INTEGER, sum AS INTEGER, iy AS INTEGER, idiv AS INTEGER sum=ix FOR i=1 TO 3 idiv=256^(3-i) iy=INT(ix/idiv) mid$(retval,i,1)=CHR$(iy) sum=sum-iy NEXT i RGB2STR=retval END FUNCTION SUB convertcolbmp(fname1 AS STRING, fname2 AS STRING, oldcolor AS INTEGER, newcolor AS INTEGER) 'Konvertiert bei einer 24-Bit-bmp 'alle colold Pixel in Pixel mit der Farbe 'colnew CONST bmpheadsize=54 CONST pos_xsize=&H12 CONST pos_ysize=&H16 DIM s AS STRING, icol AS INTEGER, k AS INTEGER DIM xsize AS INTEGER, ysize AS INTEGER, ix AS INTEGER, fuell AS INTEGER, rest AS INTEGER DIM cmd1 AS STRING OPEN fname1 FOR BINARY AS #1 cmd1="del "+fname2 SHELL(cmd1) OPEN fname2 FOR BINARY AS #2 'Header lesen und schreiben s=INPUT$(bmpheadsize,1) 'Lies x- und y-Breite aus xsize=ASC(mid$(s,pos_xsize+1,1))+ASC(mid$(s,pos_xsize+2,1))*256 ysize=ASC(mid$(s,pos_ysize+1,1))+ASC(mid$(s,pos_ysize+2,1))*256 PRINT #2,s; 'Jede Zeile enthaelt ein Vielfaches von 32 Bit, also 4 Bytes. 'Was nicht Bildinformation ist, muss aufgefuellt werden 'Ist z.B. eine Zeile 10 Pixel breit ==> 10 x 3 = 30 Bytes ==> 2 Auffuellbytes 'auf 32 Bytes. rest=(xsize*3) MOD 4 fuell=(4-rest) MOD 4 k=0 WHILE NOT EOF(1) 'Lies die naechste Zeile ein FOR ix=0 TO xsize-1 'Lies das naechste Pixel (3 Bytes) ein s=INPUT$(3,1) '? ix,tx(s) 'sleep 'Pruefe, ob es der Farbe colold entspricht icol=STR2RGB(s) IF (icol=oldcolor) THEN s=RGB2STR(newcolor) END IF PRINT #2,s; k=k+3 NEXT ix 'Ueberspringe Fuellbytes s=INPUT$(fuell,1) PRINT #2,s; WEND END SUB
Die Funktion, die jetzt in einer bmp-Grafik eine bestimmte Farbe zur Transparentfarbe macht, ist nur noch ein Sonderfall der obigen Routine:
SUB convert2transbmp(fname1 AS STRING, fname2 AS STRING, oldcolor AS INTEGER) convertcolbmp(fname1,fname2,oldcolor,&HFF00FF) END SUB
Der Rest ist eigentlich nur noch Altbekanntes. Allerdings können wir es dieses Mal viel eleganter erledigen.
Wir nehmen uns vor, dass jedes Sprite für sich programmtechnisch möglichst unabhängig stehen soll. So liegt es nahe, einen Typ tsprite zu definieren. Jede tsprite-Variable enthält dann alle notwendigen Informationen zu einem Sprite. Über welche Informationen muss ein Sprite verfügen?
Mein Vorschlag:
Eine Variable vom Typ tsprite bezeichnen wir jetzt schon einmal als "Objekt". Dieses Objekt muss initialisiert werden. Das heisst erstens, die Sprite-Bitmap zu laden und img darauf zeigen zu lassen. bg sollte auf die Nulladresse gesetzt werden, x, y kann man auch auf Null setzen, xsize und ysize stehen durch die Bitmap schon fest. Allerdings kann man sie nur ermitteln, wenn man den Bitmap-Header manuell ausliest, wie wir das im Vorabschnitt gemacht haben. bgsaved steht natürlich auf Null, ebenso die Koordinaten zum bg-Ausschnitt. Wenn wir dann ein Sprite starten lassen wollen, dann müssen wir lediglich x und y auf eine geeignete Position setzen.
Nebenbemerkung: Null als Defaultwert ist eigentlich nicht so geeignet. Ein Defaultwert sollte so gewählt sein, dass auf der einen Seite das Programm noch weiterläuft, auf der anderen Seite es aber so unsinnig weiterläuft, dass der Programmierer den Fehler sofort bemerkt. Null ist in unserem Zusammenhang bei Koordinaten keineswegs unsinnig und wir laufen Gefahr, den ein oder anderen Fehler zu übersehen. Daher verwende ich als Defaultwert von Koordinaten nicht Null, sondern eine Konstante, die ich NAN genannt habe - Not A Number. Paradoxerweise muss es natürlich durchaus eine Zahl sein; ich habe sie auf -999999 gesetzt. Wann immer ich irgendwo Kontrollausgaben zu den Koordinaten mache und ein NAN-Wert dabei verwickelt ist, werden die Ausgabenwerte extrem negativ werden und mir sofort sagen: "Da ist ein Sprite-Wert noch auf Default und muss noch sinnvoll besetzt werden."
Als Nächstes brauchen wir eine Routine um ein Sprite zu bewegen. Die alte Position ist in (x,y) des Sprites notiert; der Routine muss also nur das Sprite und die neuen Koordinaten übergeben werden. Was muss diese Routine alles erledigen?
Es ist als erstes ratsam, diese Routine ersteinmal ohne Komplikationen zu realisieren, das heisst ohne das Clipping, den Fall, dass das Sprite schon halb aus dem Bildschirm hinausragt.
Den Start eines Sprites erledigt man einfach dadurch, dass man es zur ersten Bildschirmposition bewegt. Nun können Sie schon einmal die ersten Früchte geniessen und Ihre ersten Sprites in einem Testprogramm über den Bildschirm wandern lassen.
Schwingungen und Kreisbewegungen arten leicht in unübersichtliche Formelarbeit aus. Das kann man mit s.g. Polarkoordinaten verhindern. Dann wird die Beschreibung selbst kompliziert erscheinende Bewegungen ganz einfach. Nehmen wir z.B. eine Kreisbewegung, bei der die Länge des Radius hin- und herschwingt, eine s.g. Lissajous-Figur. In Polarkoordinaten ist dies ganz einfach auszudrücken, da der Radius eine Koordinate darstellt, wie die x-Koordinate im kartesischen System. Ein Koordinatenangabe in Polarkoordinaten lautet also: (r,phi). r ist der Radius, phi der Drehwinkel. Nun kann man mit r und phi alles mögliche anstellen. Z.B. r=r{0}*cos(phi/10) bilden, was dazu führt, dass r fünfmal pro Kreisumdrehung hin- und herschwingt. Um die Rückübersetzung kümmert sich dann eine s.g. Koordinatentransformation. Die schaut einfach so aus: x = r * sin (phi) y = r * cos (phi) wenn phi der Winkel ist, den die positive x- und y-Achse einschliessen und sich im Uhrzeigersinn öffnet. Beim Darstellen eines Sprites muss man sich nicht gross drum kümmern, ob es noch vollständig auf den Bildschirm passt - das erledigt die gfx-Engine selbst. Sehr aufpassen muss man jedoch beim Abspeichern des Bildschirmhintergrunds. Hier nimmt es einem die gfx-Lib sehr übel, wenn man einen Bildschirmausschnitt angibt, der ganz oder teilweise nicht sichtbar ist. Daher kann man nicht einfach die Spriteposition plus Höhe und Breite des Sprites hernehmen, um den Bildschirmhintergrund wieder einzuladen, sondern muss explizit den zu sichernden Bereich notieren. Dazu dienen die bg-Koordinaten x1bg,y1bg,x2bg und y2bg. Wenn Sie nun die Sache das erste Mal implementieren, werden Sie vielleicht über den gleichen Strick stolpern wie ich. Bei mir funktionierte das Clipping das erste Mal fast perfekt. Fast. Das Ärgerliche war, dass manchmal ein 1-Pixel breiter Streifen des Sprites ganz unten oder oben am Bildschirmrand zurückblieb. Und es war nicht ganz trivial herauszubekommen, warum. Haben Sie die Aufgabe gelöst? Nun, hier ist meine Realisierung: Der Fallstrick befindet sich in savegetbg(), wenn man die Sichtbarkeitsprüfung (visible=....) weglässt. Wenn Sie nun einige Sprites gleichzeitig auf dem Bildschirm herumflitzen lassen, dann werden diese recht bald komische Farbflecken darauf hinterlassen. Der Grund ist auch klar: Überlappt ein Sprite plötzlich mit einem anderen, überlappen sich auch ihre Restaurationsversuche des Bildschirmhintergrunds. Da passiert es dann recht schnell, dass das Bild des einen Sprites in den bg-Speicher des anderen gerät. Dazu bewegen sich die Sprites dann noch - da entsteht Salat. Es gibt drei Möglichkeiten, damit umzugehen. Die Betrachtung dieser drei Alternativen ist gleichzeitig ein Blick in die Computergeschichte. Alternative I) werden wir hier behandeln und sie ist auch heute noch für viele 2D-Spiele und 2D-Anwendungen die Grundlage. In früheren Computern waren Sprites und "Collision Detection" bereits in den Grafikchips eingebaut, z.B. beim Commodore Amiga. Genauer gesagt ist der Begriff "Sprite" eigentlich nur für solche hardwareimplementierten Grafikobjekte korrekt. Das, was wir hier behandeln, die reine Softwarelösung, heisst korrekt eigentlich "Shape". Aber da der Begriff "Sprite" sehr viel weiter verbreitet ist und sich auch über seine einstmals spezifische Bedeutung hinaus verbreitet hat, bin ich hier auch mal nicht so genau gewesen. Jedenfalls spielte die Collision Detection bei Sprites im eigentlichen Sinn früher eine grosse Rolle und auf ihr basierten fast alle Action- und Animationsspiele bei Atari, C64 und Co. Alternative II ist eher exotisch. Alternative III ist die heutige Standardmethode. Das hat einen einfachen Grund: Echte 3D-Darstellung ist anders praktisch nicht möglich. Der 3D-Raum muss immer von hinten nach vorne auf den 2D-Schirm gebracht werden. Was hinten ist und was vorne, das bestimmt oft eine vierte Koordinate, der s.g. z-Buffer. Zurück zur Alternative I: Die Grundaufgabe besteht darin, festzustellen, ob Sprite 1 mit Sprite 2 einen überlappenden Bereich hat. Allerdings soll dieser Test erfolgen, bevor ein Sprite an einer neuen Stelle gezeichnet wird. Daher müssen wir die Aufgabe etwas umformulieren: Wir müssen feststellen, ob Sprite 1 mit Sprite 2 überlappen würde, wenn wir Sprite 1 an den neuen Koordinaten xnew, ynew zeichnen würden. Wenn das der Fall wäre, tun wir's eben nicht und die Collision detection ist erledigt. Dabei stellt sich die Frage, wie man so eine Überlappung feststellen kann. Nun, eigentlich ganz einfach: Man muss feststellen, ob eine der Ecken von Sprite 1 innerhalb von Sprite2 liegt. Und umgekehrt. Das sind also insgesamt acht if-Abfragen. Man kann das sicher auch noch abstrakter lösen, aber so tut's uns das. Die nächste Aufgabe: Das Ganze muss ja nicht nur für ein Spritepaar erledigt werden. Wir brauchen eine Routine, die ein Sprite gegen alle anderen Sprites testet, informatischer gesprochen, ein Sprite gegen ein ganzes Array von Sprites. Dabei ist es nützlich, dies als Funktion zu gestalten und diese die Nummer des Sprites aus dem Array zurückgeben zu lassen, das die Kollision verursacht. Die dritte Aufgabe: Wir müssen uns überlegen, wie wir mit der Kollisionsinformation umgehen. Die erste und einfachste Möglichkeit ist, das Sprite dann einfach nicht zu zeichnen. Erweitern Sie Ihr Programm ersteinmal so! Wenn Sie das dann getan haben, werden Sie allerdings feststellen, dass das nicht so gut aussieht, da dann die Sprites bei Kollisionen die anderen "überhüpfen". Besser wäre es, sie würden an ihrer alten Position warten wie ein Auto an einer geschlossenen Bahnschranke. Wenn Sie bisher alle Sprites innerhalb einer einzigen Schleife bewegt haben, dann ist dieses Konzept hier allerdings am Ende. Wegen den Wartevorgängen muss jedes Sprite seine eigene, individuell voranschreitende Bewegungsschleife, gewissermassen seine eigene Uhr haben. Ich habe das so realisiert, dass ich jedes Spriteobjekt durch ein anderes Objekt "umhüllt" habe, das die "Uhr" enthält: Genausogut kann man natürlich die Uhr auch in das Spriteobjekt selbst einbauen. Das wäre allerdings insofern schlecht, als tspriteclock eine Spezialisierung darstellt, mit der man das schöne allgemeine tsprite-Objekt nicht "verschmutzen" sollte. tspriteclock enthält die Speicherstruktur für eine "Uhrenbewegung", wie wir es in der Eingangsdemo gesehen haben. Für andere Bewegungen müsste es anders aussehen. org ist der Mittelpunkt der Kreisbewegung, radius ist klar, move_v ist die Bewegungsgeschwindigkeit und move_dir die Richtung. So, nun bauen Sie Ihr Programm so aus, dass ganz viele Sprites darauf herumschwirren können! Der restliche Code sieht bei mir so aus: In den Code ist noch ein Double-Buffering eingebaut, was sich allerdings nur auf langsameren Rechnern bemerkbar macht. A propos Schnelligkeit: Die Geschwindigkeit lässt sich mit dem sleep-Befehl ganz am Ende variieren. Die Bereitstellung der Startwerte für die Sprites "hardcoded" ist natürlich nicht so elegant. Viel eleganter wäre eine benutzerdefinierte Eingabe oder ein Einlesen der Daten aus einem ini-File. Kleine Übung! Was jetzt noch ganz entscheidend fehlt, ist die Bedienung per Maus. Die ist in der gfx-Lib allerdings vergleichsweise einfach. Es gibt zwei Befehle dafür, GETMOUSE und SETMOUSE, wobei man wahrscheinlich in fast allen Fällen ledglich GETMOUSE benötigt: GETMOUSE x,y,wheel,buttons. GETMOUSE fragt die Mausposition, das Rad und die Buttons ab. Befindet sich die Maus im aktiven Programmfenster, sind die Koordinaten x,y auf der Mausposition, ansonsten sind sie negativ. Die einzelnen Bits von buttons geben an, welcher Knopf gerade gedrückt ist. Zum Auswerten ist die BIT-Funktion ganz nützlich; sie gibt an, ob das Bit Nr. x im Argument gesetzt ist oder nicht. BIT(4,2) = 1, da Bit Nr. 2 im Argument, der Zahl 4, auf 1 ist. Im integer-Wert buttons repräsentiert Bit Nr. 0 den linken, Bit Nr. 1 den rechten und Bit Nr. 2 den mittleren Mausknopf. Wenn wir definieren, dann können wir mit BIT(buttons,LEFTKEY) den Status der linken Maustaste abfragen. Die Funktion gibt TRUE zurück, wenn sie gedrückt ist. Wahrscheinlich das kleinste Zeichenprogramm der Welt: Mit SETMOUSE können Sie den Mauszeiger irgendwohin schieben und den Mauszeiger sichtbar oder unsichtbar machen. Wir wollen unsere Mausattraktion nun in unser Sprite-Programm einbauen. Testen Sie mal
folgendes Programm. (Es benötigt die gleichen bmp-Files wie das Grundprogramm). Zunächst scheint nichts anders. Wenn Sie aber nun auf die Taste F1 drücken, erscheint ein Fenster. Offenbar ein "selbstgetricktes". Sie können das Fenster mit der Maus bewegen, indem Sie es irgendwo "anfassen", das braucht nicht die Titelleiste zu sein. Schliessen können Sie es mit dem Kreuz rechts oben. Mit mehrmals ESC verlassen Sie das Programm wieder. Übung: Programmieren Sie das nach! Das ist keine einfache Übung. Wenn Sie das Programm aufmerksam betrachten, sehen Sie, dass die Sprites sich unter dem Fenster wegbewegen. Beim Verschieben wird der Hintergrund des Fensters trotz sich bewegender Sprites korrekt wiederhergestellt. Ein paar Tipps: Ansonsten soll hier nur ein Ausschnitt aus der Hauptschleife betrachtet werden: Hier findet praktisch die ganze Mausverwaltung statt. Die Funktion twindow_inrange() prüft, ob sich die übergebenen Koordinaten innerhalb des übergebenen Fensters befinden. Das entspricht der Frage, ob sich der Mauszeiger innerhalb des Fensters befindet. BIT() prüft, ob der linke Mausknopf gedrückt ist. xnew>=0 and ynew>=0 prüft, ob sich der Mauszeiger überhaupt innerhalb des Programmfensters befindet. Das ist dann zusätzlich relevant, wenn das Fenster schon gepackt und dann über den Bildschirmrand hinausgeschoben wird. Innerhalb der if-Bedingung werden dann zwei Funktionalitäten behandelt: Die Aktivierung des Schliessen-Knopfs und das Verschieben. twindow_mousclose() prüft lediglich, ob sich der Mauszeiger im Schliessknopf-Bereich des Fensters befindet. (wins0() ist übrigens ein globales Array mit allen Fenstern.) Ist das der Fall, dann wird twindow_hide() aufgerufen, das das Fenster unsichtbar macht. (Es bleibt allerdings noch erhalten und kann wieder sichtbar gemacht werden!) Wegen der oben erwähnten Restaurierungsmethode, bei der dann der ganze Bildschirm neu gezeichnet wird, muss man dieser Funktion auch sämtliche Sprite-Objekte übergeben. Innerhalb der Verschiebeaktivität wird zunächst die relative Position zwischen Mauszeiger und Fensterposition (linke obere Ecke) festgestellt. Diese wird von der Mausposition abgezogen, um twindow_move() die neue Fensterposition zu übergeben. twindow_move() besteht nur aus einem Aufruf von movesprite2() und dem Setzen des show-Flags. Ganz interessant an dem Fenster-Beispiel dürfte sein: Erstens die Erkenntnis, dass auch Fenster im Grunde nichts anderes sind als Sprites. Und zweitens, dass man mit doch sehr begrenztem Aufwand selbst eine einfache Fensterverwaltung programmieren kann. Und drittens bekommt man einen Einblick, wie so eine Fensterverwaltung funktioniert - und auch, welche Komplikationen sie bereithält, wenn man sie grösser und leistungsfähiger machen will. Die Themen, die wir jetzt in der gfx-Lib nicht behandelt haben, sind Superbitmaps und Sound. Superbitmaps oder Layers oder Maps sind Bildschirmspeicher, die grösser sind als der Bildschirm. Man kann dann mit dem Bildschirm einen Ausschnitt des Layers darstellen und mit diesem auf dem Layer hin- und herfahren. Die Grafikoperationen geschehen alle auf dem Layer. Fast jedes 2D-Spiel benutzt solche Layer, ganz typisch z.B. bei Jump-and-Run-Spielen. Die Realisation solcher Layer steht und fällt mit Bildschirmseiten und Grafikspeicher via IMAGECREATE. Da die primitiven Grafikoperationen (LINE, PSET usw.) und die Bitmap-Operationen (GET, PUT) nur auf Bildschirmseiten arbeiten, muss der Weg zur Manipulation der Layer immer über Bildschirmseiten führen. Will man z.B. ein Sprite auf dem Layer bewegen, muss man zuerst einen passenden Ausschnitt des Layers in eine dafür vorgesehen Page laden, dort das Sprite bewegen und dann den Ausschnitt zurückspeichern. Das Laden und Zurückspeichern mittels PUT und GET verlangt allerdings, die Position des Zeigers im Bildspeicher des Layers zu kennen, wenn man einen bestimmten Ausschnitt (x1,y1)-(x2,y2) haben will. Da muss man vorsichtig rechnen, da man sonst viele Programmabstürze produziert...Eine (bessere) Alternative ist es allerdings, in so einem Fall nicht die gfx-Lib zu nehmen, sondern auf die SDL umzuschwenken, die sich ganz dem Thema "Layer" widmet. gfx ist nicht dafür gedacht, Grundlage aufwändiger Grafikprogramme zu sein. Beim Thema "Sound" sieht's ganz mager aus: Freebasic und die gfx unterstützen ausser "beep" nichts in Richtung Sound. Den Grund kann man hier wörtlich von den Programmieren von Freebasic erfahren:Zyklische Bewegungen: Polarkoordinaten
Clipping
Meine Lösung
#INCLUDE "fbgfx.bi"
OPTION EXPLICIT
CONST NAN=-999999
CONST xscreensize=800
CONST yscreensize=600
CONST FALSE=0
CONST TRUE=-1
CONST pi=22/7
'-----------------------------------------------------
FUNCTION min(i AS INTEGER, j AS INTEGER) AS INTEGER
IF (i<j) THEN min=i ELSE min=j
END FUNCTION
'-----------------------------------------------------
FUNCTION max(i AS INTEGER, j AS INTEGER) AS INTEGER
IF (i>j) THEN max=i ELSE max=j
END FUNCTION
'-----------------------------------------------------
TYPE tsprite
img AS ANY PTR
bg AS ANY PTR
x AS INTEGER
y AS INTEGER
xsize AS INTEGER
ysize AS INTEGER
x1bg AS INTEGER
y1bg AS INTEGER
x2bg AS INTEGER
y2bg AS INTEGER
NAME0 AS STRING*80
bgsaved AS INTEGER
END TYPE
'-----------------------------------------------------
SUB initsprite(sprite AS tsprite)
sprite.img=0
sprite.bg=0
'Koordinaten sollten auf NAN stehen, wenn das Sprite gerade nicht
'gesetzt ist.
sprite.x=NAN:sprite.y=NAN
sprite.xsize=0:sprite.ysize=0
sprite.x1bg=NAN:sprite.x2bg=NAN:sprite.y1bg=NAN:sprite.y2BG=NAN
sprite.bgsaved=NAN
END SUB
'-----------------------------------------------------
SUB loadsprite(sprite AS tsprite, fname AS STRING)
CONST bmpheadsize=54
CONST pos_xsize=&H12
CONST pos_ysize=&H16
DIM s AS STRING
'Pruefe, ob der Dateiname die Endung ".bmp" hat.
DIM s1 AS STRING=RIGHT$(fname,4)
IF (s1<>".bmp") THEN
PRINT("Error in loadsprite(): suffix of fname must be .bmp")
SLEEP
END
END IF
'Lies vorab den Header des bmp-Files ein, um die
'x- und y-Groesse festzustellen.
OPEN fname FOR BINARY AS #1
'Header lesen
s=INPUT$(bmpheadsize,1)
IF (LEN(s)=0) THEN
PRINT "Error in loadsprite(): File not found: ";fname
SLEEP
END
END IF
'Lies x- und y-Breite aus
sprite.xsize=ASC(mid$(s,pos_xsize+1,1))+ASC(mid$(s,pos_xsize+2,1))*256
sprite.ysize=ASC(mid$(s,pos_ysize+1,1))+ASC(mid$(s,pos_ysize+2,1))*256
CLOSE #1
'Lies hier die eigentlichen Daten in einen zuvor reservierten
'Speicherbereich ein
sprite.img = IMAGECREATE(sprite.xsize+10, sprite.ysize+10)
sprite.bg = IMAGECREATE(sprite.xsize+10, sprite.ysize+10)
BLOAD fname,sprite.img
sprite.NAME0=fname
END SUB
'-----------------------------------------------------
SUB savegetbg(sprite AS tsprite)
'Speichere den Background so, dass das Sprite sich auch teilweise
'ausserhalb des Screens befinden kann.
'Dazu muss an GET der Ausschnitt weitergegeben werden, der
'sich noch im Screen befindet.
DIM x1 AS INTEGER,y1 AS INTEGER,x2 AS INTEGER,y2 AS INTEGER
DIM visible AS INTEGER
'Stelle zunaechst den Ausschnitt des Sprites fest, der sich innerhalb
'des Bildschirms befindet.
x1=min(max(sprite.x,0),xscreensize-1)
y1=min(max(sprite.y,0),yscreensize-1)
x2=min(max(sprite.x+sprite.xsize-1,0),xscreensize-1)
y2=min(max(sprite.y+sprite.ysize-1,0),yscreensize-1)
'Befindet sich das Sprite komplett ausserhalb des Screens?
visible=(sprite.x+sprite.xsize-1>=0 AND sprite.y+sprite.ysize-1>=0)
visible=visible AND (sprite.x<=xscreensize-1) AND (sprite.y<=yscreensize-1)
'Speichere nun ab
IF (NOT visible) THEN
sprite.bgsaved=0 'Zeigt an, ob ein Hintergrund gespeichert ist
ELSE
GET (x1,y1)-(x2,y2),sprite.bg
sprite.bgsaved=1
END IF
sprite.x1bg=x1:sprite.x2BG=x2:sprite.y1bg=y1:sprite.y2bg=y2
END SUB
'-----------------------------------------------------
SUB movesprite(sprite AS tsprite, xnew AS INTEGER, ynew AS INTEGER)
'Bewegt das Sprite zur Position xnew/ynew
'xnew und ynew werden ggf. so korrigiert, dass das ganze Sprite
'auf den Bildschirm passt.
'Ist das Sprite an einer alten Stelle?
IF (sprite.x<>NAN) THEN
'Ist ein Background bzgl. der alten Position gespeichert?
IF (sprite.bg>0 AND sprite.bgsaved=1) THEN
'Restauriere Background
PUT (sprite.x1bg,sprite.y1bg),sprite.bg,PSET
ELSE
'Sprite befindet sich komplett ausserhalb des Screens
END IF
END IF
'Speichere Hintergrund ab
sprite.x=xnew:sprite.y=ynew
savegetbg(sprite)
'Setze Sprite an neuer Stelle
PUT (sprite.x,sprite.y),sprite.img,TRANS
END SUB
'-----------------------------------------------------
SUB closesprite(sprite AS tsprite)
IMAGEDESTROY sprite.img
IF (sprite.bg>0) THEN IMAGEDESTROY sprite.bg
END SUB
'-----------------------------------------------------
SUB testscreen
DIM ix AS INTEGER, iy AS INTEGER, i0 AS INTEGER
SCREEN 19,24
'Plotte Hintergrundgrafik
FOR ix=0 TO 79
FOR iy=0 TO 59
i0=((ix+iy) MOD 2)
LINE (ix*10,iy*10)-((ix+1)*10-1,(iy+1)*10-1),RGB(i0*200,i0*200,i0*200),BF
NEXT iy
NEXT ix
END SUB
'-----------------------------------------------------
SUB main1a
DIM spritered AS tsprite, spritegreen AS tsprite, spriteblue AS tsprite
DIM ix AS INTEGER, ix1 AS INTEGER, iy AS INTEGER,isig AS INTEGER, i AS INTEGER, _
rblue AS DOUBLE, rgreen AS DOUBLE, phi AS DOUBLE
testscreen
initsprite(spritered)
loadsprite(spritered,"testred.bmp")
initsprite(spritegreen)
loadsprite(spritegreen,"test3.bmp")
initsprite(spriteblue)
loadsprite(spriteblue,"testblue.bmp")
ix1=0
isig=1
i=0
WHILE (1=1)
iy=300-350*SIN(ix1/800*8*pi)
movesprite(spritered,ix1,iy)
'Polarkoordinaten
phi=i/400*2*pi
IF (i=400) THEN i=0
'Blaues Sprite laeuft auf konstantem Umkreis
rblue=250
'Gruenes Sprite pendelt 30 mal hin und her ==>> Lissajousfigur
rgreen=250*SIN(phi*10)
'Umwandlung blaue Koordinaten in kartesische Koordinaten
ix=xscreensize/2+rblue*SIN(phi)
iy=yscreensize/2-rblue*COS(phi)
movesprite(spriteblue,ix,iy)
'Umwandlung gruene Koordinaten in kartesische Koordinaten
ix=xscreensize/2+rgreen*SIN(phi-1)
iy=yscreensize/2-rgreen*COS(phi-1)
movesprite(spritegreen,ix,iy)
IF MULTIKEY(SC_ESCAPE) THEN EXIT WHILE
ix1+=isig*2
i+=1
IF (ix1>=xscreensize-5 OR ix1<=0) THEN
isig=-isig
END IF
SLEEP 20
WEND
SLEEP
closesprite(spritered)
closesprite(spritegreen)
closesprite(spriteblue)
END SUB
Kollidierende Sprites
'-----------------------------------------------------
TYPE torigin
x AS INTEGER
y AS INTEGER
END TYPE
'-----------------------------------------------------
TYPE tspriteclock
i AS INTEGER
radius AS DOUBLE
org AS torigin
move_v AS DOUBLE
move_dir AS INTEGER
bmpname AS STRING*80
sprite AS tsprite
END TYPE
'-----------------------------------------------------
'%%2
FUNCTION colldetectsprite(sprite1 AS tsprite, sprite2 AS tsprite, newx AS INTEGER, newy AS INTEGER) AS INTEGER
'Schaue, ob sprite1 mit sprite2 kollidieren wuerde, wenn es an newx/newy waere.
'Gibt TRUE zurueck, wenn ja
DIM x11 AS INTEGER = newx
DIM y11 AS INTEGER = newy
DIM x12 AS INTEGER = newx+sprite1.xsize-1
DIM y12 AS INTEGER = newy+sprite1.ysize-1
DIM x21 AS INTEGER = sprite2.x
DIM y21 AS INTEGER = sprite2.y
DIM x22 AS INTEGER = sprite2.x+sprite2.xsize-1
DIM y22 AS INTEGER = sprite2.y+sprite2.ysize-1
DIM s1ins2 AS INTEGER, s2ins1 AS INTEGER
'Bedingung ist, dass mindestens ein Eck eines Sprites innerhalb des anderen liegen muss.
s1ins2=(x11>=x21 AND x11<=x22 AND y11>=y21 AND y11<=y22)
s1ins2=s1ins2 OR (x11>=x21 AND x11<=x22 AND y12>=y21 AND y12<=y22)
s1ins2=s1ins2 OR (x12>=x21 AND x12<=x22 AND y11>=y21 AND y11<=y22)
s1ins2=s1ins2 OR (x12>=x21 AND x12<=x22 AND y12>=y21 AND y12<=y22)
'Jetzt versuchen wir's andersrum
s2ins1=(x21>=x11 AND x21<=x12 AND y21>=y11 AND y21<=y12)
s2ins1=s2ins1 OR (x21>=x11 AND x21<=x12 AND y22>=y11 AND y22<=y12)
s2ins1=s2ins1 OR (x22>=x11 AND x22<=x12 AND y21>=y11 AND y21<=y12)
s2ins1=s2ins1 OR (x22>=x11 AND x22<=x12 AND y22>=y11 AND y22<=y12)
colldetectsprite=s1ins2 OR s2ins1
END FUNCTION
'-----------------------------------------------------
FUNCTION colldetectsprite2(sprite1 AS tsprite, sprites() AS tsprite, isprite AS INTEGER, nsprite AS INTEGER, newx AS INTEGER, newy AS INTEGER) AS INTEGER
'Schaue, ob sprite1 mit einem Sprite aus dem Sprite-Array sprites() kollidieren wuerde, wenn es an newx/newy waere.
'isprite identifiziert sprite1 in sprites() (kann auch ausserhalb von 0..nsprite-1 liegen)
'nsprite = Anzahl der relevanten Sprites in sprites()
'Gibt TRUE zurueck, wenn ja
DIM i AS INTEGER, retval AS INTEGER=FALSE
FOR i=0 TO nsprite-1
IF (i<>isprite) THEN
IF (colldetectsprite(sprite1,sprites(i),newx,newy)) THEN retval=TRUE
END IF
NEXT i
colldetectsprite2=retval
END FUNCTION
'-----------------------------------------------------
FUNCTION colldetectsprite3(sprite1 AS tsprite, sprites() AS tsprite, isprite AS INTEGER, nsprite AS INTEGER, newx AS INTEGER, newy AS INTEGER) AS INTEGER
'Wie colldetectsprite2(), aber gibt die Nummer des Sprites zurueck, mit dem kollidiert wird.
'-1, wenn keine Kollision vorliegt
'%%4
DIM i AS INTEGER, retval AS INTEGER=-1
FOR i=0 TO nsprite-1
IF (i<>isprite) THEN
IF (colldetectsprite(sprite1,sprites(i),newx,newy)) THEN retval=i
END IF
NEXT i
colldetectsprite3=retval
END FUNCTION
'-----------------------------------------------------
SUB flipscreen
'Switche aktive Seite zur sichtbaren Seite
activepage=1-activepage
SCREENSET activepage,1-activepage
'Aktualisiere die bisher sichtbare und "veraltete" Seite
SCREENCOPY 1-activepage,activepage
'Alle neuen Operationen werden nun auf activepage ausgefuehrt.
END SUB
'-----------------------------------------------------
TYPE torigin
x AS INTEGER
y AS INTEGER
END TYPE
'-----------------------------------------------------
TYPE tspriteclock
i AS INTEGER
radius AS DOUBLE
org AS torigin
move_v AS DOUBLE
move_dir AS INTEGER
bmpname AS STRING*80
sprite AS tsprite
END TYPE
'-----------------------------------------------------
SUB movespriteclock(sc AS tspriteclock, sprites() AS tsprite, isprite AS INTEGER, nsprite AS integer)
DIM phi AS DOUBLE
DIM ix,iy, collision,collision1 AS INTEGER
'Teste erst Kollision an der alten Stelle
collision1=colldetectsprite3(sc.sprite,sprites(),isprite,nsprite,sc.sprite.x,sc.sprite.y)
'Bilde neue kartesische Koordinaten
phi=sc.i/400*2*pi
IF (sc.i=400) THEN sc.i=0
ix=sc.org.x+sc.radius*SIN(sc.move_dir*sc.move_v*phi)
iy=sc.org.y-sc.radius*COS(sc.move_dir*sc.move_v*phi)
collision=colldetectsprite3(sc.sprite,sprites(),isprite,nsprite,ix,iy)
IF ((collision<0 OR collision<isprite) AND (collision1<0 OR collision1<isprite)) THEN
'Keine Kollision oder aktuelles Sprite hat Vorfahrt
'==> Bewege Sprite an neue Position
movesprite(sc.sprite,ix,iy)
'? #3,isprite;" ";ix;" ";iy;
Variation mit Maus
Grundsätzliches
CONST LEFTKEY=0
CONST RIGHTKEY=1
CONST TRUE=-1
CONST FALSE=0
'-----------------------------------------------
'Small test for mouse
'-----------------------------------------------
#INCLUDE "fbgfx.bi"
OPTION EXPLICIT
CONST LEFTKEY=0
DIM AS INTEGER x,y,buttons
SCREEN 19,24,2
WHILE (1=1)
GETMOUSE x,y,,buttons
IF (x>=0 AND y>=0 AND BIT(buttons,LEFTKEY)) THEN PSET (x,y)
IF MULTIKEY(SC_ESCAPE) THEN EXIT WHILE
WEND
Ausbau: Bewegte Sprites mit Fenster
(...)
IF MULTIKEY(SC_ESCAPE) THEN EXIT WHILE
IF MULTIKEY(SC_F1 AND wins0(0).show=0) THEN
twindow_show(wins0(0),200,200)
twindow_write(wins0(0),0,0,"Hallo!")
END IF
IF MULTIKEY(SC_F2) THEN twindow_hide(wins0(0),spritecl(),sprites(),nsprite)
GETMOUSE xnew,ynew,,buttons
IF (twindow_inrange(wins0(0),xold,yold) AND BIT(buttons,LEFTKEY) AND wins0(0).show=1 AND xnew>=0 AND ynew>=0) THEN
'Postion des Mauszeigers innerhalb des Close-Buttons
IF (twindow_mouseclose(wins0(0),xnew,ynew)) THEN
twindow_hide(wins0(0),spritecl(),sprites(),nsprite)
ELSE
'Postion des Mauszeigers innerhalb des Fensters
xrel=xold-wins0(0).SCREEN.x
yrel=yold-wins0(0).SCREEN.y
IF (xold<-9999 OR yold<-9999) THEN
xrel=xnew-wins0(0).SCREEN.x
yrel=ynew-wins0(0).SCREEN.y
END IF
twindow_move(wins0(0),xnew-xrel,ynew-yrel)
END IF
END IF
xold=xnew
yold=ynew
(...)
Allgemeines und Weiteres
PLAY (Music): Unsupported.Type: statement Too specific and not needed by most. If anybody wants this functionality a music library would be more practical.This was barely practical in the 80's with the pc speaker. Anyone who wants this feature should rethink motives, or be shot. |
Die meisten benutzen die SDL-Sound-Funktionen.