A-Stern Algorithmus für Computerspiel

Neue Frage »

Auf diesen Beitrag antworten »
Tommy1234 A-Stern Algorithmus für Computerspiel

Hallo zusammen, Wink

für ein RPG-Spiel alá Warcraft 2 geht es um den A-Stern-Algorithmus, zu dem ich bereits zahlreiche Tutorials gelesen und leider nur zum Teil verstanden habe.

Es geht mir hier um eine Art Pseudo-Code, den ich dann selbst versuche in JAVA umzusetzen.

Dazu fand ich bereits auch einiges, allerdings konnte ich diesen nicht aussagekräftig genug in JAVA übersetzen.

Meine Bitte an euch ist nun, mir bei der Ausarbeitung eines etwas speziefischeren Pseudo-Codes zu helfen.

Zu meinem Projekt:

In meinem Kachel basierten Spiel, sind alle Kacheln 32x32 Pixel groß.
Die bewegbaren Spielobjekte variieren in ihrer Größe wegen der Grafik.

Zu meiner Pseudo-Code-Frage:

1. Erstellen einer Klasse
2. Implementierung einer OpenList als LinkedList und hinzufügen eines Startknotens
3.Implementierung einer ClosedList als LinkedList, die anfangs leer bleibt.
4.Implementierung eines Startknotens
5.Implementierung eines Zielknotens

Dazu gleich eine Frage: Was ist der Startknoten? - die Kachelnummer als int?
Außerdem möchte ich wissen, ob der AStern-Algorithmus auch für Pixel bzw. x,y Positionen anwendbar ist und man auch via sinus bzw cosinus einen diagonalen Weg, der der Luftlinie(Hypothenuse) näher kommt?

6.Schleife, die prüft ob der Zielknoten erreicht ist oder nicht
bei Erreichung,Schleifenabbruch

7. ....

So nun habe ich Probleme bei der weiteren Vorgehensweise. Wie gesagt, ich habe die Theorie noch nicht ganz verstanden, aber das Ziel ist es, den kürzesten Weg zum Ziel zu finden.

Ich hoffe jemand hat Lust mir zu helfen.

Für Verständnisfragen bin ich offen und bemühe mich bei Bedarf genauer zu erklären.

Gruß Tommy
 
Auf diesen Beitrag antworten »
eulerscheZahl

Ich würde für den Knoten eine eigene Klasse erstellen, so kannst du den Vorgängerknoten bequem ablegen.

Das mit x und y Position ist kein Problem, A* hat eine Entfernungsabschätzung (heißt in wikipedia h(x)).
Kannst du mit Pythagoras machen oder deltaX+deltaY, wenn die Wurzel zu langsam ist.

In der OpenList suchst du den Knoten mit der geringsten Entfernung (currentNode) zum Ziel. Für currentNode wirst du keinen besseren Weg mehr finden. Also kannst du den Knoten in die ClosedList verschieben.

Gleichzeitig gehst du die Nachbarn von currentNode nochmal durch, wenn die nicht schon fix sind, also in closedList:
Entfernungsabschätzung für die Nachbarn (successor): Entfernung von currentNode zum Startknoten (kennst du schon, da currentNode fix) + Abstand von currentNode zu successor. Das entspricht g(x).
Wenn g(x) eine Verbesserung darstellt, musst du das im Vorgänger von successor Speichern.
Dazu kommt dann noch h(x), was die geschätze Entfernung von successor zum Ziel ist.
Die Summe von g(x) und h(x) wird für den Knoten in OpenList gespeichert.
Auf diesen Beitrag antworten »
Tommy1234

Ok, allerdings komm ich bei der Heuristik Funktion nicht weiter wie sieht die konkret aus?

Und wenn ich dich richtig verstanden habe braucht man eine Schleife, um die Knoten rund um den currentNode zu prüfen oder nicht?

Und noch eine Verständnisfrage:

Ich schreib mir also eine Funktion in dem Stil:

findePfad(Node startNode,Node endNode);

und brauch diese dann nur noch in meiner Spielupdatefunktion aufzurufen oder
brauche ich noch weitere Funktionen zum Aufrufen?

Dann noch etwas:

Empfiehlt es sich die Node Klasse abstract zu machen oder ist das hier egal?
Auf diesen Beitrag antworten »
eulerscheZahl

Die Heuristik hängt davon ab, wie du dich bewegen kannst.
Du könntest max(deltaX, deltaY) als Ansatz wählen und mal schauen, wie du damit hinkommst.

Was du vom Spielfeld brauchst ist ein bool[][], das dir sagt, ob ein Feld passierbar ist.

Für abstract kann ich keinen Grund sehen. Da müsstest du ja erst eine abgeleitete Klasse erstellen, um sie instanziieren zu können.
 
Auf diesen Beitrag antworten »
Tommy1234

Hallo,

ich habe bisher folgenden Code geschrieben:

(sry ich nehm hier bewußt keine Code Tags):

class Node{

int x,y;
Node parent;
double g,h,f;

}

In einer weiteren Klasse möchte ich dann in einer Methode, der der startKnoten, der zielKnoten und Graphics g übergeben wird, bereits durch die Methode erstmal den gefundenen Weg per farbiger Rechtecke ausgeben momentan noch ohne Hindernis, indem ich die Methode dann in der paintComponent-Methode aufrufe.

also:

