Hallo ThomasL u.a
habe mal wieder ein Problen:
versuche die Ausführungen von Fernando Bevilaqcua Understanding Steering Behavior: Path Following in pygame und python umzusetzen.
Ein Vehikel folgt den Punkten eines Pfades.
Ein Pfad ist zusammengesetzt aus Linien und Punkten. Jeder Punkt des Pfades wird als target angesehen, sodass der seek-Algorithmus von Reynolds verwendet werden kann. Wenn ein bestimmter Abstand zu einem Punkt erreicht ist, wird der nächste Punkt gesucht, der Listenindex um 1 erhöht.
Problem: Wenn der letzte Punkt des Pfades gesucht wird, soll das Vehikel dort zur Ruhe kommen, der seek_with_approach - Algorithmus verwendet werden.
Frage: wie kann ich das machen, ohne eine Fehlermeldung wegen des Listenindex zu erhalten.
Für Tips wäre ich dankbar.
Code:
import pygame
from random import randint, uniform
import math
import copy
from vec2d import vec2d
WIDTH = 1000
HEIGHT = 640
FPS = 60
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
DARKGRAY = (40, 40, 40)
CYAN = (0, 255, 255)
MAGENTA = (255, 0, 255)
BG_COLOR = (122, 150, 134)
#BG_COLOR = (150, 150, 80)
MAX_SPEED = 2
MAX_FORCE = 0.05
APPROACH_RADIUS = 50
AGENT_FILENAME = '../images/dreieck0.png'
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.init()
class Vehicle(pygame.sprite.Sprite):
def __init__(self, screen, img_filename, init_position):
pygame.sprite.Sprite.__init__(self)
self.screen = screen
self.screen_rect = screen.get_rect()
self.base_image = pygame.image.load(img_filename).convert_alpha()
self.image = self.base_image
self.image_w, self.image_h = self.image.get_size()
self.location = vec2d(init_position)
self.velocity = vec2d(MAX_SPEED, 0)
self.direction = vec2d(0,0)
self.acceleration = vec2d(MAX_SPEED, 0)
self.desired = vec2d(0, 0)
self.current_node = 0 # Listenindex der Liste path.nodes
def apply_force(self, force):
self.acceleration += force
def follow_path(self, path):
target = vec2d(0,0)
if path != None:
path.nodes = path.get_nodes()
target = path.nodes[self.current_node]
distance = self.location.get_distance(target)
if distance <= 10:
self.current_node += 1
if self.current_node >= len(path.nodes):
self.seek_with_approach(target)
self.current_node = len(path.nodes) - 1
self.seek(target)
def seek(self, target):
self.desired = (target - self.location).normalized()* MAX_SPEED
steer = (self.desired - self.velocity)
if steer.get_length() > MAX_FORCE:
steer *= (MAX_FORCE)
self.apply_force(steer)
def seek_with_approach(self, target):
""" wenn sich Vehikel innerhalb der Entfernung
APPROACH_RADIUS befindet bremse ab """
self.desired = (target - self.location)
dist = self.desired.get_length()
self.desired.normalized()
if dist < APPROACH_RADIUS:
self.desired *= dist / APPROACH_RADIUS * MAX_SPEED
else:
self.desired *= MAX_SPEED
steer = (self.desired - self.velocity)
if steer.get_length() > MAX_FORCE:
steer *= MAX_FORCE
self.apply_force(steer)
def update(self):
if self.velocity.get_length() > 0.00001:
self.direction = self.velocity.normalized()
# Make the agent point in the correct direction.
# Since our direction vector is in screen coordinates
# (i.e. right bottom is 1, 1), and rotate() rotates
# counter-clockwise, the angle must be inverted to
# work correctly.
# self.image = pygame.transform.rotate(self.base_image,
# -180 * math.atan2(self.direction.y , self.direction.x)
# / math.pi)
self.image = pygame.transform.rotate(self.base_image, -self.direction.angle)
self.image_w, self.image_h = self.image.get_size()
# equations of motion
self.velocity += self.acceleration
# begrenze velocity auf MAX_SPEED
if self.velocity.get_length() > MAX_SPEED:
self.velocity * MAX_SPEED
self.location += self.velocity
self.acceleration *= 0.0 # resette nach jedem update
self.rect = self.image.get_rect()
self.rect.topleft = (self.location.x, self.location.y)
# Zeichne Sprite
def blitme(self):
draw_pos = self.rect.move(
[-self.image_w / 2, -self.image_h / 2])
self.screen.blit(self.image, draw_pos)
class Path(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.screen = screen
self.screen_rect = screen.get_rect()
self.nodes = []
# Add a point to the path
def add_node(self, x, y):
node = vec2d(x, y)
self.nodes.append(node);
def get_start(self):
return self.nodes[0]
def get_end(self):
return self.nodes[-1]
def get_nodes(self):
return self.nodes
def display(self):
node_list = []
for node in self.nodes:
node_list.append(node)
pygame.draw.lines(screen, BLACK, False, node_list, 3)
# generiere Pfad aus Liniensegmenten
def new_path():
path = Path()
path.add_node(20, HEIGHT/2)
path.add_node(randint(0, WIDTH/2), randint(20, HEIGHT))
path.add_node(randint(WIDTH/2, WIDTH), randint(20, HEIGHT))
path.add_node(WIDTH-20, HEIGHT/2)
return path
###### main ######
def main() :
pygame.init()
clock = pygame.time.Clock()
# Generiere Pfad
path = new_path()
# einen Agent erzeugen
agent = pygame.image.load(AGENT_FILENAME).convert_alpha()
agent = Vehicle(screen, AGENT_FILENAME,
(WIDTH/2, HEIGHT/3))
running = True
while running:
clock.tick(FPS)
screen.fill(BG_COLOR)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
path.display()
agent.follow_path(path)
agent.update()
agent.blitme()
pygame.display.set_caption('Path Following')
pygame.display.flip()
if __name__ == "__main__":
main()
Steering path following
Du machst doch jetzt schon ein paar Monate rum mit Python. Dabei ist dir doch sicher schon aufgefallen, dass Einrueckungen in Python relevant sind. Sonst kann der Interpreter den Code nicht auswerten, weil er nicht weiss, was zu was gehoert.
Und uns geht das genauso. Ich habe keine Ahnung, was von deinem Code wo wie eingerueckt ist. Und darum kann ich den nicht verstehen.
Es gibt hier im Editor den "</>"-Button, und damit kannst du deinen Code in Code-Tags setzen. Dann ist der fuer uns auch lesbar.
Und uns geht das genauso. Ich habe keine Ahnung, was von deinem Code wo wie eingerueckt ist. Und darum kann ich den nicht verstehen.
Es gibt hier im Editor den "</>"-Button, und damit kannst du deinen Code in Code-Tags setzen. Dann ist der fuer uns auch lesbar.
ja, das wäre nett und ich schau mir deinen Code morgen an. Ich sag jetzt gute Nacht.
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
_deets_ ,
ich habe keine Ahnung wie das geht ?
ich habe keine Ahnung wie das geht
Code: Alles auswählen
danke _deets_ für deine Geduld,
Hier also nochmal der Code:
Hier also nochmal der Code:
Code: Alles auswählen
import pygame
from random import randint, uniform
import math
import copy
from vec2d import vec2d
WIDTH = 1000
HEIGHT = 640
FPS = 60
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
DARKGRAY = (40, 40, 40)
CYAN = (0, 255, 255)
MAGENTA = (255, 0, 255)
BG_COLOR = (122, 150, 134)
#BG_COLOR = (150, 150, 80)
MAX_SPEED = 2
MAX_FORCE = 0.05
APPROACH_RADIUS = 50
AGENT_FILENAME = '../images/dreieck0.png'
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.init()
class Vehicle(pygame.sprite.Sprite):
def __init__(self, screen, img_filename, init_position):
pygame.sprite.Sprite.__init__(self)
self.screen = screen
self.screen_rect = screen.get_rect()
self.base_image = pygame.image.load(img_filename).convert_alpha()
self.image = self.base_image
self.image_w, self.image_h = self.image.get_size()
self.location = vec2d(init_position)
self.velocity = vec2d(MAX_SPEED, 0)
self.direction = vec2d(0,0)
self.acceleration = vec2d(MAX_SPEED, 0)
self.desired = vec2d(0, 0)
self.current_node = 0 # Listenindex der Liste path.nodes
def apply_force(self, force):
self.acceleration += force
def follow_path(self, path):
target = vec2d(0,0)
if path != None:
path.nodes = path.get_nodes()
target = path.nodes[self.current_node]
distance = self.location.get_distance(target)
if distance <= 10:
self.current_node += 1
if self.current_node >= len(path.nodes):
self.seek_with_approach(target)
self.current_node = len(path.nodes) - 1
self.seek(target)
def seek(self, target):
self.desired = (target - self.location).normalized()* MAX_SPEED
steer = (self.desired - self.velocity)
if steer.get_length() > MAX_FORCE:
steer *= (MAX_FORCE)
self.apply_force(steer)
def seek_with_approach(self, target):
""" wenn sich Vehikel innerhalb der Entfernung
APPROACH_RADIUS befindet bremse ab """
self.desired = (target - self.location)
dist = self.desired.get_length()
self.desired.normalized()
if dist < APPROACH_RADIUS:
self.desired *= dist / APPROACH_RADIUS * MAX_SPEED
else:
self.desired *= MAX_SPEED
steer = (self.desired - self.velocity)
if steer.get_length() > MAX_FORCE:
steer *= MAX_FORCE
self.apply_force(steer)
def update(self):
if self.velocity.get_length() > 0.00001:
self.direction = self.velocity.normalized()
# Make the agent point in the correct direction.
# Since our direction vector is in screen coordinates
# (i.e. right bottom is 1, 1), and rotate() rotates
# counter-clockwise, the angle must be inverted to
# work correctly.
# self.image = pygame.transform.rotate(self.base_image,
# -180 * math.atan2(self.direction.y , self.direction.x)
# / math.pi)
self.image = pygame.transform.rotate(self.base_image, -self.direction.angle)
self.image_w, self.image_h = self.image.get_size()
# equations of motion
self.velocity += self.acceleration
# begrenze velocity auf MAX_SPEED
if self.velocity.get_length() > MAX_SPEED:
self.velocity * MAX_SPEED
self.location += self.velocity
self.acceleration *= 0.0 # resette nach jedem update
self.rect = self.image.get_rect()
self.rect.topleft = (self.location.x, self.location.y)
# Zeichne Sprite
def blitme(self):
draw_pos = self.rect.move(
[-self.image_w / 2, -self.image_h / 2])
self.screen.blit(self.image, draw_pos)
class Path(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.screen = screen
self.screen_rect = screen.get_rect()
self.nodes = []
# Add a point to the path
def add_node(self, x, y):
node = vec2d(x, y)
self.nodes.append(node);
def get_start(self):
return self.nodes[0]
def get_end(self):
return self.nodes[-1]
def get_nodes(self):
return self.nodes
def display(self):
node_list = []
for node in self.nodes:
node_list.append(node)
pygame.draw.lines(screen, BLACK, False, node_list, 3)
# generiere Pfad aus Liniensegmenten
def new_path():
path = Path()
path.add_node(20, HEIGHT/2)
path.add_node(randint(0, WIDTH/2), randint(20, HEIGHT))
path.add_node(randint(WIDTH/2, WIDTH), randint(20, HEIGHT))
path.add_node(WIDTH-20, HEIGHT/2)
return path
###### main ######
def main() :
pygame.init()
clock = pygame.time.Clock()
# Generiere Pfad
path = new_path()
# einen Agent erzeugen
agent = pygame.image.load(AGENT_FILENAME).convert_alpha()
agent = Vehicle(screen, AGENT_FILENAME,
(WIDTH/2, HEIGHT/3))
running = True
while running:
clock.tick(FPS)
screen.fill(BG_COLOR)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
path.display()
agent.follow_path(path)
agent.update()
agent.blitme()
pygame.display.set_caption('Path Following')
pygame.display.flip()
if __name__ == "__main__":
main()
Naja, wenn du zb 10 Knoten hast. Dann gehen deren Indizes von 0 bis 9. Und wenn du den letzten sonderbehandeln willst, dann musst du ja schon bei 8 etwas anderes machen. Und jetzt schau dir mal genau an, was der Wert ist, mit dem du stattdessen vergleichst. Und mit welchem Operator.
Aber selbst wenn du das Problem gelöst hast, kommt dein nächstes. Du musst ja nicht nur EINMMAL sonder so lange seek_with_approach aufrufen, bis du da bist.
Es gibt viele Wege das zu lösen. Ein etwas einfacherer wäre, ein Attribut self.seek_function zu haben, dem du zuerst self.seek und dann beim erreichen des letzten Knotens self.approach zuweist. Und in follow_path als letztes immer self.seek_function aufrufst. Um den Wechsel der Strategie wirklich mit zu machen.
Aber selbst wenn du das Problem gelöst hast, kommt dein nächstes. Du musst ja nicht nur EINMMAL sonder so lange seek_with_approach aufrufen, bis du da bist.
Es gibt viele Wege das zu lösen. Ein etwas einfacherer wäre, ein Attribut self.seek_function zu haben, dem du zuerst self.seek und dann beim erreichen des letzten Knotens self.approach zuweist. Und in follow_path als letztes immer self.seek_function aufrufst. Um den Wechsel der Strategie wirklich mit zu machen.
hallo _deets_ ,
was du schreibst ist something beyond my scope, ich verstehe nicht wirklich etwas.
Das member seek_function 'verstehe' ich als boolesche Variable, mit der die beiden seek-Strategien eingeschaltet werden ??? Aber wie?
was du schreibst ist something beyond my scope, ich verstehe nicht wirklich etwas.
Das member seek_function 'verstehe' ich als boolesche Variable, mit der die beiden seek-Strategien eingeschaltet werden ??? Aber wie?
Dein jetztiger Code hat halt mehrere Probleme. Das erst ist der Index-basierte Zugriff. Hast du das verstanden? Warum du einen Index-Fehler bekommst?
Und der zweite ist, wie du zwischen zwei Verhaltensweisen/Strategien umschaltest. Im Moment tust du das *NICHT*. Du rufst genau *EINMAL* seek_with_approach auf. Danach nicht mehr. Und du musst es aber der Natur deines Problems nach aufrufen, bis die jeweils eigene Abbruchbedingung erfuellt ist.
Wie du dieses Umschalten erledigst ist deine Sache. Ich habe keine boolsche variable gemeint. Sondern so etwas hier (keine Klasse, aber mit self... geht das genauso):
Du koenntest auch immer mit eine if-Abfrage entscheiden, was jetzt aufgerufen werden soll, aber ich wuerde das nicht so machen, weil man mit dem Ansatz den ich gewaehlt habe schoene weitere Dinge machen kann, zb sowas hier:
Womit du den Strategien selbst die Steuerung ueberlaesst, und nicht den umliegenden Code anfassen musst.
Und der zweite ist, wie du zwischen zwei Verhaltensweisen/Strategien umschaltest. Im Moment tust du das *NICHT*. Du rufst genau *EINMAL* seek_with_approach auf. Danach nicht mehr. Und du musst es aber der Natur deines Problems nach aufrufen, bis die jeweils eigene Abbruchbedingung erfuellt ist.
Wie du dieses Umschalten erledigst ist deine Sache. Ich habe keine boolsche variable gemeint. Sondern so etwas hier (keine Klasse, aber mit self... geht das genauso):
Code: Alles auswählen
def strategy_a(i):
print("a", i)
def strategy_b(i):
print("b", i)
current_strategy = strategy_a
for i in range(100):
if i > 90:
current_strategy = strategy_b
current_strategy(i)
Code: Alles auswählen
def strategy_c(i):
print("c", i)
def strategy_a(i):
print("a", i)
if i > 90:
return strategy_b
elif relative_mondfeuchte() > 20:
return strategy_c
else:
return strategy_a
def strategy_b(i):
print("b", i)
current_strategy = strategy_a
for i in range(100):
current_strategy(i)
Danke _deets_ für deine ausführliche Antwort,
die Codebeispiele muss ich mir genauer ansehen, das brauch eine Weile.
Mal sehen: Indexfehler deshalb, weil über Methode follow_path current_node weiter inkrementiert wird, bis zum Abbruch:
die Codebeispiele muss ich mir genauer ansehen, das brauch eine Weile.
Mal sehen: Indexfehler deshalb, weil über Methode follow_path current_node weiter inkrementiert wird, bis zum Abbruch:
hallo _deets_ ,
das ist unfair, du bringst mich ganz schön ins schleudern mit deinen Beispielen.
Wenn ich mir das Ergebnis von Beispiel eins anschaue , verstehe ich was du meinst. Aber gleich tun sich neue Fragen auf: z.B wie kannst
du diese Zuweisung machen: current_strategy = strategy_a ??
Offenbar wird der Rumpf der Funktion startegy_a(i) an die Variable current_strategy übergeben, die später in der Schleife als Funktion
auftaucht. Oder nicht??
Frage: wo und unter welcher Überschrift kann ich das nachlesen?
Und verzeihe mir meine Doofheit!
das ist unfair, du bringst mich ganz schön ins schleudern mit deinen Beispielen.
Wenn ich mir das Ergebnis von Beispiel eins anschaue , verstehe ich was du meinst. Aber gleich tun sich neue Fragen auf: z.B wie kannst
du diese Zuweisung machen: current_strategy = strategy_a ??
Offenbar wird der Rumpf der Funktion startegy_a(i) an die Variable current_strategy übergeben, die später in der Schleife als Funktion
auftaucht. Oder nicht??
Frage: wo und unter welcher Überschrift kann ich das nachlesen?
Und verzeihe mir meine Doofheit!
Sozusagen. Der “Rumpf” ist offiziell ein Code-Objekt. Aber Bezeichnung hin oder her: du kannst das an beliebige Namen binden. “def foo()....” erzeugt erstmal ein Code-Objekt und bindet es dann automatisch an den Namen foo. Du kannst es aber noch an tausend andere Namen binden. Oder in eine Datenstruktur stecken. Das ist Python ganz egal.
Und ich weiß nicht, wo einem sowas erklärt wird. Ich hab’s irgendwann verinnerlicht. Woher kann ich dir nicht sagen.
Und ich weiß nicht, wo einem sowas erklärt wird. Ich hab’s irgendwann verinnerlicht. Woher kann ich dir nicht sagen.
- __blackjack__
- User
- Beiträge: 13004
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@hell: Funktionen sind wie alles was man in Python an einen Namen binden kann Objekte. Also Werte. Damit kann man alles machen was man auch mit anderen Objekten machen kann – an andere Namen binden, als Argumente bei Aufrufen übergeben, in Datenstrukturen wie Listen oder Wörterbücher stecken, …
Es dürfte dafür keine gesonderte Überschrift geben, denn mehr gibt es dazu ja eigentlich nicht zu sagen. Es gibt einen Haufen Funktionen die Funktionen als Argumente erwarten (`map()`, `filter()`, …) und auch welche die Funktionen als Rückgabewerte haben (`functools`-Modul, `operator`-Modul, …) und im `operator`-Modul gibt es für jeden Operator den die Python-Syntax kennt, eine Funktion, falls man so etwas irgendwo zum Übergeben an andere Funktionen braucht.
Und ``lambda``-Ausdrücke sind in dem Zusammenhang noch interessant, wenn man eine einfache Funktion braucht, für die man nicht extra eine Funktion mit Namen definieren möchte.
Es dürfte dafür keine gesonderte Überschrift geben, denn mehr gibt es dazu ja eigentlich nicht zu sagen. Es gibt einen Haufen Funktionen die Funktionen als Argumente erwarten (`map()`, `filter()`, …) und auch welche die Funktionen als Rückgabewerte haben (`functools`-Modul, `operator`-Modul, …) und im `operator`-Modul gibt es für jeden Operator den die Python-Syntax kennt, eine Funktion, falls man so etwas irgendwo zum Übergeben an andere Funktionen braucht.
Und ``lambda``-Ausdrücke sind in dem Zusammenhang noch interessant, wenn man eine einfache Funktion braucht, für die man nicht extra eine Funktion mit Namen definieren möchte.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
hallo _deets_ und _blackjack_
danke für eure Antworten .
Was mich halt ziemlich verblüfft hat, war, dass im Beispiel von _deets_
nur der Name der Funktion zugewiesen wurde ( und nicht der gesamte 'Funktionsausdruck ' : strategy_a(i) ) ,
und wie ihr sagt damit an einen anderen Namen gebunden wurde.
Mit der Zuweisung: current_strategy = strategy_a wird also eine Referenz
auf strategy_a(i) erstellt?
danke für eure Antworten .
Was mich halt ziemlich verblüfft hat, war, dass im Beispiel von _deets_
nur der Name der Funktion zugewiesen wurde ( und nicht der gesamte 'Funktionsausdruck ' : strategy_a(i) ) ,
und wie ihr sagt damit an einen anderen Namen gebunden wurde.
Mit der Zuweisung: current_strategy = strategy_a wird also eine Referenz
auf strategy_a(i) erstellt?
Nein, NICHT auf strategy_a(i). Woher soll denn das i kommen in dem Ausdruck "current_strategy = strategy_a"? Sowas kann man auch machen, das sieht dann aber anders aus.
Alles was da passiert ist, dass das callable, das du gerade unter dem Namen strategy_a finden kannst an einen anderen Namen gebunden wird. Du kannst auch
machen. Dann hast du halt 7 anderen Namen, unter denen es zu finden ist.
Alles was da passiert ist, dass das callable, das du gerade unter dem Namen strategy_a finden kannst an einen anderen Namen gebunden wird. Du kannst auch
Code: Alles auswählen
foo = bar = pille = palle = furz = egal = das_objekt_formerly_known_as_strategy_a = strategy_a
ok _deets_ , ein letztes mal möchte ich deine wertvolle Zeit in Anspruch nehmen und poste diesen (nicht funktionierenden) Code zum Ausgangsproblem. Dabei habe ich dein Beispiel eins als Vorlage verwendet.
In meiner ersten Version wurde mit: distance <= 10: curren_node += 1 von node zu node iteriert. range iteriert ebenfalls über die Nodeliste,
ich habe keine Ahnung, wie das alles funktioniert.
An dieser Stelle will ich mich bei bei dir bedanken für deine Hilfe und Geduld, tolles Forum.
Code: Alles auswählen
def follow_path(self, path):
target = vec2d(0,0)
path.nodes = path.get_nodes()
self.seek_function = self.seek
for i in range(len(path.nodes)):
target = path.nodes[i]
distance = self.location.get_distance(target)
if i >= len(path.nodes)-1:
print(i)
self.seek_function = self.seek_with_approach
self.seek_function(i)
In meiner ersten Version wurde mit: distance <= 10: curren_node += 1 von node zu node iteriert. range iteriert ebenfalls über die Nodeliste,
ich habe keine Ahnung, wie das alles funktioniert.
An dieser Stelle will ich mich bei bei dir bedanken für deine Hilfe und Geduld, tolles Forum.