Tutorial Bomberman1
Inhaltsverzeichnis
Wir programmieren einen Bombermanklon!
Teil 1 : Die Codebasis und der Editor
Vorwort
Ave, und willkommen zu meinem zweiten Tutorial, dem Anfang einer Tutorialserie!
Neben Tetris gehört Bomberman wohl zu einem der erfolgreichsten Spielkonzepte der Videospielgeschichte, wurde es doch auf fast jede Plattform und dazu noch in diversen Varianten (es existieren über 30 Bombermanspiele) umgesetzt. Der Erfolg dieses Spielkonzepts liegt (wie auch bei Tetris) in seinem recht einfachen, aber dennoch fesselndem Spielprinzip in dem es darum geht die Mitspieler durch geschicktes Platzieren von Bomben aus den Rennen zu werfen und als letzter auf dem Spielfeld zu stehen. (Mehr Infos zum Thema Bomberman gibts u.a. hier : http://www.bomberman.info)
Und gerade wegen der Einfachheit dieses Spielprinzips ist ein Bombermanklon gradezu prädestiniert um als Grundlage für eine kleine Tutorialserie zum Thema Spieleprogrammierung zu dienen. Während Anfängern ein recht einfacher Einstieg in die Spieleprogrammierung geboten wird, bietet es fortgeschritteneren Programmierern sehr viel Freiraum zur Weiterentwicklung.
In dieser mehrteiligen Serie werde ich von Anfang an und auf auch für Einsteiger (für die die Tutorials auch gedacht sind) geeignete Art und Weise zeigen wie man einen Bombermanklon und den dazugehörenden Editor von Grund auf programmiert.
In diesem ersten Teil der Serie werden wir die vom Editor und dem Spiel verwendete Codebasis erstellen und widmen uns dann später im dem Editor und seinen Eigenheiten.
Am Ende wird der geneigte Leser einen vollständen Karteneditor zum Erstellen einfacher Bombermankarten fertiggestellt haben, mit dessen Hilfe wir dann die Karten für den im zweiten Tutorial entwickelten Bombermanklons erstellen können.
Realisiert wird das Ganze natürlich in Delphi und OpenGL. Später wird dann noch FMOD als Soundlibrary zu den beiden stoßen.
Voraussetzungen
Was ich in diesem Tutorial allerdings nicht erklären werde, und was natürlich benötigt wird, sind zum einen Kenntnisse in Object Pascal (dazu gibts nicht nur sehr viele gute Tuts, sondern v.a. auch die Anleitung zu Delphi bzw. dessen Hilfedatei), und grundlegende OpenGL-Kenntnisse zu Themen wie Rotation und Bewegung, Zeichnen von geometrischen Objekten und Texturemapping. Zu diesen Themen gibts hier auf DGL ja jede Menge guter Tutorials. Wer mit diesen Voraussetzungen ausgestattet ist, der sollte in dieser Tutorialserie auf keine Problem stoßen und kann sich schonmal auf einen brauchbaren Bombermanklon freuen, den er im Anschluß dann fleißig verbessern und erweitern kann.
Das Ziel
Am Ende dieser Tutorialserie wird ein ausgewachsener 3D-Bombermanklon mit diversen Spezieleffekten wie Alphamasking, Partikelsystemen, fulminanten Explosionen und Soundausgabe stehen, der sich vor seinen Vorbildern nicht zu verstecken braucht. Und praktisch nebenbei (oder vornedran ;-)) wird dann noch ein leistungsstarker Editor mit einem einfach zu nutzenden Dateiformat entwickelt, mit dessen Hilfe in Windeseile neue Karten erstellt werden können. Ziel dieser Tutorialserie ist nicht unbedingt der perfekte Bombermanklon, sondern es geht mir eher darum den Einsteigern unter den Leser neben einem guten Programmierstil auch noch wichtige Grundlagen für die Entwicklung eines Spiels mitzugeben. Denn was bei einem kleinen Projekt schon nützlich ist, kann sich bei einem großen Projekt schnell als große Hilfe erweisen.
Eventuell (hängt zum großen Teil auch vom Feedback der Leser ab) werde ich sogar noch ein Tutorial hinterherschieben, das unseren Klon noch im Mehrspielerfähigkeiten mittels LAN erweitert.
Viel Spaß also beim Bombenlegen...
Die Codebasis
Wie die Überschrift es schon erahnen lässt, widmen wir uns zu allererst der Codebasis. Die Codebasis nennt sich deshalb so, weil sie grundlegende Funktionen und Klassen zur Verfügung stellt die sowohl vom Editor als später auch vom Spiel genutzt werden. Bezeichnen kann man das natürlich auch als Engine, doch dieser Begriff ist in meinen Augen falsch, da ich unter Engine ein viel komplexeres Grundgerüst verstehe, als dies bei einem Bombermanklon benötigt wird.
Diese Codebasis besteht zu Beginn nur aus einigen wenigen Dingen, wird aber im Verlaufe der Tutorialserie ständig erweitert und ist Dreh- und Angelpunkt unseres Bombermanklons. In diesem Tut stellt unsere Codebasis (die sich in der Datei global.pas befindet) folgende Objekte zur Verfügung :
Einen Texturenmanager (TTextureManager)
Viele von euch werden ihre Texturen bisher wohl auf einfache Art und Weise über ein (meist dynamisches) array of glUInt "verwaltet" haben, und haben dann beim Programmstart alle Texturen per Hand in dieses array eingelesen haben. Nicht nur das dass Laden der Texturen auf diese Art und Weise recht umständlich war und bei jeder neuen Textur der Quellcode geändert werden musste, nein auch der Zugriff auf die Texturen beim Zeichnen der Szene war umständlich und geschah auf die Weise glBindTexture(GL_TEXTURE_2D, Textur[3]). Dadurch war man immer gezwungen, sich zu vergewissern dass Textur Nr.3 auch die richtige Textur war.
Bei unserem Bombermanklon wollen wir diesen Fehler nicht machen, und haben auch nicht vor bei jeder neuen Textur den Quellcode ändern zu müssen. Das ist nicht nur für den Programmierer selbst komfortabler, sondern auch für die Spieler, die die Karten mit eigenen Texturen verschönern wollen.
Deshalb schreiben wir uns einen kleinen aber sehr nützlichen Texturenmanager, mit dessen Hilfe wir alle Texturen in einem Verzeichnis zur Laufzeit in den Texturenpool laden können, und der Texturen nicht anhand ihrer Position im array bindet, sondern auf diese mit einer Stringlist über deren Namen zugreift. So wird z.b. aus dem glBindTexture(GL_TEXTURE_2D, Textur[3]) ein einfaches und leichter durchschaubares TextureManager.BindTexture('steinwand1.tga').
Um uns nicht mit dem Laden verschiedener Formate herumschlagen zu müssen, benutzen wir zum Umwandeln der Texturen die glBMP-Bibliothek von http://delphigl.cfxweb.net in einer von mir um einige nützliche Dinge erweiterten Variante (ist im Download zu diesem Tutorial natürlich enthalten).
Unser Texturmanager besteht aus folgenden Prozeduren und Variablen:
- Texture : array of TglBMP
- In diesem dynamischen array werden alle geladenen Texturen abgelegt.
- TextureName : THashedStringList
- In dieser StringList werden die Texturennamen abgelegt, über die der Texturenmanager später die Texturen im Texturearray addressiert. Hier wird statt einer herkömmlichen TStringList eine THashedStringList verwendet, da diese bei häufigen Suchvorgängen (was bei vielen Texturenwechseln der Fall ist) und besonders bei großen Datenmengen schneller als die herkömmliche TStringList ist.
- Hinweis für Nutzer von Delphiversionen < 6:
- In diesen Delphiversionen existiert der Typ THashedStringList nicht. Dadruch seid ihr gezwungen doch eine normale TStringList zu verwenden.
- constructor Create
- Erstellt und initialisiert den Texturenmanager.
- destructor Destroy
- Deinitialisiert und entfernt den Texturenmanager aus dem Speicher.
- procedure AddTexturesInDir(pDirName, pFileMask : String)
- Fügt dem Texturenpool alle im Verzeichnis pDirName liegenden Texturen mit der in pFileMask angegebenen Endung hinzu.
- procedure AddTexture(pFileName, pTextureName : String)
- Fügt dem Texturenpool die aus der Datei pFileName geladene Textur unter dem Namen pTextureName hinzu.
- procedure BindTexture(pTextureName : String)
- Sucht die Textur mit dem Namen pTextureName im Texturenpool und bindet diese an das GL_TEXTURE_2D-Ziel. Wurde die Textur nicht gefunden, wird für das Texturemapping deaktiviert.
Wie man also sehen kann steckt hinter einem Texturenmanager nicht sonderlich viel Arbeit, er erleichtert einem das Leben allerdings erheblich. Besonders wenn man später Dinge wie Alphamasking einbringen will, muss man nicht mehr umständlich im Zeichencode rumwühlen, sondern man muss einfach nur noch die BindTexture-Routine ein wenig abändern. Alles in allem also ein nützliches Hilfsmittel das in keinem OpenGL-Projekt fehlen sollte.
Das Spielfeld (TMap)
Der nächste Baustein unserer Codebasis repräsentiert das Spielfeld auf dem die Aktion stattfindet. Viel zu bieten hat diese Klasse momentan, ausser einer Zeichenprozedur und einige Editorprozeduren noch nicht viel, was sich aber in naher Zukunft noch ändern wird.
Hier also Prozeduren und Variablen des Spielfelds :
- PlayGround : array of array of TFieldType
- Diese Variable repräsentiert das eigentliche Spielfeld und ist ein zweidimensionales array vom Typ TFieldType, dessen Definition die Eigenschaften eines Spielfelds festlegt und so aussieht :
TFieldType = record
Texture : String;
FType : Byte;
FSpecial : Byte;
end;
- Textur steht für den Namen der diesem Feld zugewiesenen Textur, während FType angibt ob es sich um ein Bodenteil, einen festen Block oder einen zerstörbaren Block handelt. Natürlich ist diese Variable frei erweiterbar (was später auch noch ausgiebig getan wird), und man könnte hier quasi unendlich viele Feldtypen mit den tollsten Eigenschaften (z.B. bewegliche Blöcke) definieren. FSpecial gibt letztendlich noch an, ob dieses Feld noch eine besondere Eigenschaft, wie z.B. einen Spielerstart enthält.
- DefaultFieldTexture : String
- In dieser Variable wird die Standardtextur festgelegt, die einem Feld zugewiesen wird, nachdem ein darauf befindlicher sprengbarer Block zerstört wurde.
- procedure SetSize(pSize : Integer)
- Legt die in pSize angegebene Spielfeldgröße fest, und tut nichts anderes als das dynamisches PlayGround-array in seiner Größe mittels SetLength zu verändern.
- procedure FillBorder(pTexture : String;pFieldType : Integer)
- Füllt den Rand des Spielfelds mit der in pTexture angegebenen Textur und dem in pFieldType angegebenen Feldtyp.
- procedure FillGround(pTexture : String;pFieldType : Integer)
- Füllt das Spielfeld (exklusive dem Rand) mit der in pTexture angegebenen Textur und dem in pFieldType angegebenen Feldtyp
- procedure Draw;
- Wie der Prozedurenname bereits erahnen lässt, wird in dieser Prozedur das komplette Spielfeld gezeichnet. Diese Prozedur spielt daher also ein Hauptrolle in unserem Bombermanprojekt.
- Da in der Grundversion unseres Editors nichts weiter getan wird, als die Felder in einer for..do-Schleife zu zeichnen, bedarf der Quellcode wohl kaum detaillierter Beschreibungen. Eine Zeile dürfte den Anfänger in der Leserschaft jedoch etwas unbekannt vorkommen, weshalb ich diese hier etwas detaillierter erkläre :
- glLoadName(OpenGLID)
- Mit dieser Zeile legen wir einen "Namen" (Eine Variable vom Typ Cardinal, und nicht etwa wie der leicht irreführende Titel Name zu vermuten lässte einen String) auf den NameStack von OpenGL, der dann dem nächsten gezeichneten geometrischen Objekt zugewiesen wird. Damit wird dann jedem Feld ein einzigartiger OpenGL-Name zugewiesen.
- Mit Hilfe dieses OpenGL-Namens kann man dann später mit einer Selection genannten Methode herausfinden auf welches Feld der Mauszeiger gerade zeigt. Benötigt wird das Ganze natürlich nur für den Editor. Wie genau dieses Selektionsverfahren realisiert wird, dazu gibts später im Editorkapitel mehr.
- procedure SaveToFile(pFileName : String)
- Diese Prozedur sichert die Karte in die mit pFileName angegebene Datei. Dabei wird ein einfach zu lesendes und leicht erweiterbares ASCII-Format verwendet :
Dateiversion Größe Der Karte Name der Standardtextur Textur von Feld [1,0] FeldTyp von Feld [1,0] SpezialInfo von Feld [1,0] . . . Textur von Feld [Größe Der Karte-1,Größe Der Karte-1] FeldTyp von Feld [Größe Der Karte-1,Größe Der Karte-1] SpezialInfo von Feld [Größe Der Karte-1,Größe Der Karte-1]
- Ein binäres Format hätte hier natürlich den Vorteil, das zum einen der Platzbedarf geringer wäre, und es zum anderen auch vor Veränderungen sicher wäre, allerdings wären die alten Karten nach jeder Änderung unbrauchbar.Für ein reines ASCII-Format kann man sich jedoch schnell mal einen Importer schreiben, der ältere Karten in eine neuere Version konvertiert.
- procedure LoadFromFile(pFileName : String)
- Lädt die Karte aus der in pFileName angegebene Datei.
Soviel also zu unserer aktuellen Codebasis, die als Grundlage für unseren Editor sowie das Spiel an sich dienen wird. Bis jetzt war ja noch nichts spektakuläres dabei, und selbst die unerfahreneren unter euch sollten bis hierhin keine Probleme gehabt haben. Der größte Teil unserer zukünftigen Änderungen werden an der Codebasis stattfinden und vor allem das Kartenobjekt betreffen. Natürlich werden später noch neue Klassen u.a. zur Spielerverwaltung (und evtl. Gegnerverwaltung) hinzukommen, dazu aber mehr in den folgenden Tutorials.
Doch wenden wir uns jetzt dem Editor zu...
Der Editor
Ein Spiel ist ja bekannterweise nur so gut wie sein Inhalt, der mit Hilfe der entwickelten Tools wie einem Leveleditor erstellt wird. Was nützen die tollsten und modernsten Features oder die ausgeglügelste künstliche Intelligenz, wenn die Level in denen man sich bewegt einfach mies sind...deshalb sollte man sich bei der Entwicklung eines Spiels immer auf seine Tools konzentrieren, und diese so intuitiv und gleichzeitig leistungsfähig wie möglich gestalten, damit man später schnell und einfach guten Gamecontent erstellen kann.
Zum Glück ist diese Thematik bei einem Bombermanklon nicht so brisant wie bei einem Egoshooter á la Doom3, dessen Editor sich vor 3DStudio MAX nicht zu verstecken braucht, aber trotzdem geht einem der Levelbau viel leichter von der Hand wenn man einen einfach zu nutzenden Mapeditor zur Hand hat, statt die Karten für die erste Betaversion seines Spiels mittels Notepad zusammenzubasteln.
In diesem Kapitel werden wir uns deshalb um einen leicht zu bedienenden Editor kümmern, der grundlegende Fähigkeiten zum Erstellen der Karten und einige brauchbare Tools mitbringt.
Natürlich wird der Editor mittels der Delphi-VCL realisiert, ist sie doch schliesslich dass was Delphi so groß gemacht hat, und die Entwicklungszeit von Windowsanwendungen signifikant verringert.
Wie auf obigem Screenshot zu erkennen basiert die Grundversion unseres Editors auf zwei Fenstern. Im linken Fenster wird das Spielfeld dargestellt, und im rechten befinden sich zum einen Einstellungen bezüglich des zu setztenden Feldes und einige Tools die dem Kartenbauer das Leben erleichtern.
Der Quellcode des Editors bietet ansich nichts unbekanntes und dürfte recht leicht verständlich sein. Dennoch werde ich einige relevante Codestellen erläutern :
OpenGL initialisieren (TForm1.FormCreate)
Der Einfachheit halber verwende ich in unserem Bombermanprojekt die OpenGL12.pas von Mike Lischke in einer minimal abgeänderten Form (betrifft nur das Erstellen des Rendercontext), die dem Projektquellcode natürlich beiliegt.
Diese macht das erstellen eines Rendercontextes zu einem Kinderspiel :
DC := GetDC(Handle);
RC := CreateRenderingContext(DC, [opDoubleBuffered], 32, 24, 8, 0, 0, 0, DummyPal);
In der ersten Zeile erstellen wir uns vom Handle unseres Fensters einen gültigen DeviceContext vom Typ HDC, der dann genutzt wird um den OpenGL-Rendercontext zu erstellen.
In der zweiten Zeile erstellen wir einen OpenGL-Context mit Doublebuffering, 32Bit Farbtiefe, 24bittigem Tiefenpuffer und einem 8Bit großen Stencilpuffer, mit dessen Hilfe wir in einem späteren Tutorial Schattenwürfe realisieren werden. Die DummyPal-Variable ist wie ihr Name schon sagt nur ein Dummy, der nicht benötigt wird, da unser Rendercontext keinen Palettenmodus (256 Farben) nutzt.
Einige Zeilen später werden wir dann einige OpenGL-Werte initialisieren :
glEnable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
Die Funktion dieser Zeilen dürfte bekannt sein : Zeile 1 aktiviert zweidimensionales Texturemapping, die Zeilen 2 und 3 legen den Tiefentest fest (aktiviert, und nur Fragmente die weiter vorne liegen durchlassen). Zeile 4 sagt der Grafikkarte dann noch, das sie die best aussehendste Perspektivenkorrektur verwenden soll.
Danach laden wir noch die benötigten Texturen :
TextureManager.AddTexturesInDir('textures', '*.tga');
for i := 0 to TextureManager.TextureName.Count-1 do
Form2.TextureCombo.Items.Add(TextureManager.TextureName[i]);
ChDir(ExtractFilePath(Application.ExeName));
SetLength(SpecialTex, Length(SpecialTexName));
for i := Low(SpecialTexName) to High(SpecialTexName) do
begin
SpecialTex[i] := TglBMP.Create('textures\special\'+SpecialTexName[i]);
SpecialTex[i].GenTexture(False, False);
end;
Im ersten Block sagen wir dem Texturenmanager das er alle TGA-Dateien im Unterordner textures in den Texturenpool laden soll. Ich verwende das TGA-Format deshalb, da es eines der wenigen Formate (neben PNG) ist, das einen Alphakanal unterstützt. Mehr zu diesem Alphakanal und seiner Anwendung gibts ineinem späteren Tutorial (Kleiner Hinweis : Alphamasking). Gleichzeitig fügen wir noch alle Texturennamen in die ComboBox im Toolfenster ein, damit wir diese später bequem zuweisen können.
Im zweiten Block laden wir die in der Konstantensektion festgelegten Spezialtexturen die später z.B. genutzt werden um die Startpunkte der Spieler zu kennzeichnen.
Ich hoffe ihr seit nach diesem etwas langweiligen und nicht besonders anspruchsvollem Ausflug in die Initialisierung des Editors noch wach, denn jetzt kommt etwas schwierigeres...
Spielfelder mittels des Mauszeigers auswählen (aka Selection)
Im Kapitel zur Codebasis hab ich dieses Thema schonmal kurz angeschnitten und euch gezeigt, wie man ein Objekt mittels des Namestacks unter OpenGL "bekannt" macht. Wer mehr zum Thema Selection erfahren will, kann sich ja das Tutorial dazu auf meiner Hompage durchlesen : Selection-Tutorial. (Oder das Tutorial_Selection hier im Wiki)
Nun stelle ich euch eine (recht kurze, aber knackige) Prozedur vor, die prüft ob und wenn ja, welches dieser auf dem Namestack befindlichen Objekte unter dem Mauszeiger ist. Gemeint ist die Funktion GetSelectBufferHit, die ich euch hier häppchenweise erläutern werde :
var
SelectBuffer : array[0..512] of TGLUInt;
Viewport : TVector4i;
Hits,i : Integer;
HitZValue : TGLUInt;
Hit : TGLUInt;
Die Variablendeklaration dieser Funktion bedarf erstmal einer fundierten Erklärung : Im SelectBuffer-array werden alle Objekttreffer gespeichert. Jeder Objekttreffer besteht aus vier Werten : Anzahl der Objekte auf dem Namestack zum Zeitpunkt des Treffers, kleinste Z-Koordinate aller Eckpunkte des Objektes, größte Z-Koordinate aller Eckpunkte des Objektes und Name des getroffenen Objektes. Deshalb kann unser SelectBuffer bis zu 128 (512/4) Objekttreffer beinhalten. In der Variable Viewport wird der aktuelle OpenGL-Viewport gespeichert, den wir später noch brauchen werden. Und mit den Variablen Hits, HitZValue und Hit stellen wir am Ende der Funktion in einer Schleife fest welches Objekt nun letztendlich unter dem Mauszeiger liegt.
Kommen wir nun zur eigentlichen Funktion und der in ihr enthaltenen erwähnenswerten Codezeilen :
glGetIntegerv(GL_VIEWPORT, @viewport);
Mittels dieser Zeile speichern wir den aktuellen Viewport in die vorher dafür definierte Variable.
glSelectBuffer(512, @SelectBuffer);
Hiermit teilen wir OpenGL zum einen die Größe des SelectBuffers und zum anderen dessen Adresse im Speicher mit.
glRenderMode(GL_SELECT);
Jetzt schalten wir in den SelectModus, in dem nichts auf den Bildschirm gerendert wird, aber unser SelectBuffer gefüllt wird.
glInitNames;
glPushName(0);
Diese zwei Zeilen sind unabdingbar, denn sie dienen dazu den Namestack zu initialisieren.
glMatrixMode(GL_PROJECTION);
glLoadIdentity;
gluPickMatrix(MousePos.x, viewport[3]-MousePos.y, 3.0, 3.0, Viewport);
gluPerspective(45.0, Form1.ClientWidth/Form1.ClientHeight, 0.1, 2048);
Form1.DrawScene;
Dieser Teil ist sehr wichtig. Hier setzen wir die Ansichtsmatrix so, das (wenn wir im normalen Rendermodus wären) nur der Bereich im 3 Pixel Umkreis um die Mausposition herum gerendert werden würde. Dies dient dazu das nur Objekte im direkten Umkreis der Mausposition in den Selectbuffer gerendert werden.
Hits := glRenderMode(GL_RENDER);
Hit := High(TGLUInt);
HitZValue := High(TGLUInt);
Die erste Zeile schaltet jetzt wieder in den normalen Rendermodus. Allerdings liefert uns OpenGL jetzt (da wir uns vorher im Selectmodus befanden) die Anzahl der getroffenen Objekte zurück. Die zwei nachfolgenden Zeilen initialisieren die Variablen vor, mit deren Hilfe wir später herausfinden welches Objekt dem Mauszeiger am nächsten liegt.
for i := 0 to Hits-1 do
if SelectBuffer[(i*4)+1] < HitZValue then
begin
Hit := SelectBuffer[(i*4)+3];
HitZValue := SelectBuffer[(i*4)+1];
end;
Result := Hit;
Mittels der Schleife prüfen wir ob die Z-Koordinaten des neuen Objektes niedriger als die bereits gespeicherte ist (wir erinnern uns : in SelectBuffer[(n*4)+1] steht die kleinste Z-Koordinate der Eckpunkte dieses Objekts, sprich der dem Betrachter nächste Z-Wert). Wenn dies der Fall ist, übernehmen wir dessen Z-Koordinaten in HitZValue und dessen Namen in Hit. Am Ende dieser Schleife haben wir dann endlich das Objekt, welches der Kamera am nächsten ist und geben dies im Resultat der Funktion zurück.
Wenns dir jetzt besser geht als unserem kleine Bomberman, dann hast du das Schlimmste bereits überstanden, bei Nichtverstehen nochmal das von mir erwähnte Tut durchlesen...
Zeichnen der Szene (TForm1.ApplicationEvents1Idle)
Bekannterweise gibt es ja jede Menge Möglichkeiten und Stellen an denen man seine Szene zeichnen könnte. Entweder man schreibt sich nen eigenen Loop mittels repeat..until, machts mit einem Timer (ist übrigens eine sehr schlechte Lösung) oder nimmt ganz einfach den OnIdle-Event der Form, der über die ApplicationEvents-Komponente (siehe Komponenten-Tab "Zusätzlich") zur Vefügung gestellt wird. Dieses Ereignis wird eigentlich nicht periodisch aufgerufen, sondern immer nur nach der Abarbeitung einer Fensternachricht. Allerdings kann man dies mit einem kleinen Trick umgehen :
Done := False;
Wenn diese Zeile im OnIdle-Ereignis aufgerufen wird, verschweigt man Windows ganz einfach das die Anwendung die Nachrichtenschleife durchlaufen hat. Als Folge dessen wird diese Routine permanent durchlaufen, und das OpenGL-Programm hat fast die komplette CPU-Zeit zum Zeichnen der Szene. Weitere interessante Zeilen im OnIdle-Ereignis sind folgende :
DrawScene;
SBufferHit := GetSelectBufferHit;
Nachdem in der ersten Zeile die Szene gezeichnet wurde, wird der Variable SBufferHit die Kennung des unter der Maus befindlichen Feldes zugeweisen, so dass man dieses Feld dann in der Zeichneroutine der Karte farblich hervorheben kann.
if MouseLeft then
if Assigned(SelField) then
begin
SelField^.Texture := Form2.TextureCombo.Items[Form2.TextureCombo.ItemIndex];
SelField^.FType := Form2.FieldTypeCombo.ItemIndex;
end;
Dieser Codehappen weist dem ausgewählten Feld (SelField = @TFieldType) bei gedrückter linker Maustaste die im Toolfenster ausgewählte Textur und den ausgewählten Feldtyp zu. Ist kein Feld unter dem Cursor (SelField = nil) dann geschieht nichts, da Assigned() diesen Fall abfängt.
So, geschafft!Das war alles Wichtige zum Quellcode des Editors.Der Rest des Codes ist recht einfach und aufgrund der Dokumentation und ausgiebieger Kommentare im Quelltext dürfte er auch keine Probleme machen...
Abschliessend gibts noch ein paar Worte zur Bedienung des Editors :
- Im 3D-Fenster :
- Cursortasten : Auf dem Spielfeld scrollen
- Mausrad : Rein- bzw. rauszoomen
- Bild rauf/Bild runter : Kamerawinkel verändern
- Linke Maustaste : Feld unter dem Mauscursor mit der Textur und dem in Form2 gewählten Feldtyp belegen
- Rechte Maustaste : Kontextmenü mit Spezialfeldern aufrufen
- +/- : Spielfeld rotieren
Schlusswort
Das wars fürs Erste, und ich hoffe das Tutorial hat euch gefallen. Weiterhin hoffe ich natürlich das ihr meiner Tutorialserie bis zum Ende treu bleibt und mich mit Feedback (am besten direkt ins Forum) überschüttert. Anregungen und Kritik (solange konstruktiv) sehe ich nämlich gerne und motivieren mich auch... Außerdem interessieren mich eure Modifikationen zum Editor und eure Levelkonstrukte, postet also fleissig Neuerungen, Verbesserungen und gebastelte Level ins Forum, und freut euch auf den zweiten Teil der Tutorialreihe!!!
Euer
- Sascha Willems
|
||
Vorhergehendes Tutorial: - |
Nächstes Tutorial: Tutorial_Bomberman2 |
|
Schreibt was ihr zu diesem Tutorial denkt ins Feedbackforum von DelphiGL.com. Lob, Verbesserungsvorschläge, Hinweise und Tutorialwünsche sind stets willkommen. |