public void findePfad(Node startKnoten,Node zielKnoten, Graphics g){

startKnoten.parent = null;
openList.add(startKnoten);

while(!openList.isEmpty){

if(testKnoten == zielKnoten){
g.fillRect(testKnoten.x,testKnoten.y,size,size);
break;
}

Mehr hab ich noch nicht. verwirrt

Das Problem ist, das ich keine bessere Möglichkeit zur Bestimmung der NachbarKnoten finde als via Hardcoding in dem Stil :

x+32;y; x+32;y+32; usw. für die acht Nachbarn finde.

Außerdem verstehe ich nicht, wie man mithilfe der Hypothenuse auf die Kosten kommt, denn mit ein wenig Geometrie kommt man darauf, das für vertikale und horizontale Knoten gilt kosten = 1, und diagonal kosten = wurzel 2 ??????

Dabei kann ich irgendwie keinen Zusammenhang finden.

Also um nochmal zusammenzufassen:

Wie finde ich den NachbarKnoten mit den geringsten Kosten?
Wie berechne ich die Kosten für die NachbarKnoten?

Wäre für ein Code-Beispiel dankbar,


Gruß Tommy
Auf diesen Beitrag antworten »
eulerscheZahl

code:
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:
package infoboard;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;

public class Main
{
	public static void main(String[] args)
	{
		int n = 20;
		boolean[][] grid = new boolean[2 * n][n];
		for (int i = 3; i <= 15; i++) {
			grid[i][15] = true;
			grid[15][i] = true;
		}
		ArrayList<Field> path = AStar(3, 3, 30, 15, grid, 2 * n, n);
		if (path != null) {
			for (Field f : path)
				System.out.println(f);
		}
	}

	public static ArrayList<Field> AStar(int xFrom, int yFrom, int xTo, int yTo, boolean[][] grid, int w, int h) {
		//create Fields
		Field[][] fields = new Field[w][h];
		for (int x = 0; x < w; x++) {
			for (int y = 0; y < h; y++) {
				fields[x][y] = new Field(x, y, grid[x][y]);
			}
		}
		//add neighbors
		for (int x = 0; x < w; x++) {
			for (int y = 0; y < h; y++) {
				for (int dx = -1; dx <= 1; dx++) {
					for (int dy = -1; dy <= 1; dy++) {
						if (x + dx < 0 || x + dx >= w || y + dy < 0 || y + dy >= h)
							continue;
						fields[x][y].addNeighbor(fields[x + dx][y + dy]);
					}
				}
			}
		}

		//init
		HashSet<Field> openList = new HashSet<Field>(); //HashSet nicht ideal
		HashSet<Field> closedList = new HashSet<Field>();
		openList.add(fields[xFrom][yFrom]);
		Field target = fields[xTo][yTo];

		while (!openList.isEmpty()) {
			//find min in openList
			Field current = null;
			for (Field f : openList) {
				if (current == null || current.getF() > f.getF()) {
					current = f;
				}
			}
			openList.remove(current);
			if (current == target) {
				ArrayList<Field> result = new ArrayList<Field>();
				Field tmp = target;
				while (tmp != null) {
					result.add(0, tmp);
					tmp = tmp.getPredecessor();
				}
				return result;
			}
			closedList.add(current);
			expandNode(current, target, openList, closedList);
		}
		return null;
	}

	private static void expandNode(Field current, Field target, HashSet<Field> openList, HashSet<Field> closedList) {
		HashMap<Field, Double> successors = current.getNeighbors();
		Iterator it = successors.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<Field, Double> pair = (Map.Entry<Field, Double>) it.next();
			Field successor = pair.getKey();
			if (closedList.contains(successor))
				continue;
			double tentative_g = current.getG() + pair.getValue();
			if (openList.contains(successor) && tentative_g >= successor.getG())
				continue;
			successor.setPredecessor(current);
			successor.setG(tentative_g);
			double f = tentative_g + successor.calcH(target);
			successor.setF(f);
			openList.add(successor);
		}
	}
}


code:
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:
package infoboard;

import java.util.HashMap;

public class Field {

	private int x;
	private int y;
	private boolean blocked;
	private HashMap<Field, Double> neighbors = new HashMap<Field, Double>();
	private double f;
	private double g;
	private Field predecessor;

	public Field(int x, int y, boolean blocked) {
		this.x = x;
		this.y = y;
		this.blocked = blocked;
	}

	public void addNeighbor(Field neighbor) {
		if (!neighbor.blocked && this != neighbor) {
			neighbors.put(neighbor, Math.sqrt(
					(this.x - neighbor.x) * (this.x - neighbor.x) +
							(this.y - neighbor.y) * (this.y - neighbor.y)));
		}
	}

	public HashMap<Field, Double> getNeighbors() {
		return neighbors;
	}

	public double getF() {
		return f;
	}

	public void setF(double f) {
		this.f = f;
	}

	public double getG() {
		return g;
	}

	public void setG(double g) {
		this.g = g;
	}

	public Field getPredecessor() {
		return predecessor;
	}

	public void setPredecessor(Field predecessor) {
		this.predecessor = predecessor;
	}

	public double calcH(Field target) {
		int dx = Math.abs(this.x - target.x);
		int dy = Math.abs(this.y - target.y);
		return (Math.sqrt(2) - 1) * Math.min(dx, dy) + Math.max(dx, dy);
	}

	@Override
	public String toString() {
		return x + "/" + y;
	}
}
Auf diesen Beitrag antworten »
Tommy1234

Hallo,

vielen Dank für deinen Code und deine Mühe.

Komme jetzt zurecht.

Nochmals Danke.

Gruß Tommy
 
Neue Frage »
Antworten »


Verwandte Themen

Die Beliebtesten »
Die Größten »
Die Neuesten »