Entwickler-Ecke

Multimedia / Grafik - Brauche Hilfe für KI


Sequa - Mi 08.10.03 16:54
Titel: Brauche Hilfe für KI
Hi,
ich bin im moment dabei eine Art TowerDefense wie das für Warcraft3 zu programmieren. Sinn dieses Spiels ist es mit seinen Türmen, die mann bauen kann, zu verhindern, dass die Monster vom Startpunkt aus das Ziel erreichen. Um diese effizienter zu vernichten baut man die Türme in bestimmten Systemen, zum Beispiel Reihensystem damit die Monster möglichst oft wieder bei den Türmen vorbeikommen.
Achso: Die Monster werden von den Türmen abgeschossen, desswegen ist es auch so sinnvoll öfters an den Türmen vorbeizukommen. Sollte der Weg zum Ziel komplett mit Türmen zugebaut sein, so werden diese vom Monster zerstört.

Nun, bisher habe ich nur ein Notfallsystem entwickelt. Die Monster weichen den Türmen auf sympelste Weise aus, nämlcih wenn sie auf einen Turm stoßen versuchen sie erst zu einen Richtung zu laufen, wenn diese nicht geht zur anderen und so weiter.
So ist es unheimlich leicht das Monster auszutricksen und es in eine Endlosschleife zu verwickeln. Es läuft also nurnoch hin und her.

Was ich brauche ist eine Möglichkeit den kürzesten Weg direkt beim "Spawn" des monsters zu berechnen. Sollte der Weg druch ein neues Hindernis, einen Turm, abgeschnitten werden so wird erneut von der Position der kürzeste Weg bis zum Ziel berechnet.

Bisher bin ich soweit, dass ich eine Karte in DelphiX darstelle (22x50Felder) und habe als Startpunkt das Feld (1;1) festgelegt und das Ziel ist es die unterste Reihe (Y=50) zu erreichen. Eine Wegberechnung gibt es nicht und so versucht das Monster ersteinmal immer nur nach unten zu laufen und den Türmen auszuweichen. Dabei nimmt es oft sehr lange und umständliche Wege.

Ich habe bereits einige Tutorials zur KI gelesen, wurde aber aus den C++ Beispielen nicht recht schlau.

Könnt ihr mir bei eienr solchen Berechnungsprozedur helfen?

Hier ist meine bisherige "AI", die nur den Türmen ausweicht und immer in Endlosschleifen rennt wenn sie in Sackgassen kommt...:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
procedure TGameForm.CalculateAI(startx,starty:integer);
var
AIposx,
AIposy:integer;
AIfinish:boolean;
AIusedsteps:integer;
AImaxsteps:integer;
AIcanceled:boolean;
AImovedown,
AImoveup,
AImoveright,
AImoveleft:boolean;
AImoved:boolean;
AIdirection : string;
begin

AIposx := startx;
AIposy := starty;
AIfinish := False;
AIcanceled := False;
AIusedsteps := 0;
AImaxsteps := 999;

AImovedown := True;
AImoveup := False;
AImoveright := False;
AImoveleft := False;
AIdirection := 'right';
AImoved := False;
Memo1.Lines.Add('Calculating AI from '+inttostr(startx)+'/'+inttostr(starty)+'.');

while AIfinish = False do begin

  //AI didnt move yet
  AImoved := False;

  //Counter for the AI Steps
  AIusedsteps := AIusedsteps+1;

  //Draw the AI field on starting pos and any others
  Map[AIposx,AIposy].Draw := True;

  //First Try: Move DOWN
  if (Map[AIposx, AIposy+1].Blocked = False) and (AIposy<mapheight+1) then begin
    if AIdirection <> 'up' then begin
    AIposy := AIposy+1;
    AImoved := True;
    end
    else
    AImovedown := False;
  end
  else
  AImovedown := False;

  //If AI cannot move down
  if AImovedown = False then begin

    //Move RIGHT
    if (AImoved = False) and (AIdirection = 'right') then
    if (Map[AIposx+1, AIposy].Blocked = False) and (AIposx<mapwidth+1) then begin
      AIposx := AIposx+1;
      AImoved := True;
    end
    else begin
    AImoveright := False;
    AIdirection := 'left';
    end;

    //Move LEFT
    if (AImoved = False) and (AIdirection = 'left') then
    if (Map[AIposx-1, AIposy].Blocked = False) and (AIposx>0) then begin
      AIposx := AIposx-1;
      AImoved := True;
    end
    else begin
    AImoveleft := False;
    AIdirection := 'up';
    end;

    //Move UP
    if (AImoved = False) and (AIdirection = 'up') then
    if (Map[AIposx, AIposy-1].Blocked = False) then begin
      AIposy := AIposy-1;
      AImoved := True;
      if (Map[AIposx-1, AIposy].Blocked = False) and (AIposx>0) then
        AIdirection := 'left';
      if (Map[AIposx+1, AIposy].Blocked = False) and (AIposx<mapwidth+1) then
        AIdirection := 'right';
    end
    else
    AImoveup := False;

  end;

  //AI reaches finish (mapheight/last field of the map)
  if AIposy >= mapheight then
  AIfinish := True;

  //AI failed to reach finish after AImaxsteps steps. CANCEL
  if AIusedsteps >= AImaxsteps then begin
  AIfinish := True;
  AIcanceled := True;
  end;

