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.