Tutorial Pathfinding: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
K (Uebertragung des Tutorials ins Wiki)
 
(Geeignete Welten)
Zeile 60: Zeile 60:
 
     function ErstelleEinheit(const EinheitenTyp:TEinheitenTyp;const NewOwner:TPlayer;const XPos,YPos:Word):Boolean;
 
     function ErstelleEinheit(const EinheitenTyp:TEinheitenTyp;const NewOwner:TPlayer;const XPos,YPos:Word):Boolean;
 
</pascal>
 
</pascal>
 +
 +
Die Größe davon spiegeln die Variablen FWidth und FHeight wieder.
 +
Außerdem sind hier, wie man sehen ebenfalls unsere Einheiten gespeichert.
 +
Die Variable SelectedUnitIndex , gibt an welche davon gerade ausgewählt wurde.
 +
 +
<pascal>
 +
  procedure Draw;
 +
  procedure SetSize(NewWidth,NewHeight:Word);
 +
  procedure Update;
 +
</pascal>
 +
 +
SetSize benutzt man um die Größe zu ändern, dabei werden allerdings alle Einheiten gelöscht.
 +
Ruft man Draw auf so wird ZeichneSpielFeld und ZeichneEinheiten aufgerufen um alles zu zeichenen.
 +
Der regelmäßge Aufruf von Update sorgt dafür das sich überhaupt Bewegung ins Spiel kommt.
 +
 +
<pascal>
 +
  procedure Save(FileName:String);
 +
  procedure Load(FileName:String);
 +
  procedure FloodFill(Player:TPlayer);
 +
  procedure OnSelection(Selected: Integer;Button: TMouseButton);
 +
</pascal>
 +
 +
FloodFill stellt den Besitzer aller Felder auf den Angegebenen Spieler,
 +
und OnSelection wertet Selections-Ereignisse aus.
 +
 +
<pascal>
 +
  TBefehlsTyp=(BT_STAND,BT_MOVE);
 +
  TEinheitenTyp=Word;
 +
 +
  TAnimation=record
 +
    Pos:Word;
 +
    Length:Word;
 +
    Typ:TAnimationType;
 +
  end;
 +
 +
  TBefehl=record
 +
    Case Typ:TBefehlsTyp of
 +
      BT_STAND:();
 +
      BT_MOVE:(X,Y:Word);
 +
  end;
 +
 +
  TEinheit=class
 +
  public
 +
    Ani:TAnimation;
 +
    Befehl:TBefehl;
 +
    X,Y:Word;
 +
    Owner:TPlayer;
 +
    Typ:TEinheitenTyp;
 +
    constructor Create(VomTyp:TEinheitenTyp;NewOwner:TPlayer;XPos,YPos:Word);overload;
 +
    Constructor Create;overload;
 +
    procedure Draw;
 +
    procedure Update;
 +
    function GetRealX:Double;
 +
    function GetRealY:Double;
 +
  end;
 +
</pascal>
 +
 +
Eine Einheit ist normalerweise animiert und führt ürgend eine Animation aus.
 +
Alle Daten wie eine Einheit gerade zu Zeichnen(draw) ist, sind hier verwahrt.
 +
Die Funktionen GetRealX und GetRealY sorgen dafür das die Einheit abhänig von ihrer Animation und groben Position, an der richtigen Stelle gezeichnet werden.
 +
Die Funktion Update einer Einheit ist die Wichtigste da sie regelt welche Aktion und Animation die Einheit als nächstes machen soll.
 +
 +
<pascal>
 +
procedure TEinheit.Update;
 +
begin
 +
  Inc(Ani.Pos);
 +
  if not((Ani.Pos >= Ani.Length) or (Ani.Typ = ANI_STAND))then exit;
 +
</pascal>
 +
 +
Als erstes wird die Animantions Phase 1 weiter gestellt.
 +
Weitergemacht wird nur wenn die Einheit entweder eine Animtion abgeschlossen hat, oder nichts zu tun hat.
 +
 +
<pascal>
 +
  case Befehl.Typ of
 +
    BT_STAND:
 +
    begin
 +
      if (Ani.Pos >= Ani.Length) then Ani.Pos := 0;
 +
      Ani.Typ := ANI_STAND;
 +
      Ani.Length := 1000;
 +
    end;
 +
</pascal>
 +
 +
Wenn die Einheit wirklich stehen soll dann wird höchstens die Animation Position mal zurückgesetzt.
 +
<pascal>
 +
    BT_MOVE:
 +
    begin
 +
      if (X = Befehl.x) and (Y = Befehl.Y) then
 +
      begin //Ziel erreicht
 +
        Befehl.typ := BT_STAND ;
 +
        Ani.Pos := 0;
 +
        Ani.Typ := ANI_STAND;
 +
        Ani.length := 1000;
 +
        exit;
 +
      end;
 +
</pascal>
 +
 +
Wenn die Einheit den Befehl hat sich zu bewegen, aber ihr Ziel schon erreicht hat, dann erhält sie Befehl zu stehen und wir sind fertig.

Version vom 24. November 2005, 18:15 Uhr

Wilkommen zu meinen Tutorial zum Pathfinding.

Geeignete Welten

Diese Methode geht davon aus das sich unsere virtuelle Welt aus lauter Quadraten zusammensetzt. Jedes dieser Felder wird für die Wegberechnung erstmal vereinfacht, in dem davon ausgegangen wird, das ein Feld entweder begehbar ist, oder nicht. (Oder sogar Felder haben die zwar begehbar sind aber gemieden werden solten.)