end;
if AIcanceled = False then
Memo1.Lines.Add('Finished AI in '+inttostr(AIusedsteps)+' steps.')
else
Memo1.Lines.Add('Canceled AI after '+inttostr(AIusedsteps)+' steps.');
end;


Anonymous - Mi 08.10.03 17:03

irgendwo da wirst du was finden:

Suche bei Google A* DELPHI
Suche bei Google ASTAR DELPHI


scientificus - Do 09.10.03 17:35

ich kann dir sagen, wie ich es bei einem programm gemacht habe. das war zwar einiges an quelltext, aber es hat funktioniert.

zunächst würde ich einen record deklarieren, der für jedes feld der karte den kürzesten weg vom startpunkt dorthin speichern soll:
weg : array[1..i,1..j] of record
anzahl : integer;
feldx,feldy : array[1..anzahl];
end; //oder so ähnlich
"anzahl" ist die Anzahl der Felder, die bei diesem Weg zurückgelegt werden, "feldx" und "feldy" speichern die Koordinaten der Felder, die benutzt wurden. z.B. weg[4,5].feldx[3] ist die x-koordinate des 3. Feldes, das auf dem Weg (von 1,1) nach 4,5 benutzt wird.
dieser record sollte zuerst komplett mit 0 belegt werden.

So, nun dazu, wie das record "weg" berechnet werden kann.
Man klappert in einer for-Schleife alle Felder hintereinander ab und überprüft bei jedem Feld ("aktuellesfeld",z.B. 1,1) folgendes:
Wenn die weg["umfeld"].anzahl eines benachbarten Feldes ("umfeld") kleiner ist als die eigene (oder wenn weg["aktuellesfeld"].anzahl=0) , überträgt man den wert weg["umfeld"].anzahl + 1 auf weg["aktuellesfeld"].anzahl sowie die koordinaten:
for i := 1 to weg["umfeld"].anzahl do
weg["aktuellesfeld"].feldx[i] := weg["umfeld"].feldx[i];
dasselbe mit "feldy".
jetzt speichert man in weg["aktuellesfeld"].feldx[weg["aktuellesfeld"].anzahl] die x-koordinate von "umfeld", mit "feldy" dasselbe.

Wenn man diesen Vorgang (die for-Schleife) oft genug wiederholt, liegt im record die Information des kürzesten Wegs zu jedem beliebigen Feld der Karte.

Um die Rechenzeit zu verkürzen, kann man davor noch einen array deklarieren, der alle Felder der Karte in einer sinnvollen Weise abspeichert, also spiralenförmig vom Startpunkt nach außen gehend. Das war bei meinem programm schwer zu realisieren, da ich hexagonale Felder benutzt habe, doch mit quadratischen Feldern müsste das einfacher gehen.
Am Ende sollte in diesem array z.B. stehen (mit 1,1 als Startfeld):
felderx[2,2,1,3,3,3,2,1,4,4,4,4,3,2,1,5,5,5,5,5,4,3,2,1,6,6,6,6,6,6...]
feldery[1,2,2,1,2,3,3,3,1,2,3,4,4,4,4,1,2,3,4,5,5,5,5,5,1,2,3,4,5,6...]
Wenn in der oben beschriebenen for-Schleife diese Felder abgeklappert werden, sollte das ganze schneller gehen.
Je häufiger du die for-Schleife laufen lässt, desto genauer wird die Ermittlung des kürzesten Weges. Falls also der kürzest mögliche Weg aus 60 Feldern besteht und erkannt werden soll, muss die for-Schleife 60 mal laufen gelassen werden. Im Normalfall würde ich sagen, dass 10 mal ausreicht.

Ich hoffe, es ist verständlicher als es sich anhört... :roll:
Bin für Rückfragen offen.


Gandalfus - Fr 10.10.03 21:57

http://www.policyalmanac.org/games/aStarTutorial.htm
ein gutes tutorial


Udontknow - Mo 13.10.03 14:17

Hi!

Hier gabs auch schon ein Tut mit Beispielprogramm und Quellen:

http://www.delphi-forum.de/viewtopic.php?t=4802

Cu,
Udontknow