So I am trying to create a series of "boids" at random locations, which fly at random speeds, but I am having some trouble moving the rects which are in a list, although I can draw them. I am using a provided vector module, the entire code and the module can be found here. The png I am using for the sprites.
Update: I got a rect moving, by using the instance position vector instead of the class vector. But now only one boid is drawn. I suspect that more boids are drawn at the same exact position.
class Boid():
def __init__(self, screen):
self.bird = pygame.image.load("birdie.png")
self._pos = Vector2D(random.randint(0, screen.get_width()),
random.randint(0, screen.get_height()))
self._vel = Vector2D((random.randint(1, 10) / 5.0),
(random.randint(1, 10) / 5.0))
self.speed = random.randint(1, 5)
self.bird_rect = self.bird.get_rect(center=(self._pos.x, self._pos.y))
self._boids = []
def add_boid(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self._boids.append(Boid(screen))
def move_boids(self):
s = Screen()
#self.bird_rect.move_ip(self._vel.x, self._vel.y)
self._pos += (self._vel * self.speed)
#bounds check
if self._pos.x + self.bird_rect.width >= s.width:
self._pos.x = s.width - self.bird_rect.width
self._vel.x *= -1
elif self._pos.x <= 0:
self._pos.x = 0
self._vel.x *= -1
if self._pos.y - self.bird_rect.height <= 0:
self._pos.y = self.bird_rect.height
self._vel.y *= -1
elif self._pos.y >= s.height:
self._pos.y = s.height - self.bird_rect.height
self._vel.y *= -1
def draw_boids(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
print(len(self._boids))
for boid in self._boids:
self.boidRect = pygame.Rect(self.bird_rect)
#edit: changed boid._pos.x and y to self._pos.x and y
self.boidRect.x = self._pos.x
self.boidRect.y = self._pos.y
screen.blit(self.bird, self.boidRect)
You have to iterate over all boids in the self._boids list and update their _pos and bird_rect attributes to move them.
def move_boids(self):
s = Screen()
for boid in self._boids:
boid._pos += boid._vel * boid.speed
boid.bird_rect.center = boid._pos
# Bounds check.
if boid._pos.x + boid.bird_rect.width >= s.width:
boid._pos.x = s.width - boid.bird_rect.width
boid._vel.x *= -1
elif boid._pos.x <= 0:
boid._pos.x = 0
boid._vel.x *= -1
if boid._pos.y - boid.bird_rect.height <= 0:
boid._pos.y = boid.bird_rect.height
boid._vel.y *= -1
elif boid._pos.y >= s.height:
boid._pos.y = s.height - boid.bird_rect.height
boid._vel.y *= -1
You can also simplify the draw method a bit.
def draw_boids(self):
# Blit all boids at their rects.
for boid in self._boids:
screen.blit(boid.bird, boid.bird_rect)
Related
from src.common import config, settings, utils
import time
import math
from src.routine.components import Command
from src.common.vkeys import press, key_down, key_up
class Adjust(Command):
"""Fine-tunes player position using small movements."""
def __init__(self, x, y, max_steps=5):
super().__init__(locals())
self.target = (float(x), float(y))
self.max_steps = settings.validate_nonnegative_int(max_steps)
def main(self):
counter = self.max_steps
toggle = True
error = utils.distance(config.player_pos, self.target)
while config.enabled and counter > 0 and error > settings.adjust_tolerance:
if toggle:
d_x = self.target[0] - config.player_pos[0]
threshold = settings.adjust_tolerance / math.sqrt(2)
if abs(d_x) > threshold:
walk_counter = 0
if d_x < 0:
key_down('left')
while config.enabled and d_x < -1 * threshold and walk_counter < 60:
time.sleep(0.05)
walk_counter += 1
d_x = self.target[0] - config.player_pos[0]
key_up('left')
else:
key_down('right')
while config.enabled and d_x > threshold and walk_counter < 60:
time.sleep(0.05)
walk_counter += 1
d_x = self.target[0] - config.player_pos[0]
key_up('right')
counter -= 1
else:
d_y = self.target[1] - config.player_pos[1]
if abs(d_y) > settings.adjust_tolerance / math.sqrt(2):
if d_y < 0:
time.sleep(0.25)
press(Key.ROPE, 1, up_time=0.1)
time.sleep(2)
# press(Key.JUMP, 1, up_time=0.1)
# time.sleep(0.175)
# UpJump('up').main()
else:
key_down('down')
time.sleep(0.05)
press(Key.JUMP, 3, down_time=0.1)
key_up('down')
time.sleep(0.5)
counter -= 1
error = utils.distance(config.player_pos, self.target)
toggle = not toggle
This section of code is from a bot i'm working on for a game, essentially. The bot checks my X and Y and will make my character move left or right to adjust for an X,Y I give it. The issue with this is if I over-jump the target, since my character walks through the correct (X,Y) while overshooting, it will consider itself as done.
I want it to sleep for a second or two after running once, then recheck my (X,Y) and rerun if needed to avoid overshooting.
I've been trying for a while, and want to avoid just running the same thing tons of times
I am trying to create a plot that dynamically changes as the length of the x and y data sets change.
I got it to work by plotting the plot each loop, but that is not an efficient way to accomplish this.
I use the variable self.line, in line 165 to initialize this plot. At line 228 I change the plot self.ax2.plot(self.stepData, self.nAlive, 'b-')
I am trying to use this instead to just change the data: ```self.line.set_data = (self.stepData, self.nAlive)
Here is my code:
import random
import numpy as np
import matplotlib.pyplot as plt
# ====================================================================================
class Person:
def __init__(self, position):
self.xpos = position[0]
self.ypos = position[1]
self.alive = True
self.trapped = False # if all people are trapped, need to end program
self.distance = None # distance to closest neighbor
def move(self, xmax, ymax, bc, taken):
""" moves person, if possible, to an adjacent corner """
# cannot step where someone else is currently standing
taken[self.xpos, self.ypos] = False # moving from current spot
disallowed = set() # empty set object; will add disallowed directions
if bc == 'wall':
if self.ypos == ymax or taken[self.xpos, self.ypos + 1]:
disallowed.add('north')
if self.xpos == xmax or taken[self.xpos + 1, self.ypos]:
disallowed.add('east')
if self.ypos == 0 or taken[self.xpos, self.ypos - 1]:
disallowed.add('south')
if self.xpos == 0 or taken[self.xpos - 1, self.ypos]:
disallowed.add('west')
elif bc == 'periodic':
if (self.ypos == ymax and taken[self.xpos, (self.ypos + 1) % ymax]) \
or (self.ypos < ymax and taken[self.xpos, self.ypos + 1]):
disallowed.add('north')
if (self.xpos == xmax and taken[(self.xpos + 1) % xmax, self.ypos]) \
or (self.xpos < xmax and taken[self.xpos + 1, self.ypos]):
disallowed.add('east')
if (self.ypos == 0 and taken[self.xpos, (self.ypos - 1) % ymax]) \
or (self.ypos > 0 and taken[self.xpos, self.ypos - 1]):
disallowed.add('south')
if (self.xpos == 0 and taken[(self.xpos - 1) % xmax, self.ypos]) \
or (self.xpos > 0 and taken[self.xpos - 1, self.ypos]):
disallowed.add('west')
# Use the set method 'difference' to get set of allowed directions
allowed = {'north', 'east', 'south', 'west'}.difference(disallowed)
if len(allowed) == 0:
self.trapped = True # cannot move anymore
else:
"""
Randomly pick from the allowed directions; need to convert set
object to a list because random.choice doesn't work on sets
"""
self.direction = random.choice(list(allowed))
if self.direction == 'north':
if (bc == 'wall' and self.ypos < ymax) or bc == 'periodic':
self.ypos += 1
elif self.direction == 'east':
if (bc == 'wall' and self.xpos < xmax) or bc == 'periodic':
self.xpos += 1
elif self.direction == 'south':
if (bc == 'wall' and self.ypos > 0) or bc == 'periodic':
self.ypos -= 1
elif self.direction == 'west':
if (bc == 'wall' and self.xpos > 0) or bc == 'periodic':
self.xpos -= 1
"""
With periodic boundary conditions, it's possible that (xpos, ypos) could
be off the grid (e.g., xpos < 0 or xpos > xmax). The Python modulo
operator can be used to give exactly what we need for periodic bc. For
example, suppose xmax = 20; then if xpos = 21, 21 % 20 = 1; if xpos = -1,
-1 % 20 = 19. (Modulo result on a negative first argument may seem
strange, but it's intended for exactly this type of application. Cool!)
If 0 <= xpos < xmax, then modulo simply returns xpos. For example,
0 % 20 = 0, 14 % 20 = 14, etc. Only special case is when xpos = xmax, in
which case we want to keep xpos = xmax and not xpos % xmax = 0
"""
if self.xpos != xmax:
self.xpos = self.xpos % xmax
if self.ypos != ymax:
self.ypos = self.ypos % ymax
def proximity(self, xmax, ymax, bc, people):
"""
Finds distance from closest neighbor, will calculate actual distance over diagonals, assuming 1 square is
1 ft^2. If no one else is in range (less than 6 feet away), return None.
"""
distance = None
for person in people:
if person is not self and person.alive: # can only get infected by other alive people
x = abs(self.xpos - person.xpos)
y = abs(self.ypos - person.ypos)
if bc == 'periodic': # check other direction (across boundaries) for periodic bc
tempX1 = None
tempY1 = None
for i in range(2):
if tempX1 is None:
tempX1 = (self.xpos - 0) + (xmax - person.xpos) # check distance in one direction
else:
tempX2 = (person.xpos - 0) + (xmax - self.xpos) # check distance in other direction
if tempX2 < tempX1:
tempX1 = tempX2
if tempY1 is None:
tempY1 = (self.ypos - 0) + (ymax - person.ypos) # check distance in one direction
else:
tempY2 = (person.ypos - 0) + (ymax - self.ypos) # check distance in other direction
if tempY2 < tempY1:
tempY1 = tempY2
if tempX1 < x:
x = tempX1
if tempY1 < y:
y = tempY1
if x >= 6 or y >= 6:
pass # not close enough to infect
else: # need to find distance
temp = np.sqrt(x ^ 2 + y ^ 2)
if distance is None or temp < distance:
distance = temp
if distance is not None and distance >= 6:
distance = None
return distance
def gambleYourLife(self, distance):
if distance is not None: # chance of dying!!
p = 0.5 * np.e ** (-0.3 * distance)
if np.random.binomial(1, p) == 1:
self.alive = False
# ====================================================================================
class Earth:
def __init__(self, people, gridSize, bc, nSteps):
# if nSteps = None, goes on until 1 person left
"""
Grid class takes people inputs, along with size, boundary conditions, and number of maximum steps, and runs
on a random walk until one person is left or until nSteps is reached, depending on user input.
"""
self.people = people
self.xmax = gridSize[0]
self.ymax = gridSize[1]
self.bc = bc
self.point = []
self.nSteps = nSteps
self.nAlive = [len(self.people)] # keep track of number of people alive after each step
self.stepData = [0] # list of each step
# array to keep track of points that have are taken
self.taken = np.zeros([self.xmax + 1, self.ymax + 1], dtype=bool)
fig, (self.ax1, self.ax2) = plt.subplots(1, 2) # create new figure window and ax (plot) attributes to Earth obj
self.ax1.set_xlim(0, self.xmax)
self.ax1.set_ylim(0, self.ymax)
self.ax2.set_xlim(0, nSteps)
self.ax2.set_ylim(0, len(people) + 10)
self.line, = self.ax2.plot(self.stepData, self.nAlive, 'b-')
self.ax2.plot(self.stepData, self.nAlive, '--')
self.ax2.set_xlabel('Number of steps')
self.ax2.set_ylabel('Number of people alive')
self.ax2.set_autoscalex_on(True)
# self.ax2.autoscale_view(True, True, False)
for p in self.people:
pnt, = self.ax1.plot([p.xpos], [p.ypos], 'bo')
self.taken[p.xpos, p.ypos] = True
self.point.append(pnt)
def go(self):
step = 1
while not all([p.trapped for p in self.people]) and (self.nSteps is None or step < self.nSteps):
currAlive = 0 # number of alive each round
for i, p in enumerate(self.people):
if not p.trapped and p.alive:
p.move(self.xmax, self.ymax, self.bc, self.taken)
# update grid
self.point[i].set_data(p.xpos, p.ypos)
self.point[i].set_marker('o')
self.taken[p.xpos, p.ypos] = True
"""
When using periodic boundary conditions, a position on a
wall is identical to the corresponding position on the
opposite wall. So if a walker visits (x, ymax) then
(x, 0) must also be marked as visited; if a walker vists
(0, y) then (xmax, y) must also be marked as visited; etc.
"""
if self.bc == 'periodic':
if p.xpos == self.xmax:
self.taken[0, p.ypos] = True
elif p.xpos == 0:
self.taken[self.xmax, p.ypos] = True
if p.ypos == self.ymax:
self.taken[p.xpos, 0] = True
elif p.ypos == 0:
self.taken[p.xpos, self.ymax] = True
# plot path lines
if p.direction == 'north':
self.ax1.vlines(p.xpos, p.ypos - 1, p.ypos)
elif p.direction == 'east':
self.ax1.hlines(p.ypos, p.xpos - 1, p.xpos)
elif p.direction == 'south':
self.ax1.vlines(p.xpos, p.ypos + 1, p.ypos)
elif p.direction == 'west':
self.ax1.hlines(p.ypos, p.xpos + 1, p.xpos)
# determine proximity to others and potentially die
neighbor = p.proximity(self.xmax, self.ymax, self.bc, self.people)
p.gambleYourLife(neighbor)
if not p.alive:
self.point[i].set_color('r')
else: # still alive
currAlive += 1
# update plot
self.nAlive.append(currAlive)
self.stepData.append(step)
# self.line.set_data = (self.stepData, self.nAlive)
self.ax2.plot(self.stepData, self.nAlive, 'b-')
step += 1
plt.pause(0.2)
# ====================================================================================
def program(nPeople=10, gridSize=(20, 20), bc='wall', nSteps=100):
initPositions = set() # set of tuple of initial position of each person
flock = [] # initialize - list of all people
for p in range(nPeople):
while True:
x = random.randint(0, gridSize[0]) # randomly place person on graph, only one person per point
y = random.randint(0, gridSize[1])
if (x, y) not in initPositions:
break
initPositions.add((x, y))
tempPerson = Person(position=(x, y))
flock.append(tempPerson) # add person to list of people
flock = tuple(flock) # turn list into tuple (not sure why... not sure if necessary)
pandemic = Earth(flock, gridSize, bc, nSteps)
pandemic.go()
# main program =======================================================================
program()
You have to turn on the interactive mode with the command plt.ion(), or to show the figure "not blocked" with the argument block=False using fig.show(block=False).
Here is an example of using plt.ion():
# %%
# first cell ------------------------------
import matplotlib.pyplot as plt
x = [1,2,3]
y = [1,7,5]
plt.ion() # interactive mode on
fig,ax = plt.subplots()
line, = ax.plot(x,y)
fig.show()
# %%
# second cell ------------------------------
x.append(4)
y.append(10)
line.set_data(x,y) # set new data
ax.relim() # recompute the data limits
ax.autoscale_view() # automatic axis scaling
# %%
# third cell: just a radom example to plot sequentially ---------------------
from random import randint
for i in range(5):
x.append(x[-1]+1)
y.append(randint(0,10))
line.set_data(x,y) # set new data
ax.relim() # recompute the data limits
ax.autoscale_view() # automatic axis scaling
plt.pause(0.5)
I'm trying to create a collision detection between 4 controllable characters on an RPG battle map. Here is the function I'm using
def player_collission(Lord_x,Lord_y,Journeyman_x,Journeyman_y,Archer_x,Archer_y,
Cleric_x,Cleric_y):
print("Running")
if abs(Lord_x - Journeyman_x) <= 0 and abs(Lord_y - Journeyman_y) <= 0:
print("Colission detected")
return True
elif abs(Lord_x - Archer_x) <= 0 and abs(Lord_y - Archer_y) <= 0:
print("Colission detected")
return True
elif abs(Lord_x - Cleric_x) <= 0 and abs(Lord_y == Cleric_y) <= 0:
print("Colission detected")
return True
elif abs(Journeyman_x - Archer_x) <= 0 and abs(Journeyman_y - Archer_y) <= 0:
print("Colission detected")
return True
elif abs(Journeyman_x - Cleric_x) <= 0 and abs(Journeyman_y - Cleric_y) <= 0:
print("Colission detected")
return True
elif abs(Archer_x - Cleric_x) <= 0 and abs(Archer_y == Cleric_y) <= 0:
print("Colission detected")
return True
else:
return False #I didnt use classes so it has alot of if statements
if player_up:
p_collide = player_collission(Lord_x,Lord_y,Journeyman_x,Journeyman_y,Archer_x,Archer_y,
Cleric_x,Cleric_y)
if current_player == "lord":
if p_collide != True:
Lord_y -= tile_increment
if Lord_y <= 0:
Lord_y = 50
What happens is that the characters still move into each other but it detects the collision after it has already moved into each other and freezes all movement. I'm not sure how to re arrange it to make it work properly.
You detect collision when it has already happened. This is why you see characters overlapping.
Instead, you should detect if a collision is going to happen, an prevent a motion that would lead to that.
An example:
def move(coords, velocity):
"""Moves coords according to velocity."""
x, y = coords # Unpack a 2-tuple.
vx, vy = velocity
return (x + vx, y + vy)
tom_coords = (0, 0) # Tom is in the corner.
tom_v = (1, 1) # Tom moves by diagonal.
jerry_coords = (5, 0) # Jerry is a bit away from Tom.
jerry_v = (0, 1) # Jerry moves vertically.
while True:
new_tom_coords = move(tom_coords, tom_v) # Tom moves without fear.
new_jerry_coords = move(jerry_coords, jerry_v)
if new_jerry_coords == new_tom_coords: # Would be a collision!
new_jerry_coords = jerry_coords # Back to previous tile.
vx, vy = jerry_v
jerry_v = (-vx, -vy) # Jerry runs back.
print("Collision imminent, Jerry runs away!")
else:
jerry_coords = new_jerry_coords # Only update if no collision.
# Could also check collisions with walls, etc.
tom_coords = new_tom_coords
# Not inside pygame, so just print it and wait for a key press.
print('Tom:', tom_coords, 'Jerry:', jerry_coords)
input("Enter to continue, Ctrl+C to stop ")
Run it in and see how Tom and Jerry come close to each other but never occupy the same tile.
I am making an asteroid remake of sorts, on of the big things in asteroids is the fact that the bullets rotate to match the objects direction. I have managed to make the bullets come out of the ship, nut it stays the same upright rotation. I would Like to make the bullet match rotation with the ship, so it looks like the bullets are coming out of the ship. My code is long unfortunately, but I do not know what to take out to make it more compact. I just tried to add comments and hope I guess. Any help is greatly appreciated. The vast majority of the actions happens in the 'MOUSEBUTTONDOWN" code. I have a 'rotate" function for the ship already, I'm thinking maybe we have to just use that, but I am unable to implement it.
The Image for The Ship
Image for the bullet I Want to rotate
import pygame as game
import pygame.math as math
import random as r
'''
Here I defined things
'''
game.init()
game.display.set_caption("Asteroids")
screen = game.display.set_mode([800,600])
gameon=True
bgcolor = game.color.Color("#FFFFFF")
ship = game.image.load("ship.png")
ship = game.transform.scale(ship, (50, 50))
rect = ship.get_rect()
rect.x=400
rect.y=400
shipdir = 0
newship = ship
auto = False
arrow = game.image.load("bullet.png")
shiphitbox = ship.get_rect()
shiphitbox.x = 100
shiphitbox.y = 100
arrows=[]
#code to rotate the ship
def rotateship(shipdir):
newship = game.transform.rotate(ship, shipdir)
newrect = rect.copy()
newrect.center = newship.get_rect().center
return newship.subsurface(newrect).copy()
def getmove():
#returns x-y movement based on directrion facing
global shipdir
d=shipdir
if d >= 349 or d < 11:
return [0, -2]
elif d >=11 and d < 34:
return [-1,-2]
elif d >=34 and d < 56:
return [-2,-2] #
elif d >=56 and d < 79:
return [-2,-1]
elif d >=79 and d < 102:
return [-2,0]
elif d >=102 and d < 125:
return [-2,1]
elif d >=125 and d < 147:
return [-2,2] #
elif d >=147 and d < 170:
return [-1,2]
elif d >=170 and d < 191:
return [0,2]
elif d >=191 and d < 214:
return [1,2]
elif d >=214 and d < 237:
return [2,2] #
elif d >=237 and d < 260:
return [2,1]
elif d >=260 and d < 282:
return [2,0]
elif d >=282 and d < 305:
return [2,-1]
elif d >=305 and d < 328:
return [2,-2] #
elif d >=328 and d < 349:
return [1,-2]
##main loop of the game
while gameon:
screen.fill(bgcolor)
screen.blit(newship,(rect.x,rect.y))
key = game.key.get_pressed()
event=game.event.poll()
#controls
if key[game.K_a]:
if shipdir==0: shipdir=360
shipdir -= 1
newship = rotateship(shipdir)
if key[game.K_d]:
if shipdir==360: shipdir=0
shipdir += 1
newship = rotateship(shipdir)
if auto==True or key[game.K_SPACE] or key[game.K_w] :
move = getmove()
rect.x += move[0]
rect.y += move[1]
game.time.delay(5)
#shooting
if event.type==game.MOUSEBUTTONDOWN:
'''
This is where the shooting happens
'''
arrowbox = arrow.get_rect()
arrowbox.x = rect.x;
arrowbox.y = rect.y;
move = getmove()
data = [arrowbox, move[0], move[1]]
arrows.append(data)
if event.type==game.QUIT:
gameon=False;
#spawning projectiles
for bullet in arrows:
bullet[0].x += bullet[1]
bullet[0].y += bullet[2]
if bullet[0].x > 700 or bullet[0].x<0:
arrows.remove(bullet)
for bullet in arrows:
screen.blit(arrow,(bullet[0].x,bullet[0].y))
game.display.flip() #redraws / refreshes screen
##comes here when game is over
while True:
screen.fill(bgcolor)
game.display.flip()
event=game.event.poll()
if event.type == game.QUIT:
break
game.quit()
I would Like to make the bullet match rotation with the ship, so it looks like the bullets are coming out of the ship.
You have to know the angle of rotation for each separate bullet. Add the angle of rotation to the bullet data:
data = (arrowbox, move[0], move[1], shipdir)
arrows.append(data)
Create rotated bullets by pygame.transform.rotate and blit it:
for bullet in arrows:
rotBullet = pygame.transform.rotate(arrow, bullet[3])
screen.blit(rotBullet, (bullet[0].x, bullet[0].y))
I Have Been Trying for days on how to do this. Basically You Control A Player, And zombies follow you.
Problem Is, I Cant Seem To Get The Zombies To Follow! I Tried If Statements For Example
if playerx > zombiex:
zombiex=zombiex - 2
screen.blit(zombie,(zombiex,zombiey))
aaaaaand That Didnt Work.... :/
Any Ideas?
Maybe this is what you searched for.
def length(x, y):
return (x ** 2 + y ** 2) ** .5
def norm(x, y):
_len = length(x, y)
return x / _len, y / _len
class ZombieController(object):
def __init__(self, zombie_view_range):
self._zombs = []
self.append = self._zombs.append
self._range = zombie_view_range
def NextFrame(self, player_pos):
px, py = player_pos
_range = self._range
for zombie in self._zombs:
x, y = zombie.position
dx, dy = px - x, py - y
_len = length(dx, dy)
if _len <= _range:
speed = zombie.speed
direction = norm(dx, dy)
zombie.positon = x + direction[0] * speed, y + direction[1] * speed
First, can't answer your whole questions as there's not enough information. How doesn't it work exactly?
Second, if you want the zombies to follow, you need thier coordinates to converge with the players so you need something like:
if playerx > zombiex:
zombiex = zombiex + max(zombiespeed, playerx - zombiex)
elif playerx < zombiex:
zombiex = zombiex - max(zombiespeed, zombiex - playerx)
NB:
I replace 2 with zombiespeed which you define elsewhere so you can change the speed in one place for future.
I use max() to ensure the zombie won't move PAST the player when very close.
You'd obviously do the same for the y direction too.