Das folgende Tutorial ist direkt im Sourcecode eingebettet. Am einfachsten ist es, sich die Kommentare (grün) durchzulesen, und sich dann über die entsprechenden Codeabschnitte Gedanken zu machen. Dies kann online geschehen - besser ist es aber, einfach den vollständigen Sourcecode nach BaseGraph Pascal zu laden, dann kann man auch gleich ein wenig experimentieren.
Der Sourcecode kann hier heruntergeladen werden - einfach entpacken, die .src Datei in BGP öffnen und starten.
Fragen können im BaseGraph Pascal Forum gepostet werden.
Wer lieber gleich das Spiel ausprobieren möchte, kann dieses hier herunterladen.
Viel Spass beim Programmieren
program AstroMine;
(*
AstroMine ist ein kleines Spiel, das die Fähigkeiten von BaseGraph Pascal demonstriert.
Zum Code: das Programm wurde dahingehend erstellt, um auch ein wenig die Erstellung
eines kleinen Projektes zu erklären. Der verwendete Programmierstil ist hingegen
nicht ganz optimal (z.B. wird die Tatsache ausgenutzt, dass in BaseGraph Pascal
Variablen *garantiert* mit 0 vorinitialisiert werden.
Die BaseGraph Pascal IDE wurde extra dafür ausgelegt, um funktionale Blöcke in
eigenen Seiten unterbringen zu können - im Fall von AstroMine enthält jeder Sourceblock
genau eine Routine, die ebenso heißt, wie der Block. Auf diese Weise sind sowohl
Übersichtlichkeit als auch schneller Zugriff auf die einzelnen Codefragmente
garantiert.
*)
const
BackGroundSpeed = 0.125;
HorizSpeed = 1.5;
VertSpeed = 3;
EnemySpeed = 1.0;
EnemyRotSpeed = 60.0;
MaxEnemies = 10;
(*
Die oberen Werte sind eh selbsterklärend
*)
type
TGameState = (PlayerStart, Standard, Shoot, Win, Lose);
(*
Die verschiedenen Stati des Gamemanagers, bei einem komplexeren Projekt würde
das Programm wahrscheinlich in mehreren Managerroutinen ablaufen - für das
einface AstroMine tut es auch eine einzige.
*)
var
Background: TTexture2D;
Ship: TBody;
Enemy: array[0..MaxEnemies-1] of TBody;
EnemyRS: array[0..MaxEnemies-1] of TRenderSettings;
EnemyExplodeTime: array[0..MaxEnemies-1] of Double;
EnemyExplosionColor: array[0..MaxEnemies-1] of TColor3f;
DefaultRS: TRenderSettings;
Red, Yellow, Blue, Green: TMaterial;
TexAnim: float;
Points: integer;
GameState: TGameState;
SpawnTimerValue: double;
Startup, Missile, Laugh: integer;
(*
Ich sehe keinen Grund für kleinere Projekte keine globalen Variablen zu verwenden
- allerdings sollte man damit nicht übertreibem, um den Überblick nicht zu verlieren.
BackGround: die Hintergrundtextur
Enemy: die Feindobjekte
EnemyRS: explodierende Feindobjekte werden anders gezeichnet, die OpenGL States
dazu werden in EnemyRS gekapselt
EnemyExplodeTime : die "Explosionszeiten" da ja auch mehrere Feindobjekte gleichzeitig
explodieren können
EnemyExplosionColor : die Explosionsfarben, die zur Laufzeit zufällig generiert werden
DefaultRS Rendereinstellungen für die Rakete
Red, Yellow, Blue, Green: Materialdefinitionen für die entsprechenden Farben
TexAnim: Fortschritt der Scrollanimation der Hintergrundtextur
Points: erreichte Punkte
GameState: der aktuelle Status des Spieles
SpawnTimerValue: Zeitmessung bis zum nächsten "Enemyspawn"
Startup, Missile, Laugh: Soundhandles für die entsprechenden Sounds
*)
{$INCLUDE ShowMessage} // gibt einen Text im 3D-Fenster aus
{$INCLUDE MakeShip} // erstellt das Spielerschiff
{$INCLUDE MakeEnemy} // erstellt eine Feindmine
{$INCLUDE DrawBackground} // zeichnet und animiert den Hintergrund und Score
{$INCLUDE SteerShipLR} // steuert Spielerschiff horizontal
{$INCLUDE MakeRenderSettings} // erstellt Rendereinstellungen für "normale" Objekte und Explosionen
{$INCLUDE LoseGame} // beendet das Spiel nachdem verloren wurde und startet es auf Wunsch neu
{$INCLUDE SpawnEnemy} // spawnt ein Feindschiff in Abhängigkeit von Punktestand und verstrichener Zeit
{$INCLUDE ManageEnemies} // verwaltet die Feinde und deren Interaktion mit dem Spieler
procedure ManageGame;
var v: TVec;
begin
case GameState of
PlayerStart: begin
ShowMessage(-0.5, 0, '<SPACE> drücken, um die Erde vor' + #13 + 'dem sicheren Untergang zu bewahren');
Ship.SetCoordAngle(-90,0,0);
Ship.SetPos(0,-0.8,-2);
if KeyDown(VK_SPACE) then begin
SoundPlayMedia;
StartTimer;
SpawnTimerValue := QueryTimer;
GameState := Standard;
end;
end;
Standard: begin
SteerShipLR(HorizSpeed*2);
ManageEnemies;
if KeyDown(VK_UP) then
GameState := Shoot;
end;
Shoot: begin
SteerShipLR(HorizSpeed);
Ship.Move(0, LastFrameTime*VertSpeed, 0);
ManageEnemies;
v := Ship.GetPosv;
if v.Y > 0.9 then begin
v.y := -0.8;
Ship.SetPosv(v);
GameState := Standard;
end;
end;
end;
end;
(*
Der Gamemanager - viel Interessantes gibt es dazu nicht zu sagen - so komplex
ist AstroMine ja auch nicht
*)
var i: integer;
begin
ChangeDisplaySettings(800,600,0,0);
FormSetCursor(crNone);
FontLoad(RessourceFile('font.bmp'), RessourceFile('font.fci'));
glEnable(GL_CULL_FACE);
(*
Bildschirmauflösung setzen, Cursor verschwinden lassen, Font laden, OpenGL
Rückseitenentfernung aktivieren.
*)
Startup := SoundGetHandle(RessourceFile('attention.wav'));
Missile := SoundGetHandle(RessourceFile('missile.wav'));
Laugh := SoundGetHandle(RessourceFile('evil_laugh.wav'));
SoundLoadMedia(RessourceFile('midi'));
SoundSetVolume(-500);
(*
Kurz zum Sound: über SoundHandles können beliebig viele Sounds gleichzeitig
abgespielt werden, wobei ein Handle auch mehrfach gespielt werden kann (es ist
also z.B. nur notwendig eine Explosion zu laden).
Über Sound...Media Routinen können DirectMedia Files gespielt werden - soll heißen,
die schlucken alles, was auch der MediaPlayer versteht.
Sound...Primary Routinen können einen Wave- oder MidiStream spielen, der wie
Sound Handles auch 3D positionierbar ist.
*)
Red := TMaterial.Create;
Red.SetDiffuse(1,0,0,0,True);
Yellow := TMaterial.Create;
Yellow.SetDiffuse(1,1,0,0,True);
Blue := TMaterial.Create;
Blue.SetDiffuse(0,1,0,0,True);
Green := TMaterial.Create;
Green.SetDiffuse(0,0,1,0,True);
(*
Materiale erstellen und definieren
*)
Background := NewTexture2D(RessourceFile('clouds.bmp'), GL_LUMINANCE);
DefaultRS := NewRenderSettings;
MakeStandardRS(DefaultRS);
Ship := MakeShip;
for i:=0 to MaxEnemies-1 do begin
EnemyRS[i] := NewRenderSettings;
MakeStandardRS(EnemyRS[i]);
Enemy[i] := MakeEnemy(EnemyRS[i]);
end;
ShowObj(Ship);
SoundPlay(Startup);
GameState := PlayerStart;
(*
Hintergrundtextur laden, Rendereinstellungen für den Spieler und sämtliche
Feindminen generieren und vorinitialisieren. Status des Gamemanagers setzen.
Um die Eigenschaften der Feindschiffe (Alphawert, Farbe) zu ändern, definiert
AstroMine einfach für jedes Feindschiff Rendereinstellungen und ändert diese, falls
es notwendig sein sollte - auf diesem Weg ist es sehr einfach, BaseGraph die
Arbeit machen zu lassen, ohne die BaseGraph CallBackroutinen nutzen zu müssen
(die in dieser Version von BGP (1.4.04) ohnehin noch nicht integriert sind)
*)
repeat
glDisable(GL_COLOR_MATERIAL);
DrawBackground;
bgTransform;
bgDrawObjects;
ManageGame;
SwapBuffers;
until KeyPressed(VK_ESCAPE) or (GameState=Win) or (GameState=Lose);
(*
Die Hauptrenderschleife des Programms, die nichts anderes macht, als alle Objekte
darzustellen und zwischendurch den Gamemanager aufzurufen und neue Feinde zu
generieren.
*)
RemoveTexture(Background);
RemoveMaterial(Red);
RemoveMaterial(Blue);
RemoveMaterial(Yellow);
Ship.Free;
for i:=0 to MaxEnemies-1 do begin
Enemy[i].Free;
RemoveRenderSettings(EnemyRS[i]);
end;
RemoveRenderSettings(DefaultRS);
(*
Nach getaner Arbeit ist es meist eine gute Idee, erstellte Objekte wieder
freizugeben. BaseGraph Pascal macht dies nicht automatisch, Faulpelze können
aber die Option Projekt>Projektdaten entfernen verwenden.
Für die erstellten Executables ist es natürlich egal, da Ressourcen beim
Beenden des Programms auf jeden Fall freigegeben werden - allerdings
schadet es nichts, sich gleich einen halbwegs sauberene Programmierstil
zuzulegen
*)
end.