Related
I'm trying to code a pong game on Python with Pygame. Weirdly, the game works good, the ball bounces off on edges, respawns using space, the paddles move (up/down arrow and i/k keys) without leaving the screen. Most of the time the ball collides with paddles but some other times the ball acts weirdly or teleports.
I thought the problem might with the collision logic. So that's what I'm sharing, if you want more there's the link below.
P1 and P2 represent the paddles. Basically I check if x is near the paddle and then check if y is in the paddle range (from topPaddleY to bottomPaddleY)
For more code, I'm sharing it with codeskulptor3. https://py3.codeskulptor.org/#user305_Voqctw5Vzh_0.py
class Ball:
def __init__(self, radius, center, color):
self.radius = radius
self.initial_attributes = center
self.center = list(center)
self.color = color
self.vel = [random.choice(VELOCITIES), random.choice(VELOCITIES)]
def draw(self):
pygame.draw.circle(screen, self.color, tuple(self.center), self.radius )
def update(self, p1, p2):
x = self.center[0]
y = self.center[1]
y_adjusted = (y+self.radius, y-self.radius)
if y_adjusted[0] >= HEIGHT or y_adjusted[1] <= 0:
self.vel[1] = -self.vel[1]
if x <= 28 or x >= WIDTH - 28:
p1_range = [p1.pos[1], p1.pos[1] + 100]
if p1_range[0] <= y <= p1_range[1]:
self.vel[0] = (-self.vel[0])
p2_range = [p2.pos[1], p2.pos[1] + 100]
if p2_range[0] <= y <= p2_range[1]:
self.vel[0] = (-self.vel[0])
def move(self, p1, p2):
self.center[0] = self.center[0] + self.vel[0]
self.center[1] = self.center[1] + self.vel[1]
self.update(p1, p2)
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE]:
self.spawn()
def spawn(self):
self.center = list(self.initial_attributes)
self.vel = [random.choice(VELOCITIES), random.choice(VELOCITIES)]
Edit : I was able to fix it by using as suggested the y_adjusted + adding a print statement to check the position of the ball during the collision. It appeared that velocity changed more than once during the collision So i added a flag for reflection. It seems to be rolling good now. Thanks SO.
def update(self, p1, p2):
x = self.center[0]
y = self.center[1]
y_adjusted = (y+self.radius, y-self.radius)
reflected = False
if y_adjusted[0] >= HEIGHT or y_adjusted[1] <= 0:
self.vel[1] = -self.vel[1]
if x <= 28 or x >= WIDTH - 28:
p1_range = [p1.pos[1], p1.pos[1] + 100]
if ((p1_range[0] <= y_adjusted[0] <= p1_range[1]) or (p1_range[0] <= y_adjusted[1] <= p1_range[1])) and (not(reflected)):
self.vel[0] = (-self.vel[0])
reflected = True
p2_range = [p2.pos[1], p2.pos[1] + 100]
if ((p2_range[0] <= y_adjusted[0] <= p2_range[1]) or (p2_range[0] <= y_adjusted[1] <= p2_range[1])) and (not(reflected)):
self.vel[0] = (-self.vel[0])
reflected = True
else :
reflected = False
I was able to fix it by using as suggested the y_adjusted + adding a print statement to check the position of the ball during the collision. It appeared that velocity changed more than once during the collision So i added a flag for reflection. It seems to be rolling good now.
def update(self, p1, p2):
x = self.center[0]
y = self.center[1]
y_adjusted = (y+self.radius, y-self.radius)
reflected = False
if y_adjusted[0] >= HEIGHT or y_adjusted[1] <= 0:
self.vel[1] = -self.vel[1]
if x <= 28 or x >= WIDTH - 28:
p1_range = [p1.pos[1], p1.pos[1] + 100]
if ((p1_range[0] <= y_adjusted[0] <= p1_range[1]) or (p1_range[0] <= y_adjusted[1] <= p1_range[1])) and (not(reflected)):
self.vel[0] = (-self.vel[0])
reflected = True
p2_range = [p2.pos[1], p2.pos[1] + 100]
if ((p2_range[0] <= y_adjusted[0] <= p2_range[1]) or (p2_range[0] <= y_adjusted[1] <= p2_range[1])) and (not(reflected)):
self.vel[0] = (-self.vel[0])
reflected = True
else :
reflected = False
i am trying to make a game where you can shoot bullets to kill emojis. However, i can't manage to figure out how to stop spamming the space key to shoot bullets. If you keep on spamming, the game would be too easy. I am not exactly sure if I should use the sleep command or something else.Please help! thanks!
Here is my code:
# import everything from turtle
from turtle import *
import random
import math
#create a link to the object (creates the environment)
screen = Screen()
speed1 = 1.3
ht()
amountOfEmojis = 11
#set a boundary for screen, if touches end, goes to the other side
screenMinX = -screen.window_width()/2
screenMinY = -screen.window_height()/2
screenMaxX = screen.window_width()/2
screenMaxY = screen.window_height()/2
#establish important data for screen environment
screen.setworldcoordinates(screenMinX,screenMinY,screenMaxX,screenMaxY)
screen.bgcolor("black")
#turtle setup
penup()
ht()
speed(0)
goto(0, screenMaxY - 50)
color('white')
write("Welcome to Emoji Run!", align="center", font=("Courier New",26))
goto(0, screenMaxY - 70)
write("Use the arrow keys to move and space to fire. The point of the game is to kill the emojis", align="center")
goto(0, 0)
color("red")
emojis = ["Poop_Emoji_7b204f05-eec6-4496-91b1-351acc03d2c7_grande.png", "1200px-Noto_Emoji_KitKat_263a.svg.png",
"annoyningface.png", "Emoji_Icon_-_Sunglasses_cool_emoji_large.png"]
class Bullet(Turtle):
#constructor, object for a class, pass in information
def __init__(self,screen,x,y,heading):
#create a bullet
Turtle.__init__(self)#clones bullet
self.speed(0)
self.penup()
self.goto(x,y)
self.seth(heading)#pointing to itself
self.screen = screen
self.color('yellow')
self.max_distance = 500
self.distance = 0
self.delta = 20
self.shape("bullet")
#logic to move bullet
def move(self):
self.distance = self.distance + self.delta#how fast it's going to move
self.forward(self.delta)
if self.done():
self.reset()
def getRadius(self):
return 4#collision detection helper function
def blowUp(self):
self.goto(-300,0)#function that makes something go off the screen
def done(self):
return self.distance >= self.max_distance # append to list
class Asteroid(Turtle):
def __init__(self,screen,dx,dy,x,y,size,emoji):#spawn asteroid randomly
Turtle.__init__(self)#clone itself
self.speed(0)
self.penup()
self.goto(x,y)
self.color('lightgrey')
self.size = size
self.screen = screen
self.dx = dx
self.dy = dy
r = random.randint(0, len(emoji) - 1)
screen.addshape(emojis[r])
self.shape(emojis[r])
#self.shape("rock" + str(size)) #sets size and shape for asteroid
def getSize(self):#part of collision detection
return self.size
#getter and setter functions
def getDX(self):
return self.dx
def getDY(self):
return self.dy
def setDX(self,dx):
self.dx = dx
def setDY(self,dy):
self.dy = dy
def move(self):
x = self.xcor()
y = self.ycor()
#if on edge of screen. go to opposite side
x = (self.dx + x - screenMinX) % (screenMaxX - screenMinX) + screenMinX
y = (self.dy + y - screenMinY) % (screenMaxY - screenMinY) + screenMinY
self.goto(x,y)
def blowUp(self):
self.goto(-300,0)#function that makes something go off the screen
def getRadius(self):
return self.size * 10 - 5
class SpaceShip(Turtle):
def __init__(self,screen,dx,dy,x,y):
Turtle.__init__(self)
self.speed(0)
self.penup()
self.color("white")
self.goto(x,y)
self.dx = dx
self.dy = dy
self.screen = screen
self.bullets = []
self.shape("turtle")
def move(self):
x = self.xcor()
y = self.ycor()
x = (self.dx + x - screenMinX) % (screenMaxX - screenMinX) + screenMinX
y = (self.dy + y - screenMinY) % (screenMaxY - screenMinY) + screenMinY
self.goto(x,y)
#logic for collision
def powPow(self, asteroids):
dasBullets = []
for bullet in self.bullets:
bullet.move()
hit = False
for asteroid in asteroids:
if intersect(asteroid, bullet):#counts every asteroid to see if it hits
asteroids.remove(asteroid)
asteroid.blowUp()
bullet.blowUp()
hit = True
if (not bullet.done() and not hit):
dasBullets.append(bullet)
self.bullets = dasBullets
def fireBullet(self):
self.bullets.append(Bullet(self.screen, self.xcor(), self.ycor(), self.heading()))
def fireEngine(self):#how turtle moves
angle = self.heading()
x = math.cos(math.radians(angle))
y = math.sin(math.radians(angle))
self.dx = self.dx + x#how it rotates
self.dy = self.dy + y
self.dx = self.dx / speed1
self.dy = self.dy / speed1
#extra function
def turnTowards(self,x,y):
if x < self.xcor():
self.left(7)
if x > self.xcor():
self.right(7)
def getRadius(self):
return 10
def getDX(self):
return self.dx
def getDY(self):
return self.dy
#collision detection
def intersect(object1,object2):
dist = math.sqrt((object1.xcor() - object2.xcor())**2 + (object1.ycor() - object2.ycor())**2)
radius1 = object1.getRadius()
radius2 = object2.getRadius()
# The following if statement could be written as
# return dist <= radius1+radius2
if dist <= radius1+radius2:
return True
else:
return False
#adds object to screen
screen.register_shape("rock3",((-20, -16),(-21, 0), (-20,18),(0,27),(17,15),(25,0),(16,-15),(0,-21)))
screen.register_shape("rock2",((-15, -10),(-16, 0), (-13,12),(0,19),(12,10),(20,0),(12,-10),(0,-13)))
screen.register_shape("rock1",((-10,-5),(-12,0),(-8,8),(0,13),(8,6),(14,0),(12,0),(8,-6),(0,-7)))
screen.register_shape("ship",((-10,-10),(0,-5),(10,-10),(0,10)))
screen.register_shape("bullet",((-2,-4),(-2,4),(2,4),(2,-4)))
#ship spawn exactly the middle everytime
ship = SpaceShip(screen,0,0,(screenMaxX-screenMinX)/2+screenMinX,(screenMaxY-screenMinY)/2 + screenMinY)
#randomize where they spawn
asteroids = []
for k in range(amountOfEmojis):
dx = random.random() * 6 - 3
dy = random.random() * 6 - 3
x = random.randrange(10) * (screenMaxX - screenMinX) + screenMinX
y = random.random() * (screenMaxY - screenMinY) + screenMinY
asteroid = Asteroid(screen,dx,dy,x,y,random.randint(1,3), emojis)
asteroids.append(asteroid)
def play():
# Tell all the elements of the game to move
ship.move()
gameover = False
for asteroid in asteroids:
r = random.randint(0, 1)
if r == 1:
asteroid.right(50)
else:
asteroid.left(20)
asteroid.move()
if intersect(ship,asteroid):
write("You Got Killed :(",font=("Verdana",25),align="center")
gameover = True
ship.powPow(asteroids)
screen.update()
if not asteroids:
color('green')
write("You Killed the Emojis!!",font=("Arial",30),align="center")
ht()
if not gameover:
screen.ontimer(play, 30)
bullets = []
#controls
def turnLeft():
ship.left(7)
def turnRight():
ship.right(7)
def go():
ship.fireEngine()
def fire():
ship.fireBullet()
ht()
screen.tracer(0);
screen.onkey(turnLeft, 'left')
screen.onkey(turnRight, 'right')
screen.onkey(go, 'up')
screen.onkey(fire, 'space')
screen.listen()
play()
Thanks!
You could try something like this:
import time
FIRE_DELAY = 0.1 # number of seconds between bullets
LAST_FIRE = 0
def fire():
t = time.time()
if t - LAST_FIRE > FIRE_DELAY:
LAST_FIRE = t
ship.fireBullet()
Does Spyder have a good way to detect what is causing a crash?
All the changes we made are in the ArrayPQ class and the node class, the other code works without our changes.
import math
import numpy as np
import numpy.random as rand
import numpy.linalg as linalg
from tkinter import Tk, Frame, Canvas
class ArrayPQ:
def __init__(self, num_balls):
self.collisionNodes = np.array([])
self.pastCollisions = np.zeros(num_balls)
def parent(self, i):
return (i-1) // 2
def right(self, i):
return (i*2) + 1
def left(self, i):
return (i*2) + 2
def insert(self, i, j, value, num_collisions_i, num_collisions_j):
self.pastCollisions[i] = num_collisions_i
self.pastCollisions[j] = num_collisions_j
self.collisionNodes = np.append(self.collisionNodes, node.nodeinit(self, i, j, value, num_collisions_i, num_collisions_j))
self.heapify1(i)
def heapify1(self, i):
l = self.left(i)
r = self.right(i)
end = len(self.collisionNodes)
top = i
if l < end and self.collisionNodes(i) < self.collisionNodes(l):
top = l
if r < end and self.collisionNodes(top) < self.collisionNodes(r):
top = r
if max != i:
self.swap(i, top)
self.heapify1(top)
def heapify2(self, i):
if self.right(i) + len(self.collisionNodes)
def delete(self, i):
self.swap(self, 0, (len(self.collisionNodes) -1))
del self.collisionNodes[len(self.collisionNodes)-1]
self.heapify1(i)
def swap(self, i, j):
self.collisionNodes[i], self.collisionNodes[j] = self.collisionNodes[j],
self.collisionNodes[i]
def get_next(self):
temp = self.collisionsNodes[0]
return temp.BBi, temp.BBj, temp.T, temp.CCi, temp.CCj
class node:
def nodeinit(self, Bi, Bj, T, Ci, Cj):
self.BBi = Bi
self.BBj = Bj
self.TT = T
self.CCi = Ci
self.CCj = Cj
class Painter:
#__init__ performs the construction of a Painter object
def __init__(self, root, scale=500, border=5, refresh_speed=5,
filename='balls.txt', min_radius=5, max_radius=10, num_balls=20):
#width and height are used to set the simulation borders
width = scale + border
height = scale + border
self.time = 0
#setup will set up the necessary graphics window and the ball list
self.setup(root, width, height, border, refresh_speed)
#Check the input parameter 'filename' to load predetermined simulation
#otherwise set up the default simulation
if filename is None:
self.init_balls(max_radius, min_radius, num_balls)
self.num_balls = num_balls
else:
self.num_balls = self.read_balls(scale, filename)
#Create the priority data structure
self.PQ = ArrayPQ(self.num_balls)
#Initialize all possible collision times
self.init_ball_collision_times()
self.init_wall_collision_times()
#draw will draw the graphics to the window
self.draw()
#refresh is a loop method intended to create animations
self.refresh()
#A blank return indicates the end of the function
return
#setup creates the window to display the graphics along with the red border
#of the simulation
def setup(self, root, width, height, border, refresh_speed):
# Draw frame etc
self.app_frame = Frame(root)
self.app_frame.pack()
self.canvas = Canvas(self.app_frame, width = width, height = height)
self.canvas_size = (int(self.canvas.cget('width')),
int(self.canvas.cget('height')))
self.canvas.pack()
self.refresh_speed = refresh_speed
# Work area
self.min_x = border
self.max_x = width - border
self.min_y = border
self.max_y = height - border
#create array to hold the n number of balls
self.balls = []
self.ball_handles = dict()
return
def read_balls(self, scale, filename):
f = open(filename)
num_balls = int(f.readline().strip())
for l in f:
ll = l.strip().split(" ")
x = scale*float(ll[0])
y = scale*float(ll[1])
vx = scale*float(ll[2])
vy = scale*float(ll[3])
radius = scale*float(ll[4])
mass = float(ll[5])
r = int(ll[6])
g = int(ll[7])
b = int(ll[8])
tk_rgb = "#%02x%02x%02x" % (r, g, b)
new_ball = Ball(radius, x, y, vx, vy, mass, tk_rgb)
self.balls.append(new_ball)
return num_balls
def init_balls(self, max_radius, min_radius, num_balls):
for i in np.arange(num_balls):
while(True):
radius = (max_radius - min_radius) * rand.random_sample() +
min_radius
ball_min_x = self.min_x + radius
ball_max_x = self.max_x - radius
x = (ball_max_x - ball_min_x)*rand.random_sample() + ball_min_x
ball_min_y = self.min_y + radius
ball_max_y = self.max_y - radius
y = (ball_max_y - ball_min_y)*rand.random_sample() + ball_min_y
vx = rand.random_sample()
vy = rand.random_sample()
mass = 1.0 # rand.random_sample()
new_ball = Ball(radius, x, y, vx, vy, mass)
if not new_ball.check_overlap(self.balls):
self.balls.append(new_ball)
break
#init_wall_collision_times will set all of the balls' minimum collision time
def init_wall_collision_times(self):
for i in np.arange(len(self.balls)):
bi = self.balls[i]
tix = bi.horizontal_wall_collision_time(self.min_x, self.max_x)
tiy = bi.vertical_wall_collision_time(self.min_y, self.max_y)
self.PQ.insert(i, -1, tix + self.time, self.balls[i].count, -1)
self.PQ.insert(-1, i, tiy + self.time, -1, self.balls[i].count)
return
#init_ball_collision_times will set all of the balls' minimum collision time
#with all other balls and store that time within the ith and jth index of
#PQ.ball_collision_time
def init_ball_collision_times(self):
for i in np.arange(self.num_balls):
bi = self.balls[i]
for j in np.arange(i+1, self.num_balls):
bj = self.balls[j]
tij = bi.ball_collision_time(bj)
self.PQ.insert(i, j, tij + self.time, self.balls[i].count,
self.balls[j].count)
# self.ball_collision_times[i][j] = tij
# self.ball_collision_times[j][i] = tij
return
#walls (horizontal and vertical) and all other balls within the PQ array
def update_collision_times(self, i):
bi = self.balls[i]
tix = bi.horizontal_wall_collision_time(self.min_x, self.max_x)
tiy = bi.vertical_wall_collision_time(self.min_y, self.max_y)
self.PQ.insert(i, -1, tix + self.time,self.balls[i].count, -1)
self.PQ.insert(-1, i, tiy + self.time, -1, self.balls[i].count)
for j in np.arange(self.num_balls):
bj = self.balls[j]
tij = bi.ball_collision_time(bj) + self.time
if i > j:
self.PQ.insert(j, i,
tij,self.balls[j].count,self.balls[i].count)
else:
self.PQ.insert(i, j, tij,self.balls[i].count,
self.balls[j].count)
return
#draw will draw the borders and all balls within self.balls
def draw(self):
#Draw walls
self.canvas.create_line((self.min_x, self.min_y), (self.min_x,
self.max_y), fill = "red")
self.canvas.create_line((self.min_x, self.min_y), (self.max_x,
self.min_y), fill = "red")
self.canvas.create_line((self.min_x, self.max_y), (self.max_x,
self.max_y), fill = "red")
self.canvas.create_line((self.max_x, self.min_y), (self.max_x,
self.max_y), fill = "red")
#Draw balls
for b in self.balls:
obj = self.canvas.create_oval(b.x - b.radius, b.y - b.radius, b.x +
b.radius, b.y + b.radius, outline=b.tk_rgb, fill=b.tk_rgb)
self.ball_handles[b] = obj
self.canvas.update()
#refresh is called to update the state of the simulation
#-each refresh call can be considered one iteration of the simulation
def refresh(self):
#get the next collision
i, j, t, num_collisions_i, num_collision_j = self.PQ.get_next()
#gather the current collisions of the ith and jth ball
current_collisions_i = self.balls[i].count
current_collisions_j = self.balls[j].count
#Check the difference in time between the predicted collision time and
#the current time stamp of the simulation
delta = t - self.time
#If the difference is greater than 1, then just move the balls
if delta > 1.0:
# cap delta to 1.0
for bi in self.balls:
bi.move()
self.canvas.move(self.ball_handles[bi], bi.vx, bi.vy)
self.time += 1.0
#Otherwise a collision has occurred
else:
#Move all balls
for bi in self.balls:
bi.move(delta)
self.canvas.move(self.ball_handles[bi], bi.vx*delta,
bi.vy*delta)
#increment the simulation time stamp
self.time += delta
#Delete the top element within the Priority Queue
self.PQ.delete()
#if i is -1 then this indicates a collision with a vertical wall
#also this if statement checks if the number of collisions recorded
#when the collision returned by PQ.get_next() is equal to the
#number of collisions within the jth ball
#this acts as a test to check if the collision is still valid
if i == -1 and num_collision_j == current_collisions_j:
#compute what happens from the vertical wall collision
self.balls[j].collide_with_vertical_wall()
#update collision times for the jth ball
self.update_collision_times(j)
#if j is -1 then this indicates a collision a horizontal wall
#while also checking if the number of collisions match
#to see if the collision is valid
elif j == -1 and num_collisions_i == current_collisions_i:
#compute what happens from the horizontal wall collision
self.balls[i].collide_with_horizontal_wall()
#update collision times for the ith ball
self.update_collision_times(i)
elif num_collision_j == current_collisions_j and num_collisions_i ==
current_collisions_i:
#Execute collision across the ith and jth ball
self.balls[i].collide_with_ball(self.balls[j])
#update collision times for both the ith and jth ball
self.update_collision_times(i)
self.update_collision_times(j)
#update the canvas to draw the new locations of each ball
self.canvas.update()
self.canvas.after(self.refresh_speed, self.refresh)
def __init__(self, radius, x, y, vx, vy, mass, tk_rgb="#000000"):
self.radius = radius
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.mass = mass
self.tk_rgb = tk_rgb
#since this ball was just initialized, it hasn't had any collisions yet
self.count = 0
return
#move changes the displacement of the ball by the velocity
def move(self, dt=1.0):
self.x += self.vx*dt
self.y += self.vy*dt
return
#check_overlap checks if this ball is overlapping with any other
#ball, it is used to see if a collision has occurred
def check_overlap(self, others):
for b in others:
min_dist = b.radius + self.radius
center_dist = math.sqrt((b.x - self.x)*(b.x - self.x) + \
(b.y - self.y)*(b.y - self.y))
if center_dist < min_dist:
return True
return False
#collide_with_ball computes collision, changing the Ball's velocity
#as well as the other ball's velocity
def collide_with_ball(self, other):
dv_x = other.vx - self.vx
dv_y = other.vy - self.vy
dr_x = other.x - self.x
dr_y = other.y - self.y
sigma = self.radius + other.radius
dv_dr = dv_x * dr_x + dv_y * dr_y
J = 2.0 * self.mass * other.mass * dv_dr/((self.mass +
other.mass)*sigma)
Jx = J * dr_x/sigma
Jy = J * dr_y/sigma
self.vx += Jx/self.mass
self.vy += Jy/self.mass
other.vx -= Jx/other.mass
other.vy -= Jy/other.mass
#Increment the collision count for both balls
self.count += 1
other.count += 1
return
# Compute when an instance of Ball collides with the given Ball other
# Return the timestamp that this will occur
def ball_collision_time(self, other):
dr_x = other.x - self.x
dr_y = other.y - self.y
if dr_x == 0 and dr_y == 0:
return float('inf')
dv_x = other.vx - self.vx
dv_y = other.vy - self.vy
dv_dr = dv_x * dr_x + dv_y * dr_y
if dv_dr > 0:
return float('inf')
dv_dv = dv_x * dv_x + dv_y * dv_y
dr_dr = dr_x * dr_x + dr_y * dr_y
sigma = self.radius + other.radius
d = dv_dr * dv_dr - dv_dv * (dr_dr - sigma * sigma)
# No solution
if d < 0 or dv_dv == 0:
return float('inf')
return - (dv_dr + np.sqrt(d))/dv_dv
#collide_with_horizontal_wall executes the change in the Ball's
#velocity when colliding with a horizontal wall
def collide_with_horizontal_wall(self):
self.vx = -self.vx
self.count += 1
return
#collide_with_vertical_wall executes the change in the Ball's
#velocity when colliding with a vertical wall
def collide_with_vertical_wall(self):
self.vy = -self.vy
self.count += 1
return
# Compute when the instance of Ball collides with a horizontal wall
# Return the time stamp that this will occur
def horizontal_wall_collision_time(self, min_x, max_x):
if self.vx < 0:
# x + delta_t * vx = min_x + radius
return (min_x + self.radius - self.x)/(1.0*self.vx)
if self.vx > 0:
# x + delta_t * vx = max_x - radius
return (max_x - self.radius - self.x)/(1.0*self.vx)
return float('inf')
# Compute when the instance of Ball collides with a vertical wall
# Return the time stamp that this will occur
# Inputs of min_y and max_y
def vertical_wall_collision_time(self, min_y, max_y):
if self.vy < 0:
# y + delta_t * vy = min_y + radius
return (min_y + self.radius - self.y)/(1.0*self.vy)
if self.vy > 0:
# y + delta_t * vy = max_y - radius
return (max_y - self.radius - self.y)/(1.0*self.vy)
return float('inf')
#show_stats will print out the Ball's data
#specifically the radius, position, velocity, mass, and color
def show_stats(self):
print("radius: %f"%self.radius)
print("position: %f, %f"%(self.x, self.y))
print("velocity: %f, %f"%(self.vx, self.vy))
print("mass: %f"%self.mass)
print("rgb: %s"%self.tk_rgb)
return
#Main script
if __name__ == "__main__":
#Set the parameters for the graphics window and simulation
scale = 800
border = 5
#Set radius range for all balls
max_radius = 20
min_radius = 5
#set number of balls
num_balls = 10
#set refresh rate
refresh_speed = 5
rand.seed(12394)
#create the graphics object
root = Tk()
#Create the painter object
p = Painter(root, scale, border, refresh_speed, None, min_radius,max_radius,
num_balls)
#If you have the commented out files in your directory then
#uncomment them to see what they do
#p = Painter(root, scale, border, refresh_speed, 'wallbouncing.txt')
#p = Painter(root, scale, border, refresh_speed, 'p10.txt')
#p = Painter(root, scale, border, refresh_speed, 'billiards4.txt')
#p = Painter(root, scale, border, refresh_speed, 'squeeze.txt')
#run the Painter main loop function (calls refresh many times)
root.mainloop()
import pygame
import random
import numpy as np
import matplotlib.pyplot as plt
import math
number_of_particles = 70
my_particles = []
background_colour = (255,255,255)
width, height = 500, 500
sigma = 1
e = 1
dt = 0.1
v = 0
a = 0
r = 1
def r(p1,p2):
dx = p1.x - p2.x
dy = p1.y - p2.y
angle = 0.5 * math.pi - math.atan2(dy, dx)
dist = np.hypot(dx, dy)
return dist
def collide(p1, p2):
dx = p1.x - p2.x
dy = p1.y - p2.y
dist = np.hypot(dx, dy)
if dist < (p1.size + p2.size):
tangent = math.atan2(dy, dx)
angle = 0.5 * np.pi + tangent
angle1 = 2*tangent - p1.angle
angle2 = 2*tangent - p2.angle
speed1 = p2.speed
speed2 = p1.speed
(p1.angle, p1.speed) = (angle1, speed1)
(p2.angle, p2.speed) = (angle2, speed2)
overlap = 0.5*(p1.size + p2.size - dist+1)
p1.x += np.sin(angle) * overlap
p1.y -= np.cos(angle) * overlap
p2.x -= np.sin(angle) * overlap
p2.y += np.cos(angle) * overlap
def LJ(r):
return -24*e*((2/r*(sigma/r)**12)-1/r*(sigma/r)**6)
def verlet():
a1 = -LJ(r(p1,p2))
r = r + dt*v+0.5*dt**2*a1
a2 = -LJ(r(p1,p2))
v = v + 0.5*dt*(a1+a2)
return r, v
class Particle():
def __init__(self, (x, y), size):
self.x = x
self.y = y
self.size = size
self.colour = (0, 0, 255)
self.thickness = 1
self.speed = 0
self.angle = 0
def display(self):
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
def move(self):
self.x += np.sin(self.angle)
self.y -= np.cos(self.angle)
def bounce(self):
if self.x > width - self.size:
self.x = 2*(width - self.size) - self.x
self.angle = - self.angle
elif self.x < self.size:
self.x = 2*self.size - self.x
self.angle = - self.angle
if self.y > height - self.size:
self.y = 2*(height - self.size) - self.y
self.angle = np.pi - self.angle
elif self.y < self.size:
self.y = 2*self.size - self.y
self.angle = np.pi - self.angle
screen = pygame.display.set_mode((width, height))
for n in range(number_of_particles):
x = random.randint(15, width-15)
y = random.randint(15, height-15)
particle = Particle((x, y), 15)
particle.speed = random.random()
particle.angle = random.uniform(0, np.pi*2)
my_particles.append(particle)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(background_colour)
for i, particle in enumerate(my_particles):
particle.move()
particle.bounce()
for particle2 in my_particles[i+1:]:
collide(particle, particle2)
particle.display()
pygame.display.flip()
pygame.quit()
I wanted to simulate particles by Lennard-Jones potential. My problem with this code is that I do not know how to use the Verlet algorithm.
I do not know where I should implement the Verlet algorithm; inside the class or outside?
How can I use velocity from the Verlet algorithm in the move method?
Is my implementation of the Verlet algorithm correct, or should I use arrays for saving results?
What else should I change to make it work?
You can keep the dynamical variables, position and velocity, inside the class instances, however then each class needs an acceleration vector to accumulate the force contributions. The Verlet integrator has the role of a controller, it acts from outside on the collection of all particles. Keep the angle out of the computations, the forth and back with trigonometric functions and their inverses is not necessary. Make position, velocity and acceleration all 2D vectors.
One way to implement the velocity Verlet variant is (see https://stackoverflow.com/tags/verlet-integration/info)
verlet_step:
v += a*0.5*dt;
x += v*dt; t += dt;
do_collisions(t,x,v,dt);
a = eval_a(x);
v += a*0.5*dt;
do_statistics(t,x,v);
which supposes a vectorized variant. In your framework, there would be some iterations over the particle collection to include,
verlet_step:
for p in particles:
p.v += p.a*0.5*dt; p.x += p.v*dt;
t += dt;
for i, p1 in enumerate(particles):
for p2 in particles[i+1:]:
collide(p1,p2);
for i, p1 in enumerate(particles):
for p2 in particles[i+1:]:
apply_LJ_forces(p1,p2);
for p in particles:
p.v += p.a*0.5*dt;
do_statistics(t,x,v);
No, you could not have done nothing wrong since you did not actually call the Verlet function to update position and velocity. And no, a strict vectorization is not necessary, see above. The implicit vectorization via the particles array is sufficient. You would only need a full vectorization if you wanted to compare with the results of a standard integrator like those in scipy.integrate using the same model to provide the ODE function.
Code with some add-ons but without collisions, desingularized potential
import pygame
import random
import numpy as np
import matplotlib.pyplot as plt
import math
background_colour = (255,255,255)
width, height = 500, 500
aafac = 2 # anti-aliasing factor screen to off-screen image
number_of_particles = 50
my_particles = []
sigma = 10
sigma2 = sigma*sigma
e = 5
dt = 0.1 # simulation time interval between frames
timesteps = 10 # intermediate invisible steps of length dt/timesteps
def LJ_force(p1,p2):
rx = p1.x - p2.x
ry = p1.y - p2.y
r2 = rx*rx+ry*ry
r2s = r2/sigma2+1
r6s = r2s*r2s*r2s
f = 24*e*( 2/(r6s*r6s) - 1/(r6s) )
p1.ax += f*(rx/r2)
p1.ay += f*(ry/r2)
p2.ax -= f*(rx/r2)
p2.ay -= f*(ry/r2)
def Verlet_step(particles, h):
for p in particles:
p.verlet1_update_vx(h);
p.bounce()
#t += h;
for i, p1 in enumerate(particles):
for p2 in particles[i+1:]:
LJ_force(p1,p2);
for p in particles:
p.verlet2_update_v(h);
class Particle():
def __init__(self, (x, y), (vx,vy), size):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.size = size
self.colour = (0, 0, 255)
self.thickness = 2
self.ax = 0
self.ay = 0
def verlet1_update_vx(self,h):
self.vx += self.ax*h/2
self.vy += self.ay*h/2
self.x += self.vx*h
self.y += self.vy*h
self.ax = 0
self.ay = 0
def verlet2_update_v(self,h):
self.vx += self.ax*h/2
self.vy += self.ay*h/2
def display(self,screen, aa):
pygame.draw.circle(screen, self.colour, (int(aa*self.x+0.5), int(aa*self.y+0.5)), aa*self.size, aa*self.thickness)
def bounce(self):
if self.x > width - self.size:
self.x = 2*(width - self.size) - self.x
self.vx = - self.vx
elif self.x < self.size:
self.x = 2*self.size - self.x
self.vx = - self.vx
if self.y > height - self.size:
self.y = 2*(height - self.size) - self.y
self.vy = - self.vy
elif self.y < self.size:
self.y = 2*self.size - self.y
self.vy = - self.vy
#------------ end class particle ------------
#------------ start main program ------------
for n in range(number_of_particles):
x = 1.0*random.randint(15, width-15)
y = 1.0*random.randint(15, height-15)
vx, vy = 0., 0.
for k in range(6):
vx += random.randint(-10, 10)/2.
vy += random.randint(-10, 10)/2.
particle = Particle((x, y),(vx,vy), 10)
my_particles.append(particle)
#--------- pygame event loop ----------
screen = pygame.display.set_mode((width, height))
offscreen = pygame.Surface((aafac*width, aafac*height))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
offscreen.fill(background_colour)
for k in range(timesteps):
Verlet_step(my_particles, dt/timesteps)
for particle in my_particles:
particle.display(offscreen, aafac)
pygame.transform.smoothscale(offscreen, (width,height), screen)
pygame.display.flip()
pygame.quit()
I'm working on a 2D python game project for my CS class, and I've hit a bump, I don't know what the problem is:
The project is a large part of my grade, and up until now I've had an A+
This project is incredibly frustrating
NEW
ok so i've got everything working so far, except for some reason My protaganist() is stuck at the top left corner of the game screen !
Also, i need ideas on how to create a jump action
If anyone could help I would be incredibly grateful!
I am importing a game engine my teacher made available from his book website, but i it is too long for me to add but i will try to add some of it at the bottom
Here is all my code:
import gameEngine
import pygame
import math
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.mixer.init()
sndAtk = pygame.mixer.Sound("OOT_AdultLink_Attack1.wav")
#goal is to create a game
#must have menu to start game
#menu should have a start and quit button.. start runs gaming operations and quit exits program
#sprites for character and enemies and bullets maybe, use one large image and simply move visibiliy
#this saves memory as 1 image is loaded instead of many
"""
class game(gameEngine.scene):
def __init__(self, scene):
self.background()
self.sprites["spawn.gif", "badguys.gif"]
"""
"""
protaganist is our hero sprite
should run left and right, jump left and right
and attack left and right...
I might add in the bow and jump attack
"""
class scrollinggrass(gameEngine.SuperSprite):
def __init__(self, scene):
gameEngine.SuperSprite.__init__(self, scene)
self.setImage("gamebackground.jpg")
self.rect.centerx = 20
self.rect.centery = 500
self.rect = self.image.get_rect()
self.dx = 10
self.dy = 0
self.checkKeys()
def checkKeys(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_RIGHT]:
print("working")
self.forward(3)
run.play()
if keys[pygame.K_LEFT]:
self.forward(-3)
class hearts(gameEngine.SuperSprite):
def __init__(self, scene):
gameEngine.SuperSprite.__init__(self, scene)
self.setImage("heart.png")
self.setTransparentColor = self.imageMaster.get_at((1,1))
self.imageMaster.set_colorkey(self.setTransparentColor)
self.setPosition((550 , 30))
class badguy(gameEngine.SuperSprite):
def __init__(self, scene):
gameEngine.SuperSprite.__init__(self, scene)
self.setImage("badguy1.png")
self.rect = self.imageMaster.get_rect()
self.health = 2
self.DEAD = 1
self.state = 0
class protaganist(gameEngine.SuperSprite):
def __init__(self, scene):
gameEngine.SuperSprite.__init__(self, scene)
self.imageList = []
self.rect = self.imageMaster.get_rect()
self.STANDING = 0
self.RUNNING = 1
self.ATTACKING = 2
self.JUMPING = 3
self.DEAD = 10
self.imageFrame = 0
self.state = self.STANDING
self.hearts = 1
self.heartPts = self.hearts * 3
self.stats()
self.loadImages()
# self.image = self.imageList[0]
self.checkKeys()
def stats(self):
#sets it up so each heart is essentially 3 hit points
if self.heartPts >= 3:
self.hearts = 1
elif self.heartPts >= 6:
self.hearts = 2
elif self.heartPts == 9:
self.hearts = 3
elif self.heartPts > 9:
self.heartPts = 9
# changes state to dead if hp == 0
if self.heartPts == 0:
self.state = DEAD
def loadImages(self):
self.setPosition((320 , 380))
self.setImage("heroSTANDING.gif")
self.setTransparentColor = self.imageMaster.get_at((1,1))
self.imageMaster.set_colorkey(self.setTransparentColor)
def checkKeys(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_RIGHT]:
self.state = runRight
self.frame += 1
if self.frame >= len(self.imageList):
self.frame = 1
self.image = self.imageList[self.frame]
# self.image = self.image.get_rect()
# self.rect.center = (320, 240)
if keys[pygame.K_LEFT]:
self.state = 1
while keys[pygame.K_g]:
self.state = Attacking
sndAtk.play()
if self.state == self.DEAD:
self.image = self.deadImgList[0]
self.frame += 1
self.image = self.deadImgList[self.frame]
#self.image = self.image.get_rect()
#self.rect.center = (320, 240)
class game(gameEngine.Scene):
def __init__ (self):
gameEngine.Scene.__init__(self)
pygame.display.set_caption("Link's Mediocre Adventure")
background = pygame.Surface(screen.get_size())
background.fill((0, 0, 0))
screen.blit(background, (0, 0))
pro = protaganist(self)
baddy = badguy(self)
baddy1 = badguy(self)
heart = hearts(self)
grass = scrollinggrass(self)
goodlySprites = self.makeSpriteGroup((grass, pro, heart))
baddySprites = self.makeSpriteGroup((baddy, baddy1))
# self.addSpriteGroup(goodlySprites)
self.addGroup((baddySprites))
clock = pygame.time.Clock()
keepGoing = True
while keepGoing:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
keepGoing = False
if pro.state == pro.ATTACKING:
if pro.collidesGroup(baddySprites):
baddy.health -= 1
baddy1.health -= 1
if baddy.health == 0:
baddy.reset()
elif baddy1.health == 0:
baddy.reset()
elif pro.state != pro.ATTACKING:
if pro.collideGroup(baddySprites):
pro.heartPts -= 1
goodlySprites.update()
baddySprites.update()
goodlySprites.draw(screen)
baddySprites.draw(screen)
pygame.display.flip()
def main():
game.start()
if __name__ == "__main__":
game()
game engine
class SuperSprite(pygame.sprite.Sprite):
""" An enhanced Sprite class
expects a gameEngine.Scene class as its one parameter
Use methods to change image, direction, speed
Will automatically travel in direction and speed indicated
Automatically rotates to point in indicated direction
Five kinds of boundary collision
"""
def __init__(self, scene):
pygame.sprite.Sprite.__init__(self)
self.scene = scene
self.screen = scene.screen
#create constants
self.WRAP = 0
self.BOUNCE = 1
self.STOP = 2
self.HIDE = 3
self.CONTINUE = 4
#create a default text image as a placeholder
#This will usually be changed by a setImage call
self.font = pygame.font.Font("freesansbold.ttf", 30)
self.imageMaster = self.font.render(">sprite>", True, (0, 0,0), (0xFF, 0xFF, 0xFF))
self.image = self.imageMaster
self.rect = self.image.get_rect()
#create properties
#most will be changed through method calls
self.x = 200
self.y = 200
self.dx = 0
self.dy = 0
self.dir = 0
self.rotation = 0
self.speed = 0
self.maxSpeed = 10
self.minSpeed = -3
self.boundAction = self.WRAP
self.pressed = False
self.oldCenter = (100, 100)
self.states = {}
self.currentState = "default"
def update(self):
self.oldCenter = self.rect.center
self.checkEvents()
self.__rotate()
self.__calcVector()
self.__calcPosition()
self.checkBounds()
self.rect.center = (self.x, self.y)
def checkEvents(self):
""" overwrite this method to add your own event code """
pass
def __rotate(self):
""" PRIVATE METHOD
change visual orientation based on
rotation property.
automatically called in update.
change rotation property directly or with
rotateBy(), setAngle() methods
"""
oldCenter = self.rect.center
self.oldCenter = oldCenter
self.image = pygame.transform.rotate(self.imageMaster, self.rotation)
self.rect = self.image.get_rect()
self.rect.center = oldCenter
def __calcVector(self):
""" calculates dx and dy based on speed, dir
automatically called in update
"""
theta = self.dir / 180.0 * math.pi
self.dx = math.cos(theta) * self.speed
self.dy = math.sin(theta) * self.speed
self.dy *= -1
def __calcPosition(self):
""" calculates the sprites position adding
dx and dy to x and y.
automatically called in update
"""
self.x += self.dx
self.y += self.dy
def checkBounds(self):
""" checks boundary and acts based on
self.BoundAction.
WRAP: wrap around screen (default)
BOUNCE: bounce off screen
STOP: stop at edge of screen
HIDE: move off stage and wait
CONTINUE: keep going at present course and speed
automatically called by update
"""
scrWidth = self.screen.get_width()
scrHeight = self.screen.get_height()
#create variables to simplify checking
offRight = offLeft = offTop = offBottom = offScreen = False
if self.x > scrWidth:
offRight = True
if self.x < 0:
offLeft = True
if self.y > scrHeight:
offBottom = True
if self.y < 0:
offTop = True
if offRight or offLeft or offTop or offBottom:
offScreen = True
if self.boundAction == self.WRAP:
if offRight:
self.x = 0
if offLeft:
self.x = scrWidth
if offBottom:
self.y = 0
if offTop:
self.y = scrHeight
elif self.boundAction == self.BOUNCE:
if offLeft or offRight:
self.dx *= -1
if offTop or offBottom:
self.dy *= -1
self.updateVector()
self.rotation = self.dir
elif self.boundAction == self.STOP:
if offScreen:
self.speed = 0
elif self.boundAction == self.HIDE:
if offScreen:
self.speed = 0
self.setPosition((-1000, -1000))
elif self.boundAction == self.CONTINUE:
pass
else:
# assume it's continue - keep going forever
pass
def setSpeed(self, speed):
""" immediately sets the objects speed to the
given value.
"""
self.speed = speed
def speedUp(self, amount):
""" changes speed by the given amount
Use a negative value to slow down
"""
self.speed += amount
if self.speed < self.minSpeed:
self.speed = self.minSpeed
if self.speed > self.maxSpeed:
self.speed = self.maxSpeed
def setAngle(self, dir):
""" sets both the direction of motion
and visual rotation to the given angle
If you want to set one or the other,
set them directly. Angle measured in degrees
"""
self.dir = dir
self.rotation = dir
def turnBy (self, amt):
""" turn by given number of degrees. Changes
both motion and visual rotation. Positive is
counter-clockwise, negative is clockwise
"""
self.dir += amt
if self.dir > 360:
self.dir = amt
if self.dir < 0:
self.dir = 360 - amt
self.rotation = self.dir
def rotateBy(self, amt):
""" change visual orientation by given
number of degrees. Does not change direction
of travel.
"""
self.rotation += amt
if self.rotation > 360:
self.rotation = amt
if self.rotation < 0:
self.rotation = 360 - amt
def setImage (self, image):
""" loads the given file name as the master image
default setting should be facing east. Image
will be rotated automatically """
self.imageMaster = pygame.image.load(image)
self.imageMaster = self.imageMaster.convert()
def setDX(self, dx):
""" changes dx value and updates vector """
self.dx = dx
self.updateVector()
def addDX(self, amt):
""" adds amt to dx, updates vector """
self.dx += amt
self.updateVector()
def setDY(self, dy):
""" changes dy value and updates vector """
self.dy = dy
self.updateVector()
def addDY(self, amt):
""" adds amt to dy and updates vector """
self.dy += amt
self.updateVector()
def setComponents(self, components):
""" expects (dx, dy) for components
change speed and angle according to dx, dy values """
(self.dx, self.dy) = components
self.updateVector()
def setBoundAction (self, action):
""" sets action for boundary. Values are
self.WRAP (wrap around edge - default)
self.BOUNCE (bounce off screen changing direction)
self.STOP (stop at edge of screen)
self.HIDE (move off-stage and stop)
self.CONTINUE (move on forever)
Any other value allows the sprite to move on forever
"""
self.boundAction = action
def setPosition (self, position):
""" place the sprite directly at the given position
expects an (x, y) tuple
"""
(self.x, self.y) = position
def moveBy (self, vector):
""" move the sprite by the (dx, dy) values in vector
automatically calls checkBounds. Doesn't change
speed or angle settings.
"""
(dx, dy) = vector
self.x += dx
self.y += dy
self.checkBounds()
def forward(self, amt):
""" move amt pixels in the current direction
of travel
"""
#calculate dx dy based on current direction
radians = self.dir * math.pi / 180
dx = amt * math.cos(radians)
dy = amt * math.sin(radians) * -1
self.x += dx
self.y += dy
def addForce(self, amt, angle):
""" apply amt of thrust in angle.
change speed and dir accordingly
add a force straight down to simulate gravity
in rotation direction to simulate spacecraft thrust
in dir direction to accelerate forward
at an angle for retro-rockets, etc.
"""
#calculate dx dy based on angle
radians = angle * math.pi / 180
dx = amt * math.cos(radians)
dy = amt * math.sin(radians) * -1
self.dx += dx
self.dy += dy
self.updateVector()
def updateVector(self):
#calculate new speed and angle based on dx, dy
#call this any time you change dx or dy
self.speed = math.sqrt((self.dx * self.dx) + (self.dy * self.dy))
dy = self.dy * -1
dx = self.dx
radians = math.atan2(dy, dx)
self.dir = radians / math.pi * 180
def setSpeedLimits(self, max, min):
""" determines maximum and minimum
speeds you will allow through
speedUp() method. You can still
directly set any speed you want
with setSpeed() Default values:
max: 10
min: -3
"""
self.maxSpeed = max
self.minSpeed = min
def dataTrace(self):
""" utility method for debugging
print major properties
extend to add your own properties
"""
print "x: %d, y: %d, speed: %.2f, dir: %.f, dx: %.2f, dy: %.2f" % \
(self.x, self.y, self.speed, self.dir, self.dx, self.dy)
def mouseDown(self):
""" boolean function. Returns True if the mouse is
clicked over the sprite, False otherwise
"""
self.pressed = False
if pygame.mouse.get_pressed() == (1, 0, 0):
if self.rect.collidepoint(pygame.mouse.get_pos()):
self.pressed = True
return self.pressed
def clicked(self):
""" Boolean function. Returns True only if mouse
is pressed and released over sprite
"""
released = False
if self.pressed:
if pygame.mouse.get_pressed() == (0, 0, 0):
if self.rect.collidepoint(pygame.mouse.get_pos()):
released = True
return released
def collidesWith(self, target):
""" boolean function. Returns True if the sprite
is currently colliding with the target sprite,
False otherwise
"""
collision = False
if self.rect.colliderect(target.rect):
collision = True
return collision
def collidesGroup(self, target):
""" wrapper for pygame.sprite.collideany
simplifies checking sprite - group collisions
returns result of collision check (sprite from group
that was hit or None)
"""
collision = pygame.sprite.spritecollideany(self, target)
return collision
def distanceTo(self, point):
""" returns distance to any point in pixels
can be used in circular collision detection
"""
(pointx, pointy) = point
dx = self.x - pointx
dy = self.y - pointy
dist = math.sqrt((dx * dx) + (dy * dy))
return dist
def dirTo(self, point):
""" returns direction (in degrees) to
a point """
(pointx, pointy) = point
dx = self.x - pointx
dy = self.y - pointy
dy *= -1
radians = math.atan2(dy, dx)
dir = radians * 180 / math.pi
dir += 180
return dir
def drawTrace(self, color=(0x00, 0x00, 0x00)):
""" traces a line between previous position
and current position of object
"""
pygame.draw.line(self.scene.background, color, self.oldCenter,
self.rect.center, 3)
self.screen.blit(self.scene.background, (0, 0))
def addState(self, stateName, stateImageFile):
""" Creates a new sprite state with the associated name
and image. Useful to build multi-state sprites.
"""
#load the image
tempImage = pygame.image.load(stateImageFile)
tempImage.convert()
self.states[stateName] = tempImage
def setState(self, stateName):
""" attempts to set the sprite to the indicated state
(image)
"""
self.imageMaster = self.states[stateName]
self.rect = self.imageMaster.get_rect()
self.currentState = stateName
def getState(self):
""" returns the current state name
(default if no states have been explicitly set)
"""
return self.currentState
if pro.state == pro.ATTACKING:
if pro.collidesWith(baddySprites):
baddy.health -= 1
if baddy.health == 0:
baddy.reset()
elif pro.state != pro.ATTACKING:
if pro.collidesWith(baddySprites):
pro.heartPts -= 1
baddySprites is a sprite group, so I bet you have to use collidesGroup instead of collidesWith.
if pro.state == pro.ATTACKING:
if pro.collidesGroup(baddySprites):
baddy.health -= 1
if baddy.health == 0:
baddy.reset()
elif pro.state != pro.ATTACKING:
if pro.collidesGroup(baddySprites):
pro.heartPts -= 1
But even if you do this, it seems like you'll still have problems. Namely, this code only ever deducts health from baddy and not baddy1. I'm assuming sprite groups support iteration. If so, you should perform collision detection independently on each enemy.
for enemy in baddySprites:
if pro.state == pro.ATTACKING:
if pro.collidesWith(enemy):
enemy.health -= 1
if enemy.health == 0:
enemy.reset()
elif pro.state != pro.ATTACKING:
if pro.collidesWith(enemy):
pro.heartPts -= 1