ich bin immer noch mit Reynolds Steering Algorithmen zu Gange. Im Moment versuche ich mich an Hide. Ein Vehikel versteckt hinter einem
Hindernis vor einem anderen Agenten, der über den Bildschirm wandert. Was den Algoritmus betrifft, so orientiere ich mich an Matt Buckland, Programming Game AI ...,ch 03. Das Programm "läuft", aber eben nur "läuft".
Probleme: lasse ich den Hunter wandern, so schert der sich nur bedingt um die Hindernisse, die auf seinem Weg sind. Ebenso verhält sich das
Vehikel, welches sich versteckt. Das Verhalten ist überhaupt nicht ausbalanciert, so scheint das Wanderverhalten das Hindernisvermeidungsverhalten zu überlagern. Ähnliches Probleme hatte ich schon beim Interposealgorithmus, stelle ich den Agenten Hindernisse in den Weg, so gab es beschriebene Probleme.
Wenn ich mir die Beispielprogramme von Buckland (wine) ansehe, so erblasse ich vor Neid.
Ich weiss, dass es verschiedene Methoden gibt, die Agenten zu ausgewogenerem Verhalten zu bringen, aber wie? Die C++-Programme von
Buckland sind mir eiiiiiiinige Nummern zu weit entfernt, als dass ich sie auch nur ein kleines bisschen verstehe.
Frage: gibt es eine Möglichkeit, die nicht zu komplex ist, das Verhalten der Vehikel etwas ausgewogener zu gestalten.
Und noch eine Frage: der Methode hide(hunter, obstacles) übergebe ich eine Instanz von Vehicel, wie kann ich diese Methode in update aufrufen (statt in main ), ohne Fehlermeldung (hunter nicht bekannt) ?
Für Tips wäre ich dankbar, hell.
Hier der Code:
Code: Alles auswählen
import os, sys
import pygame
from random import randint, uniform, choice
import math
sys.path.append('..')
#from vec2d import vec2d
Vec2 = pygame.math.Vector2
WIDTH = 840
HEIGHT = 680
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)
CYAN = (0, 255, 255)
DARKGRAY = (40, 40, 40)
#BG_COLOR = (122, 150, 134)
BG_COLOR = (150, 150, 80)
FLEE_DISTANCE = 200
LOOK_AHEAD = 80
WANDER_CIRCLE_DISTANCE = 160
WANDER_CIRCLE_RADIUS = 20
CHANGE = 0.2
MAX_AVOID_FORCE = 0.3
APPROACH_RADIUS = 50
NUM_WALLS = 4
WALL_LIMIT = 40
SIZE_W = 80
SIZE_H = 80
walls = []
AGENT_FILENAME1 = '../images/bluecreep_0.png'
AGENT_FILENAME2 = '../images/old_pinkcreep_0.png'
AGENT_FILENAME3 ='../images/yellowcreep_0.png'
screen = pygame.display.set_mode((WIDTH, HEIGHT))
class Vehicle(pygame.sprite.Sprite):
def __init__(self, screen, img_filename, init_position, max_speed, max_force):
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.location = Vec2(init_position)
self.image_w, self.image_h = self.image.get_size()
self.rect = self.image.get_rect()
self.rect.center = self.location
self.location = Vec2(init_position)
self.max_speed = max_speed
self.max_force = max_force
self.velocity = Vec2(self.max_speed,self.max_speed)
self.acceleration = Vec2(0, 0)
self.direction = Vec2(0,0)
self.wanderangle = 0.0
def apply_force(self, force):
self.acceleration += force
def seek(self, target):
desired = (target - self.location).normalize() * self.max_speed
steer = (desired - self.velocity)
if steer.length() > self.max_force:
steer.scale_to_length(self.max_force)
self.apply_force(steer)
#def seek_with_approach(self, target):
# """ wenn sich Vehikel innerhalb der Entfernung
# APPROACH_RADIUS befindet bremse ab """
# desired = (target - self.location)
# dist = desired.length()
# desired.normalize_ip()
# if dist < APPROACH_RADIUS:
# desired *= dist / APPROACH_RADIUS * self.max_speed
# else:
# desired *= self.max_speed
# steer = desired - self.velocity
# if steer.length() > self.max_force:
# steer * self.max_force
# self.apply_force(steer)
def seek_with_approach(self, target):
""" nach Bucklands arrive-Algorithmus """
deceleration = 5
to_target = Vec2(0, 0)
to_target = target - self.location
# berechne die Entfernung zum target
dist = to_target.length()
if dist > 0:
deceleration_tweaker = 3
#calculate the speed required to reach the target given the desired
#deceleration
speed = dist / (deceleration * deceleration_tweaker)
#make sure the velocity does not exceed the max
speed = min(speed, self.max_speed)
#from here proceed just like Seek except we don't need to normalize
#the ToTarget vector because we have already gone to the trouble
#of calculating its length: dist.
#desired = Vec2(0, 0)
desired = to_target * speed / dist
self.apply_force(desired - self.velocity)
self.apply_force(Vec2(0,0))
def evade(self,hunter):
# berechne Entfernung zwischen pursuer und evader
distance = hunter.location - self.location
# -> nach: SteeringBehavior.java (Buckland)
look_ahead = distance.length() / (self.max_speed + hunter.velocity.length())
future_position = hunter.location + hunter.velocity * look_ahead
# Aufruf der Methode seek_with_approach
self.flee(future_position)
def flee(self, target):
""" Reynolds steering Algorithmus: flee
wenn sich Mover innerhalb der FLEE_DISTANCE
befindet """
dist = self.location - target
if dist.length() < FLEE_DISTANCE:
desired = (self.location - target).normalize() * self.max_speed
else:
desired = self.velocity.normalize() * self.max_speed
steer = desired - self.velocity
if steer.length() > self.max_force:
steer.scale_to_length(self.max_force)
self.apply_force(steer)
def get_hiding_position(self,pos_obst, radius_obst, pos_target):
""" pos_obst: Vec2, position eines Hindernisses(Kreis)
radius_obst: float, Radius des Hindernisses
pos_target: Vec2, position des Targetagenten """
# calculate how far away the agent is to be from the chosen obstacle’s
# bounding radius
dist_from_boundary = 20.0
dist_away = radius_obst + dist_from_boundary
# calculate the heading toward the object from the target
to_obst = Vec2(0, 0)
to_obst = (pos_obst - pos_target).normalize()
#print(to_obst) # debug
# scale it to size and add to the obstacle's position to get
# the hiding spot
to_obst.scale_to_length(dist_away)
return to_obst + pos_obst
def hide(self, hunter, walls):
world_record = 1000000
dist_to_closest = world_record
best_hiding_spot = Vec2(0,0)
#iteriere über alle Kreise ( Hindernisse)
#cur_obst = Vec2(0.0,0.0)
for cur_obst in walls:
#calculate the position of the hiding spot for this obstacle
hiding_spot = Vec2(0,0)
hiding_spot = self.get_hiding_position(cur_obst.location,
cur_obst.size_w,
hunter.location)
#print(hiding_spot) # debug
# find closest hiding_spot to the agent
dist = hiding_spot.distance_to(hunter.location)
if dist < dist_to_closest:
dist_to_closest = dist
best_hiding_spot = hiding_spot
# if no suitable obstacles found then evade the target
if dist_to_closest == world_record:
steer = self.evade(hunter)
return steer
else: #use Arrive on the hiding spot
steer = self.seek_with_approach(best_hiding_spot)
return steer
def avoid_walls(self):
steer = Vec2(0, 0)
desired = Vec2(0, 0)
near_wall = False
if self.location.x < WALL_LIMIT:
desired = Vec2(self.max_speed, self.velocity.y)
near_wall = True
if self.location.x > WIDTH - WALL_LIMIT:
desired = Vec2(-self.max_speed, self.velocity.y)
near_wall = True
if self.location.y < WALL_LIMIT:
desired = Vec2(self.velocity.x, self.max_speed)
near_wall = True
if self.location.y > HEIGHT - WALL_LIMIT:
desired = Vec2(self.velocity.x, -self.max_speed)
near_wall = True
if near_wall:
steer = (desired - self.velocity)
if steer.length() > self.max_force:
steer.scale_to_length(self.max_speed)
self.apply_force(steer)
def find_most_threatening(self, walls, ahead, ahead2):
most_threatening = None
for wall in walls:
collision = self.line_intersect_circle(wall, ahead, ahead2)
if collision and (not most_threatening or
self.location.distance_to(wall.location) <
self.location.distance_to(most_threatening.location)):
most_threatening = wall
return most_threatening
def line_intersect_circle(self, wall, ahead, ahead2 ):
wall_center = Vec2(wall.location)
dist1 = wall_center.distance_to(ahead)
dist2 = wall_center.distance_to(ahead2)
return (dist1 <= wall.size_w) or (dist2 <= wall.size_w )
def collision_avoidance(self,wall):
steer = Vec2(0, 0)
ahead = self.location + self.velocity.normalize() * LOOK_AHEAD
ahead2 = self.location + self.velocity.normalize() * LOOK_AHEAD * 0.5
most_threatening = self.find_most_threatening(walls, ahead, ahead2)
if most_threatening:
steer = ahead - most_threatening.location
steer.normalize_ip()
#steer *= MAX_AVOID_FORCE
steer.scale_to_length(MAX_AVOID_FORCE)
else:
steer = Vec2(0, 0)
self.apply_force(steer)
def wander(self):
""" Reynolds steering Algorithmus: wander
circle_pos : Position des Mittelpunktes des Wanderkreises """
# berechne Mittelpunkt des Wanderkreises
circle_pos = self.velocity.normalize() * WANDER_CIRCLE_DISTANCE
# und relativ zur Position des Vehikels
circle_pos += self.location
# ändere den wanderangle ein klein wenig
self.wanderangle += uniform(-CHANGE, CHANGE)
# berechne displacement-Vektor
self.displacement = Vec2(WANDER_CIRCLE_RADIUS* math.cos(self.wanderangle),
WANDER_CIRCLE_RADIUS * math.sin(self.wanderangle))
# berechne wanderforce
target = Vec2(circle_pos + self.displacement)
# zum anzeigen des displacement-Vektors
return self.seek(target)
def update(self):
if self.velocity.length() > 0.00001:
self.direction = self.velocity.normalize()
# 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)
# wenn vec2d verwendet wird:
#self.image = pygame.transform.rotate(self.base_image, -self.direction.angle)
#self.image_w, self.image_h = self.image.get_size()
self.velocity.scale_to_length(self.max_speed)
# equations of motion
self.velocity += self.acceleration
# begrenze velocity auf MAX_SPEED
if self.acceleration.length() > 0.000001:
self.velocity.scale_to_length(self.max_speed)
if self.velocity.length() > self.max_speed:
self.velocity.scale_to_length(self.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)
# wenn agent Fenstergrenzen erreicht
if self.location.x > WIDTH:
self.location.x = 0
if self.location.x < 0:
self.location.x = WIDTH
if self.location.y > HEIGHT:
self.location.y = 0
if self.location.y < 0:
self.location.y = HEIGHT
self.rect.center = self.location
# zeichne sprites
def blitme(self):
draw_pos = self.rect.move(
[-self.image_w / 2, -self.image_h / 2])
self.screen.blit(self.image, draw_pos)
def draw_vectors(self):
""" zeichne Pfeilrepräsentanten der Vektoren
velocity und desired """
center = self.location + self.velocity.normalize() * WANDER_CIRCLE_DISTANCE
pygame.draw.line(screen, RED,(self.location),
(self.location + self.displacement), 2)
class Wall():
def __init__(self, location_x, location_y, SIZE_W, SIZE_H):
self.x = location_x
self.y = location_y
self.location = Vec2(self.x, self.y)
self.size_w = SIZE_W
self.size_h = SIZE_H
def display(self):
""" Hindernis werden als
Kreis dargestellt """
pygame.draw.circle(screen, CYAN, (int(self.x),int(self.y)), int(self.size_w), 2)
############### Funktionen ###############
def calculate_non_overlapping_circles():
protection = 0
while len(walls) < 500:
# zufällige Positonen generieren
location_x = randint(SIZE_W, WIDTH - SIZE_W)
location_y = randint(SIZE_W, HEIGHT - SIZE_W)
wall = Wall(location_x, location_y, SIZE_W, SIZE_H)
overlapping = False
# teste alle positionen
for i in range(len(walls)):
wall = Wall(location_x, location_y, SIZE_W, SIZE_H)
other = Wall(location_x, location_y, SIZE_W, SIZE_H)
other = walls[i]
# überschneiden sich die Kreise
distance = ball_intersect_ball(wall.x, wall.y, other.x, other.y)
if (distance < wall.size_w*2 + other.size_w*2):
overlapping = True
# falls sich Kreise nicht überschneiden
# füge diese Position zur Liste hinzu
if not overlapping:
walls.append(wall)
return walls
protection += 1
if protection > 1000:
break
def ball_intersect_ball(x1, y1, x2, y2):
""" x1, y1 : Position eines Kreises
x2, y2 : Position eines zweiten Kreises
"""
# find distance between the two objects
x_dist = x1-x2 # distance horiz
y_dist = y1-y2 # distance vert
distance = math.sqrt((x_dist*x_dist) + (y_dist*y_dist)) # diagonal distance
return distance
################################# main ################################
def main() :
pygame.init()
clock = pygame.time.Clock()
# erzeuge Hindernisse
location_x =WIDTH/2
location_y = HEIGHT/2
for i in range(NUM_WALLS):
# Aufruf der Funktion
calculate_non_overlapping_circles()
# Vehikel hinzufügen
hunter = pygame.image.load(AGENT_FILENAME2).convert_alpha()
hunter = Vehicle(screen, AGENT_FILENAME2,
(randint(0, WIDTH),randint(0, HEIGHT)), 1.5, 0.5)
agent = pygame.image.load(AGENT_FILENAME3).convert_alpha()
agent = Vehicle(screen, AGENT_FILENAME3,
(randint(0, WIDTH),randint(0, HEIGHT)), 1.5, 0.5)
#agent_b = pygame.image.load(AGENT_FILENAME1).convert_alpha()
#agent_b = Vehicle(screen, AGENT_FILENAME1,
# (randint(0, WIDTH),randint(0, HEIGHT)),1, 0.5)
running = True
while running:
clock.tick(FPS)
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
screen.fill(BG_COLOR)
for wall in walls:
wall.display()
agent.collision_avoidance(wall)
hunter.collision_avoidance(wall)
# Update and redraw all agents
agent.update()
agent.hide(hunter, walls)
agent.blitme()
hunter.update()
hunter.avoid_walls()
#hunter.wander()
hunter.blitme()
pygame.display.set_caption('Autonome Agenten: Hide')
pygame.display.flip()
if __name__ == "__main__":