Nun wird weiter davon ausgegangen, dass eine Einheit von einen Feld nur in 8 (bzw. 4) Richtungen, nämlich in die 8 (4) benachbarten Felder bewegen kann.

Tutorial pathfinding BewegungsRichtungen.png

Von dem nächsten Feld kann sich die Einheit logischerweise weiter in ein nächstes Feld bewegen allerdings immer nur in 8 Richtungen. Wer sich so manches 2D Strategie-Spiel genau anschaut dem fällt sicher auf, dass es dort genauso ist.

Wir wollen zum Beispiel aus einer solchenso Karte,

Tutorial pathfinding Beispiel1-Spiel.png

eine "PathMap" erzeugen,

Tutorial pathfinding Beispiel1-Pathmap.png

um zu wissen wie das Objekt auf die andere Seite kommt.

Wichtige Tastur Befehle des Beispiel Programmes(mit Quelltext):

  • F1: Pathmap vor dem füllen anzeigen
  • F2: Pathmap anzeigen
  • F3: Draufsicht mit Gitter;

Aufbau des Beispiel-Programmes:

Alles wesentliche bis auf das Pathfinding befindt sich in der SpielFeldUnit. Hier haben wir unser Feld definiert.

  TPlayer=(plnone,pl1,pl2,plNeutral);

  TFeld= object
  public
   Owner:TPlayer;
  end;

Und unser gesamtes Spielfeld welche einen Zweifachen Array davon enthält.

  TSpielFeld=class(TPersistent)
  private
    FWidth:Word;
    FHeight:Word;
    procedure ZeichneSpielFeld;
    procedure ZeichneEinheiten;
  public
    Feld: array of array of TFeld;
    Einheit:array of TEinheit;
    SelectedUnitIndex:Integer;
    function ErstelleEinheit(const EinheitenTyp:TEinheitenTyp;const NewOwner:TPlayer;const XPos,YPos:Word):Boolean;

Die Größe davon spiegeln die Variablen FWidth und FHeight wieder. Außerdem sind hier, wie man sehen ebenfalls unsere Einheiten gespeichert. Die Variable SelectedUnitIndex , gibt an welche davon gerade ausgewählt wurde.

   procedure Draw;
   procedure SetSize(NewWidth,NewHeight:Word);
   procedure Update;

SetSize benutzt man um die Größe zu ändern, dabei werden allerdings alle Einheiten gelöscht. Ruft man Draw auf so wird ZeichneSpielFeld und ZeichneEinheiten aufgerufen um alles zu zeichenen. Der regelmäßge Aufruf von Update sorgt dafür das sich überhaupt Bewegung ins Spiel kommt.

   procedure Save(FileName:String);
   procedure Load(FileName:String);
   procedure FloodFill(Player:TPlayer);
   procedure OnSelection(Selected: Integer;Button: TMouseButton);

FloodFill stellt den Besitzer aller Felder auf den Angegebenen Spieler, und OnSelection wertet Selections-Ereignisse aus.

  TBefehlsTyp=(BT_STAND,BT_MOVE);
  TEinheitenTyp=Word;

  TAnimation=record
    Pos:Word;
    Length:Word;
    Typ:TAnimationType;
  end; 

  TBefehl=record
    Case Typ:TBefehlsTyp of
      BT_STAND:();
      BT_MOVE:(X,Y:Word);
  end;

  TEinheit=class
  public
    Ani:TAnimation;
    Befehl:TBefehl;
    X,Y:Word;
    Owner:TPlayer;
    Typ:TEinheitenTyp;
    constructor Create(VomTyp:TEinheitenTyp;NewOwner:TPlayer;XPos,YPos:Word);overload;
    Constructor Create;overload;
    procedure Draw;
    procedure Update;
    function GetRealX:Double;
    function GetRealY:Double;
  end;

Eine Einheit ist normalerweise animiert und führt ürgend eine Animation aus. Alle Daten wie eine Einheit gerade zu Zeichnen(draw) ist, sind hier verwahrt. Die Funktionen GetRealX und GetRealY sorgen dafür das die Einheit abhänig von ihrer Animation und groben Position, an der richtigen Stelle gezeichnet werden. Die Funktion Update einer Einheit ist die Wichtigste da sie regelt welche Aktion und Animation die Einheit als nächstes machen soll.

procedure TEinheit.Update;
begin
  Inc(Ani.Pos);
  if not((Ani.Pos >= Ani.Length) or (Ani.Typ = ANI_STAND))then exit;

Als erstes wird die Animantions Phase 1 weiter gestellt. Weitergemacht wird nur wenn die Einheit entweder eine Animtion abgeschlossen hat, oder nichts zu tun hat.

  case Befehl.Typ of
    BT_STAND:
    begin
      if (Ani.Pos >= Ani.Length) then Ani.Pos := 0;
      Ani.Typ := ANI_STAND;
      Ani.Length := 1000;
    end;

Wenn die Einheit wirklich stehen soll dann wird höchstens die Animation Position mal zurückgesetzt.

    BT_MOVE:
    begin
      if (X = Befehl.x) and (Y = Befehl.Y) then
      begin //Ziel erreicht
        Befehl.typ := BT_STAND ;
        Ani.Pos := 0;
        Ani.Typ := ANI_STAND;
        Ani.length := 1000;
        exit;
      end;

Wenn die Einheit den Befehl hat sich zu bewegen, aber ihr Ziel schon erreicht hat, dann erhält sie Befehl zu stehen und wir sind fertig.