Skip to content

Instantly share code, notes, and snippets.

@bytezen
Created July 20, 2018 14:33
Show Gist options
  • Save bytezen/68cb262659b86396d20fcfeb919c6df8 to your computer and use it in GitHub Desktop.
Save bytezen/68cb262659b86396d20fcfeb919c6df8 to your computer and use it in GitHub Desktop.
Day4 - Seek Steering Behavior
from pygame.math import Vector2
import util
class Behavior:
NONE = 0
SEEK = 1
## id_map = {0:'NONE',1:'SEEK'}
##
## def __init__(self,entity,behavior_type):
## self.type = behavior_type
## self.entity = entity
##
## def calculate(self, **kwargs):
## pass
##
## def __repr__(self):
## return self.id_map[self.type]
##
class SteeringBehaviors:
"""
Class that encapsulates a host of steering behaviors that can be applied to
an entity. Entitiies are of type Actor. See the Actor documentation for how to use
"""
def __init__(self, entity):
"""
Create a steering behaviors for entity. Entity is expected to be of
type MovingEntity.
"""
self.entity = entity
self._steering_force = Vector2()
self._flags = Behavior.NONE
def turn_on(self, behavior):
self._flags |= behavior
def turn_off(self, behavior):
if self.is_on(behvior):
self._flags ^= behavior
def is_on(self,behavior):
return self._flags & behavior > 0
def calculate(self):
self._steering_force *= 0
if self.is_on(Behavior.SEEK):
self._steering_force += self.seek(self.entity.seek_target)
# clamp the steering_force to the maximum if necessary
util._clamp_vector(self._steering_force,0, self.entity.max_force)
return self._steering_force
def seek(self,target):
"""Compute the steering force required to seek a target
Args:
target (Vector2, tuple, list): The coordinates of the target
Returns:
force (Vector2): The steering force for this behavior
"""
# our desired velocity is the velocity that would take us to the target
# from our current position
desiredVelocity = Vector2(target) - self.entity.pos
# avoid dividing by 0 by making sure the length of the desired velocity
# is greater than 0
if desiredVelocity.length() > 0.0001:
desiredVelocity.normalize_ip()
# scale the vector by our max_speed
desiredVelocity *= self.entity.max_speed
# the resulting force is the difference between where we want to go
# and our current velocity
return desiredVelocity - self.entity.vel
import pygame as pg
from pygame.math import Vector2
import pygame.gfxdraw
from behaviors import *
import util
pg.init()
IMAGE_PATH = 'images/'
MAX_SPEED = 100
MAX_FORCE = 1000
class Boid:
"""Class representing a game agent that can move
Attrs:
color ('red',''purple','green','blue') - color of the agent
pos (Vector2) - the position of the agent
mass (float) - the mass of the agent
vel (Vector2) - velocity of the agent
heading (Vector2) - heading of the agent
side(Vector2) - the vector perpendicular to the agent
max_force (float) - the maximum force that can be exerted on the agent
max_speed (float) - the maximum speed that the agent can attain
steering (Steering Behavior) - manages the behavior of the agent
steering_force (Vector2) - the force resulting from the behaviors that
are controlling the agent
Mehtods:
update(dtime) - updates the agents position using the time since the last update
as the time value for the physics calculations
draw(surface) - renders the agent to the screen
seek_on () - turns seek behavior on
seek_off () - turns seek behavior off
"""
_ID = 0
def __init__(self, pos=(0,0), vel=(0,0), mass = 1.0, color='red'):
self.pos = Vector2(pos)
self.mass = mass
self.vel = Vector2(vel)
self.heading = Vector2(1,0)
self.side = self.heading.rotate(-90)
self._orig_surface = _get_image(color)
_,self.angle = self.heading.as_polar()
self.steering = SteeringBehaviors(self)
self.steering_force = Vector2()
self.seek_target = Vector2(100,100)
self.max_force = MAX_FORCE
self.max_speed = MAX_SPEED
@property
def vel(self):
return self._vel
@vel.setter
def vel(self,value):
"""sets the velocity, heading and side vectors
copies the value to the velocity vector. If the value
is the zero vector then heading and side are set to
zero vectors too.
"""
self._vel = Vector2(value)
#calculate the heading angle from the velocity
_,self.angle = self._vel.as_polar()
if self._vel.length() > 0.001:
#calculate the heading from the velocity
self.heading = self._vel.normalize()
#calculate the side vector (perpendicular to the heading)
self.side = self.heading.rotate(90)
else:
self.heading = Vector2()
self.side = Vector2()
def update(self, dtime):
# get the steering force
self.steering_force = self.steering.calculate()
# accel = Force / mass
accel = self.steering_force / self.mass
# velocity
# velocity = initialVelocity + acceleration * changeInTime
self.vel = self.vel + accel * dtime
# make sure that we don't exceed the speed limit
util._clamp_vector(self.vel,0, self.max_speed)
#position
# position = positionInitial + velocity * changeInTime
self.pos += self.vel * dtime
#update the angle
_,self.angle = self.vel.as_polar()
def draw(self, surface):
_surface, _rect = util._rotate(self._orig_surface,
self.angle,
self.pos,
Vector2(0,0))
surface.blit(_surface, _rect)
#draw the steering force
f = self.steering_force / self.max_force
f *= 50
pg.gfxdraw.line(surface,
int(self.pos.x),
int(self.pos.y),
int(self.pos.x + f.x),
int(self.pos.y + f.y),
pg.Color(200,0,0))
def seek_on(self):
self.steering.turn_on(Behavior.SEEK);
def seek_off(self):
self.steering.turn_off(Behavior.SEEK);
def _get_image(color):
if color == 'green':
return pg.image.load(IMAGE_PATH+'boid3_small.png').convert_alpha()
if color == 'blue':
return pg.image.load(IMAGE_PATH+'boid2_small.png').convert_alpha()
if color == 'purple':
return pg.image.load(IMAGE_PATH+'boid4_small.png').convert_alpha()
return pg.image.load(IMAGE_PATH+'boid1_small.png').convert_alpha()
import pygame as pg
from pygame.locals import *
from pygame.math import Vector2
from boid import Boid
import random
##from game_world import *
import sys
##import game_world as world
BOID_NUMBER = 1
pg.init()
window = pg.display.set_mode((500,500), SRCALPHA | HWSURFACE)
clock = pg.time.Clock()
# load some images of boids
##boid_orig_surface = pg.image.load('images/boid1_small.png').convert_alpha()
##boid_surface = pg.transform.rotozoom(boid_orig_surface, -45, 1.0)
##offset = Vector2(-boid_orig_surface.get_width() * 0.5,
## -boid_orig_surface.get_height() * 0.5)
##offset.rotate_ip(-45)
# load the target image
target = pg.image.load('images/target.png').convert_alpha()
target_pos = (250,250)
boids = []
for i in range(BOID_NUMBER):
boid = Boid(color=random.choice(['red','blue','green','purple']))
boid.pos = (random.randint(0, 500), random.randint(0,500))
boid.max_speed = random.randint(100,100)
boid.seek_target = target_pos
boid.seek_on()
boids.append(boid)
frame = 0
while True:
window.fill(pg.Color('white'))
for event in pg.event.get():
if event.type == MOUSEBUTTONUP:
target_pos = event.pos
for b in boids:
b.seek_target = target_pos
if event.type == QUIT:
pg.display.quit()
pg.quit()
sys.exit()
# draw the target on the screen
window.blit(target,target.get_rect(center=target_pos))
# update and draw the agent on the screen
for b in boids:
b.update(clock.get_time() * .001)
b.draw(window)
clock.tick(60)
pg.display.update()
import pygame as pg
from pygame.math import Vector2
def _rotate(surface, angle, pivot, offset):
"""Rotate the surface around the pivot point
Args:
surface (pygame.Surface): The surface that is to be rotated
angle (float): Rotate by this angle
pivot (tuple, list, pygame.math.Vector2): The pivot point
offset (pygame.math.Vector2): This vector is added to the pivot point after the rotation
Returns:
rotated image (pygame.Surface): the new rotated surface
rect (pygame.Rect): A rect with proper positioning for the rotated surface
"""
#rotate the image
rotated_image = pg.transform.rotozoom(surface, -angle, 1)
#rotate the offset vector
rotated_offset = offset.rotate(angle)
#Add the rotated offset vector to the center point to shift the rectangle
rect = rotated_image.get_rect(center = pivot + rotated_offset)
return rotated_image , rect
def _clamp_vector(vector,min_length, max_length):
"""make sure that a vectors length is between minimum and maximum
Args:
vector (Vector2) - the vector to scale
min_length (positive float) - the minimum length of the vector
max_length (positive float) - the maximum length of the vector
Return:
the vector passed in is changed if necessary
"""
length = vector.length()
if length > .001:
if length < min_length:
return vector.scale_to_length(min_length)
elif length > max_length:
return vector.scale_to_length(max_length)
else:
return vector
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment