Optimizing and abstracting in pygame [closed] - python

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
What's the best way to abstract and fix this code? I'm relatively new to coding and pygame, and need some help finishing this project. It's due tomorrow, and any help would be greatly appreciated.
My goal is to create a rocket game where my spaceship should dodge blocks and collect coins, and ultimately winning the game after collecting a certain amount of coins and reaching the top.
So far, I'm having trouble with 3 main things:
I hate that I have many lines of the same code, and want to find a way to condense it.
I have a collision problem with a loophole that I can't fix, the spaceship passes through the block instead of colliding with it whenever it passes through the top half of it
I'm wondering what's the best way to add scoring after collecting the coins to create an if statement that will let the user win after collecting a certain amount and reaching the top
I've tried and failed to create a class to hold my characters, and don't know how to assign them different properties like speed
# goals - get the scoring done by adding 1 to the score if the spaceship collides with the block, then deleting the block and printing the new score
# figure out the collision problem, fix the loophole where the spaceship can pass through the block if it is above the bottom of the block
# figure out a way to reduce redundancy by making a class for the stars and the block
import pygame
import time
import random
pygame.init()
screenWidth = 700
screenHeight = 700
wn = pygame.display.set_mode((screenWidth, screenHeight))
pygame.display.set_caption("Maxym's First Adventure")
clock = pygame.time.Clock()
spaceshipImg = pygame.image.load('spaceship.png')
black = (0, 0, 0)
blue = (50, 120, 200)
def startmenu():
intro = True
while intro:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
wn.fill(blue)
messagedisplay("Spaceship")
pygame.display.update()
gameloop()
def thing(thingx, thingy, thingw, thingh, color):
pygame.draw.rect(wn, color, [thingx, thingy, thingw, thingh])
def coin(coinx, coiny, coinw, coinh, color):
pygame.draw.rect(wn, color, [coinx, coiny, coinw, coinh])
def spaceship(x, y):
wn.blit(spaceshipImg, (x, y))
def messagedisplay(text):
font = pygame.font.Font('freesansbold.ttf', 115)
wn.blit(font.render(text, True, (0, 0, 0)), (0, 0))
pygame.display.update()
time.sleep(2)
gameloop()
def win():
messagedisplay("You win")
def crash():
messagedisplay("You crashed")
def collect():
score +=1
def gameloop():
x = screenWidth * .45
y = screenHeight * .8
width = 75
height = 132
velocity = 50
starx = random.randrange(0, screenWidth)
stary = 0
starspeed = 30
starw = 50
starh = 50
star2x = random.randrange(0, screenWidth)
star2y = 0
star2speed = 50
star2w = 80
star2h = 80
star3x = random.randrange(0, screenWidth)
star3y = 0
star3speed = 70
star3w = 30
star3h = 30
star4x = random.randrange(0, screenWidth)
star4y = 0
star4speed = 30
star4w = 200
star4h = 40
coinx = random.randrange(0, screenWidth/2)
coiny = random.randrange(0, screenHeight)
coinw = 20
coinh = 20
coin2x = random.randrange(0, screenWidth/2)
coin2y = random.randrange(0, screenHeight)
coin2w = 20
coin2h = 20
coin3x = random.randrange(screenWidth/2, screenWidth)
coin3y = random.randrange(0, screenHeight)
coin3w = 20
coin3h = 20
coin4x = random.randrange(screenWidth/2, screenWidth)
coin4y = random.randrange(0, screenHeight)
coin4w = 20
coin4h = 20
coin5x = random.randrange(0, screenWidth)
coin5y = random.randrange(0, screenHeight)
coin5w = 20
coin5h = 20
run = True
while run:
pygame.time.delay(100)
wn.fill((255, 255, 255))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
print(event)
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and x > velocity:
x -= velocity
if keys[pygame.K_RIGHT] and x < screenWidth-width-velocity:
x += velocity
if keys[pygame.K_UP] and y > velocity:
y -= velocity
if keys[pygame.K_DOWN] and y < screenHeight-height-velocity:
y += velocity
pygame.draw.rect(wn, (255, 255, 255), (x, y, width, height))
thing(starx, stary, starw, starh, black)
thing(star2x, star2y, star2w, star2h, black)
thing(star3x, star3y, star3w, star3h, black)
thing(star4x, star4y, star4w, star4h, black)
coin(coinx, coiny, coinw, coinh, blue)
coin(coin2x, coin2y, coin2w, coin2h, blue)
coin(coin3x, coin3y, coin3w, coin3h, blue)
coin(coin4x, coin4y, coin4w, coin4h, blue)
coin(coin5x, coin5y, coin5w, coin5h, blue)
stary += starspeed
star2y += star2speed
star3y += star3speed
star4y += star4speed
if stary > screenHeight:
stary = 0 - starh
starx = random.randrange(0, screenWidth)
if star2y > screenHeight:
star2y = 0 - star2h
star2x = random.randrange(0, screenWidth)
if star3y > screenHeight:
star3y = 0 - star3h
if star4y > screenHeight:
star4y = 0 - star4h
score = 0
if y + height >= stary and y + height <= stary + starh or y <= stary + starh and y + height >= stary + starh:
if x + width >= starx and x + width <= starx + starw or x <= starx + starw and x + width >= starx + starw:
crash()
if y + height >= star2y and y + height <= star2y + star2h or y <= star2y + star2h and y + height >= star2y + star2h:
if x + width >= star2x and x + width <= star2x + star2w or x <= star2x + star2w and x + width >= star2x + star2w:
crash()
if y + height >= star3y and y + height <= star3y + star3h or y <= star3y + star3h and y + height >= star3y + star3h:
if x + width >= star3x and x + width <= star3x + star3w or x <= star3x + star3w and x + width >= star3x + star3w:
crash()
if y + height >= star4y and y + height <= star4y + star4h or y <= star4y + star4h and y + height >= star4y + star4h:
if x + width >= star4x and x + width <= star4x + star4w or x <= star4x + star4w and x + width >= star4x + star4w:
crash()
if y + height >= coiny and y + height <= coiny + coinh or y <= coiny + coinh and y + height >= coiny + coinh:
if x + width >= coinx and x + width <= coinx + coinw or x <= coinx + coinw and x + width >= coinx + coinw:
collect()
if y + height >= coin2y and y + height <= coin2y + coin2h or y <= coin2y + coin2h and y + height >= coin2y + coin2h:
if x + width >= coin2x and x + width <= coin2x + coin2w or x <= coin2x + coin2w and x + width >= coin2x + coin2w:
collect()
if y + height >= coin3y and y + height <= coin3y + coin3h or y <= coin3y + coin3h and y + height >= coin3y + coin3h:
if x + width >= coin3x and x + width <= coin3x + coin3w or x <= coin3x + coin3w and x + width >= coin3x + coin3w:
collect()
if y + height >= coin4y and y + height <= coin4y + coin4h or y <= coin4y + coin4h and y + height >= coin4y + coin4h:
if x + width >= coin4x and x + width <= coin4x + coin4w or x <= coin4x + coin4w and x + width >= coin4x + coin4w:
collect()
if y + height >= coin5y and y + height <= coin5y + coin5h or y <= coin5y + coin5h and y + height >= coin5y + coin5h:
if x + width >= coin5x and x + width <= coin5x + coin5w or x <= coin5x + coin5w and x + width >= coin5x + coin5w:
collect()
if y <= 10 and score >= 3:
win()
spaceship(x, y)
pygame.display.update()
clock.tick(60)
startmenu()
gameloop()
pygame.quit()
quit()

Keep values in list instead of variables star, star2, etc. And then you can use for loop to make code shorter.
Instead of thingx, thingy, thingw, thingh (and similar) you should use pygame.Rect() and then you can have
def thing(thing_rect, color):
instead of
def thing(thingx, thingy, thingw, thingh, color):
and
pygame.draw.rect(wn, color, thing_rect)
instead of
pygame.draw.rect(wn, color, [thingx, thingy, thingw, thingh])
pygame.Rect has functions to check collisions so you don't have to write long code in if.
pygame.Rect has rect.bottom to get rect.y + rect.height and rect.right to get rect.x + rect.width

An immediate gain would be made from moving the "coin" into a simple data structure, then keep a list of them, rather than 6 quasi-static objects.
This allows you to loop over a list of coins, with only a single set of testing code. Consider the snippet:
MAX_COINS = 6
all_coins = []
def addCoin( colour=(0,30,240), coinw=20, coinh=20 ):
global all_coins
coinx = random.randrange(0, screenWidth/2)
coiny = random.randrange(0, screenHeight)
all_coins.append( [ colour, pygame.Rect( coinx, coiny, coinw, coinh ) ] )
def drawCoin( window, coin ):
coin_colour = coin[0]
coin_rect = coin[1]
pygame.draw.rect( window, coin_colour, coin_rect )
...
# Create 6 coins, or add more if one/some collected
for i in range( MAX_COINS - len( all_coins ) ):
addCoin()
# Draw all coins
for coin in all_coins:
drawCoin( screen, coin )
# Check coin collision with player
for coin in all_coins:
player_rect = pygame.Rect( x, y, width, height )
coin_rect = coin[1]
if ( pygame.Rect.colliderect( player_rect, coin_rect ) ):
all_coins.del( coin ) # remove the coin from the coin-list
collect()
This code presents a coin-solution which perhaps has a few less lines than the existing code, but now supports any number of coins, and protects the code from typo-bugs. Furthermore if the collision code needs to be changed, there is only one-set of logic to analyse and fix. A nearly identical change could be made for the Star objects (maybe even just store the type into the coin-like object, and use it for both).
Of course this code closely matches the workings of the already-existing PyGame Sprite library. Refactoring to use sprites would be an even better change.

Related

I am trying to permanently 'kill' targets when hit with a bullet but they re-appear after using '.kill()' (PYGAME)

I have uploaded quite a few questions recently and I think people are getting tired of me but I am bad at programming and am trying to code a game for A-level coursework so I need all the help I can get to learn the language. Anyway, I'll show a few relevant bits of code below for reference. I use all_targets because I want to add delay using threshold so they don't all spawn simultaneously. I think this is causing the targets to be re-added to the sprite group but I can't resolve it. I want targets to be removed from target_sprites when hit with a bullet. I have tried using booleans to make it so that if destroyed == True:
all_bullets.remove(item) but it does not seem to work. Any help is appreciated.
class Target(pygame.sprite.Sprite):
def __init__(self, width, height, offset, threshold):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([width, height])
self.image = target_img
self.rect = self.image.get_rect()
self.rect.center = (self.rect.x + 50, self.rect.y + offset)
self.threshold = threshold
target_sprites = pygame.sprite.Group()
target_1 = Target(100, 100, 100, 0)
target_2 = Target(100, 100, 300, 150)
target_3 = Target(100, 100, 200, 300)
target_4 = Target(100, 100, 100, 450)
target_5 = Target(100, 100, 400, 600)
target_6 = Target(100, 100, 250, 750)
#Function to add delay between targets spawning.
def target_delay():
global clock
clock += 1
for item in all_targets:
if clock >= item.threshold:
target_sprites.add(item)
#Function to make targets move each time screen refreshes.
def movement():
for item in target_sprites:
item.rect.x += 1
for item in all_bullets_keep:
if item['y']-30 < (target_1.rect.y) + 100 and item['y']+30 > target_1.rect.y:
if item['x']+10 > target_1.rect.x and item['x']-30 < (target_1.rect.x) + 100:
target_1.kill()
if item['y']-30 < (target_2.rect.y) + 100 and item['y']+30 > target_2.rect.y:
if item['x']+10 > target_2.rect.x and item['x']-30 < (target_2.rect.x) + 100:
target_2.kill()
if item['y']-30 < (target_3.rect.y) + 100 and item['y']+30 > target_3.rect.y:
if item['x']+10 > target_3.rect.x and item['x']-30 < (target_3.rect.x) + 100:
target_3.kill()
if item['y']-30 < (target_4.rect.y) + 100 and item['y']+30 > target_4.rect.y:
if item['x']+10 > target_4.rect.x and item['x']-30 < (target_4.rect.x) + 100:
target_4.kill()
if item['y']-30 < (target_5.rect.y) + 100 and item['y']+30 > target_5.rect.y:
if item['x']+10 > target_5.rect.x and item['x']-30 < (target_5.rect.x) + 100:
target_5.kill()
if item['y']-30 < (target_6.rect.y) + 100 and item['y']+30 > target_6.rect.y:
if item['x']+10 > target_6.rect.x and item['x']-30 < (target_6.rect.x) + 100:
target_6.kill()
You don't need the threshold attribute. Remove it:
class Target(pygame.sprite.Sprite):
def __init__(self, width, height, offset):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([width, height])
self.image = target_img
self.rect = self.image.get_rect()
self.rect.center = (self.rect.x + 50, self.rect.y + offset)
Create an empty sprite group:
target_sprites = pygame.sprite.Group()
The number of milliseconds since pygame.init() can be retrieved by pygame.time.get_ticks(). See pygame.time module. Use the ticks to spawn the sprites at interval in the main application loop (target_delay has to be called in the appliction loop):
offsets = [100, 300, 200, 100, 400, 150]
next_target_time = 0
threshold = 1000 # 1000 milliseconds = 1 second
def target_delay():
global next_target_time, offsets
current_time = pygame.time.get_ticks()
if current_time > next_target_time and len(offsets) > 0:
target_sprites.add(Target(100, 100, offsets[0]))
offsets.pop(0)
next_target_time = current_time + threshold
Evaluate if a target has to be killed (kill) in a loop:
for item in all_bullets_keep:
for target in target_sprites:
if item['y']-30 < (target.rect.y) + 100 and item['y']+30 > target.rect.y:
if item['x']+10 > target.rect.x and item['x']-30 < (target.rect.x) + 100:
target.kill()
I don't know if this helps, but there is a sprite function target_1.alive() that tells you whether the sprite has been killed or not. You can use this to detect first whether or not to draw it, and second, when all of them have been killed, so you know when to end the game.

Ensuring there is always a platform for player to jump on

I am making a game where the player has to jump on platforms. However i am having a problem ensuring that there is always a tile for the player to step on. I created platforms this way:
p1 = Platforms(random.randint(0, 200), -100)
p2 = Platforms(random.randint(200, 400),-250)
p3 = Platforms(random.randint(400, 600),-600)
p4 = Platforms(random.randint(600, 800),-400)
p5 = Platforms(random.randint(800, 1000),-500)
p6 = Platforms(random.randint(800, 1000),-300)
random.randint(n, n) is the x position of the player and the numbers after comma is the y position of the player. How can i ensure that there is atleast 4 platforms on the screen withn 300 x-distance and 200 y-distance of each other (since my window size is 1200, 600) while also giving an impression that they are random and not not always in the same position. Thanks
Also here is my platform class for any references needed:
class Platforms:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = 100
self.height = 20
self.speed_list = [0.5, 0.8, 1]
def draw(self):
pygame.draw.rect(g.d, (3, 124, 23), (self.x, self.y, self.width, self.height))
def move(self):
self.y += random.choice(self.speed_list)
def reset(self):
if p1.y > 600:
p1.y = -100
p1.x = random.randint(0, 200)
if p2.y > 600:
p2.y = -250
p2.x = random.randint(200, 400)
if p3.y > 600:
p3.y = -600
p3.x = random.randint(400, 600)
if p4.y > 600:
p4.y = -400
p4.x = random.randint(600, 800)
if p5.y > 600:
p5.y = -500
p5.x = random.randint(800, 1000)
if p6.y > 600:
p6.y = -300
p6.x = random.randint(800, 1000)
def on_plat(self):
for plat in g.platlist:
if (b.x > plat.x) and (b.x < plat.x + plat.width) or (b.x + b.side > plat.x) and (b.x + b.side < plat.x + plat.width):
if (b.y > plat.y) and (b.y < plat.y + plat.height) or (b.y + b.side > plat.y) and (b.y + b.side < plat.y + plat.height):
b.is_jumping = False
b.y = plat.y - b.side
p1 = Platforms(random.randint(0, 200), -100)
p2 = Platforms(random.randint(200, 400),-250)
p3 = Platforms(random.randint(400, 600),-600)
p4 = Platforms(random.randint(600, 800),-400)
p5 = Platforms(random.randint(800, 1000),-500)
p6 = Platforms(random.randint(800, 1000),-300)
g.platlist = [p1, p2, p3, p4, p5, p6]
while True:
for plats in g.platlist:
plats.draw()
plats.move()
plats.reset()
plats.on_plat()
A simple workaround is to make a loop and take the last plateform position.
Something like:
platforms = []
for i in range(6):
x, y = 0, 0
if (i > 0):
lastplatform = platforms[i - 1]
x, y = platforms[i - 1].x, platforms[i - 1].y
x += random.randint(0, 200)
y += random.randint(-100, 100)
platforms.append(Platforms(x, y))
You can make additional check to prevent your plateform being vertically out of screen by changing the range of the y random number generation.

Snake segments that "flow" rather than "snap" to the snake's head position

I am trying to make a snake game in python and I would like the segments of the snake to flow when the user presses the WASD keys rather than the segments snapping to the user's desired direction
import pygame
import random
import time
pygame.init()
win = pygame.display.set_mode((800,600))
pygame.display.set_caption("Pygame")
clock = pygame.time.Clock()
x = 30
y = 30
x2 = x
y2 = random.randrange(1,601-30)
vel = 2
run = True
facing = 0
direction = 0
text = pygame.font.SysFont('Times New Roman',30)
score = 0
segments = []
green = ((0,128,0))
white = ((255,255,255))
counting = 0
segmentTime = time.time()
class segmentClass():
def __init__(self,x,y,pos,color):
self.x = x
self.y = y
self.pos = pos
self.color = color
def draw(self,win):
pygame.draw.rect(win,(self.color),(self.x,self.y,30,30))
def gameOver():
global run
run = False
def segmentGrowth():
global x2
global y2
global score
global vel
global ammount
segments.append(segmentClass(x,y,len(segments)+1,green))
ammount = 0
x2 = random.randrange(1,801-30)
y2 = random.randrange(1,601-30)
score += 1
print(vel)
while run:
currentTime = time.time()
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
vel += (score*0.0001)
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
if direction != 1:
direction = 1
facing = -1
if keys[pygame.K_s]:
if direction != 1:
direction = 1
facing = 1
if keys[pygame.K_a]:
if direction != 0:
direction = 0
facing = -1
if keys[pygame.K_d]:
if direction != 0:
direction = 0
facing = 1
if direction == 1:
y += (vel*facing)
else:
x += (vel*facing)
if x > x2 and x < x2 + 30 or x + 30 > x2 and x + 30 < x2 + 30:
if y == y2:
segmentGrowth()
if y > y2 and y < y2 + 30 or y + 30 > y2 and y + 30 < y2 + 30:
segmentGrowth()
if y > y2 and y < y2 + 30 or y + 30 > y2 and y + 30 < y2 + 30:
if x == x2:
segmentGrowth()
if x > x2 and x < x2 + 30 or x + 30 > x2 and x + 30 < x2 + 30:
segmentGrowth()
if x > 800-30 or y > 600-30 or x < 0 or y < 0:
gameOver()
win.fill((0,0,0))
for segment in segments:
if direction == 0: #X value
if facing == 1: #Right
segment.x = x - (35 * segment.pos)
segment.y = y
else: #Left
segment.x = x + (35 * segment.pos)
segment.y = y
else: #Y value
if facing == -1: #Up
segment.y = y + (35 * segment.pos)
segment.x = x
else:#Down
segment.y = y - (35 * segment.pos)
segment.x = x
for segment in segments:
segment.draw(win)
scoreDisplay = text.render(str(score),1,(255,255,255))
win.blit(scoreDisplay,(760,0))
pygame.draw.rect(win,(0,128,0),(x,y,30,30))
pygame.draw.rect(win,(255,0,0),(x2,y2,30,30))
pygame.display.update()
pygame.quit()
How it works is there is a list of segments and a class for information of each segment (ie x, y, etc). I append to that list an instance of the segment class whenever the user has collided with the red cube. I have this code:
for segment in segments:
if direction == 0: #X value
if facing == 1: #Right
segment.x = x - (35 * segment.pos)
segment.y = y
else: #Left
segment.x = x + (35 * segment.pos)
segment.y = y
else: #Y value
if facing == -1: #Up
segment.y = y + (35 * segment.pos)
segment.x = x
else:#Down
segment.y = y - (35 * segment.pos)
segment.x = x
That will move all segments of the snake all at once when the player decides what direction they want the snake to move. However, the segments are snapping immediately to the x position of the head rather than moving one at a time, smoothly. If someone could help me out with this that would be great. Thanks!
Nice game. I recommend to create a list of points, which is a list of tuples of the snakes head positions ((x, y)). Add every position to the list:
pts = []
while run:
# [...]
pts.append((x, y))
Create a function which calculates the position of a part of the snake by its index (i) counted to the head of the snake. The distance to the head has to be lenToI = i * 35.
The distance between to points can be calculated by the Euclidean distance (math.sqrt((px-pnx)*(px-pnx) + (py-pny)*(py-pny)), where the points are (px, py) and (pnx, pny). If the sum of the distances between the points (lenAct) exceeds the length to point (lenToI), then the position of part i is found:
def getPos(i):
global pts
lenToI = i * 35
lenAct = 0
px, py = pts[-1]
for j in reversed(range(len(pts)-1)):
px, py = pts[j]
pnx, pny = pts[j+1]
lenAct += math.sqrt((px-pnx)*(px-pnx) + (py-pny)*(py-pny))
if lenAct >= lenToI:
break
return (px, py)
Write another function cutPts, which deletes the points from the list, which ar not further required:
def cutPts(i):
global pts
lenToI = i * 35
lenAct = 0
cut_i = 0
px, py = pts[0]
for j in reversed(range(len(pts)-1)):
px, py = pts[j]
pnx, pny = pts[j+1]
lenAct += math.sqrt((px-pnx)*(px-pnx) + (py-pny)*(py-pny))
if lenAct >= lenToI:
break
cut_i = j
del pts[:cut_i]
Update the positions of the segments in a loop:
pts.append((x, y))
for i in range(len(segments)):
segments[i].x, segments[i].y = getPos(len(segments)-i)
cutPts(len(segments)+1)
Regarding the comment:
how would I go about calling the gameOver() function if the head of the snake touches any of its segments? I tried using an if statement to check for collision (the same way I did for the apple) using segment.x and segment.y but this won't work since the second segment of the snake always overlaps the head when the snake moves.
Note, the head can never "touch" the first segment, except the direction is changed to the reverse direction, but this case can be handled by an extra test, with ease.
It is sufficient to check if the head "hits" any segment except the fist one which connects to the head.
Use pygame.Rect.colliderect to check for the intersection of rectangular segments:
def selfCollide():
for i in range(len(segments)-1):
s = segments[i]
if pygame.Rect(x, y, 30, 30).colliderect(pygame.Rect(s.x, s.y, 30, 30)):
return True
return False
if selfCollide():
gameOver()

Elastic collision between moving particles in python: why is kinetic energy not conserved?

I am trying to code a particle simulation in pygame but am having trouble coding the collisions between particles. All the collisions are elastic so kinetic energy should be conserved. However, I am experiencing two main problems:
Particles speed up and up until they get out of control
Particles clump together and stop moving
I would really appreciate any insight to help solve these problems.
I used this document (http://www.vobarian.com/collisions/2dcollisions2.pdf) to help calculate the new velocities of the particles after the collision. The mass of all the particles is the same so I have ignored their mass in my calculations.
Here is my code so far:
import pygame
pygame.init()
import random
import numpy
HEIGHT = 500
WIDTH = 500
NUM_BALLS = 2
#setup
win = pygame.display.set_mode((WIDTH,HEIGHT))
pygame.display.set_caption("Simulation")
class Particle(object):
def __init__(self, x, y , radius):
self.x = x
self.y = y
self.radius = radius
self.xvel = random.randint(1,5)
self.yvel = random.randint(1,5)
def Draw_circle(self):
pygame.draw.circle(win, (255,0,0), (self.x, self.y), self.radius)
def redrawGameWindow():
win.fill((0,0,0))
for ball in balls:
ball.Draw_circle()
pygame.display.update()
balls = []
for turn in range(NUM_BALLS):
balls.append(Particle(random.randint(20,WIDTH-20),random.randint(20,HEIGHT-20),20))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for ball in balls:
if (ball.x + ball.radius) <= WIDTH or (ball.x - ball.radius) >= 0:
ball.x += ball.xvel
if (ball.x + ball.radius) > WIDTH or (ball.x - ball.radius) < 0:
ball.xvel *= -1
ball.x += ball.xvel
if (ball.y + ball.radius) <= HEIGHT or (ball.y - ball.radius) >= 0:
ball.y += ball.yvel
if (ball.y + ball.radius) > HEIGHT or (ball.y - ball.radius) < 0:
ball.yvel *= -1
ball.y += ball.yvel
#code for collision between balls
for ball in balls:
for i in range(len(balls)):
for j in range (i+1, len(balls)):
d = (balls[j].x-balls[i].x)**2 + (balls[j].y-balls[i].y)**2
if d < (balls[j].radius + balls[i].radius)**2:
print("collision")
n = (balls[i].x-balls[j].x, balls[i].y-balls[j].y) #normal vector
un = n/ numpy.sqrt((balls[i].x-balls[j].x)**2 + (balls[i].y-balls[j].y)**2) #unit normal vector
ut = numpy.array((-un[1],un[0])) #unit tangent vector
u1n = numpy.vdot(un, (balls[j].xvel, balls[j].yvel)) #initial velocity in normal direction for first ball
v1t = numpy.vdot(ut, (balls[j].xvel, balls[j].yvel)) #initial velocity in tangent direction (remains unchanged)
u2n = numpy.vdot(un, (balls[i].xvel, balls[i].yvel)) #second ball
v2t = numpy.vdot(ut, (balls[i].xvel, balls[i].yvel))
v1n = u2n
v2n = u1n
v1n *= un
v2n *= un
v1t *= ut
v2t *= ut
v1 = v1n + v1t #ball 1 final velocity
v2 = v2n + v2t #ball 2 final velocity
balls[j].xvel = int(numpy.vdot(v1, (1,0))) #splitting final velocities into x and y components again
balls[j].yvel = int(numpy.vdot(v1, (0,1)))
balls[i].xvel = int(numpy.vdot(v2, (1,0)))
balls[i].yvel = int(numpy.vdot(v2, (0,1)))
redrawGameWindow()
pygame.time.delay(20)
pygame.quit()
In v1 = v1n + v1t, v1n is a vector, but v1t is a scalar.
So, the value v1t gets added to both components of the v1n vector, which causes the error.
You want to add the tangential velocity as a vector, so you should do just like you did for the normal velocities:
v1n *= un
v2n *= un
v1t *= ut
v2t *= ut
Note that using the same variable for two very different things, the vector and its norm, makes your code more difficult to understand and can lead to errors - as it just did. You should probably rename your variables in a more consistent way, in order to make the differences clearer.
Also, ut must be a numpy array, but you made it a tuple. Consider the difference:
With a tuple
>>> t = (3, 4)
>>> 3*t
(3, 4, 3, 4, 3, 4)
With a np.array:
>>> t = np.array((3, 4))
>>> 3*t
array([ 9, 12])
So, you have to change
ut = (-un[1],un[0])
into
ut = numpy.array((-un[1],un[0]))
The problem where the things would stick together is simple, even though you always checked for collision, the time where it would actually recognise it would be when the circles would be past touching but slightly into each other. The circles now bounce and move a little. However they have not moved enough to completely to not be touching, this mean the bounce again and it goes in a loop. I made code that fixes this, but you have to change the bounce function and use your code, as mine is not that perfect, but functional.
import pygame
pygame.init()
import random
import numpy
HEIGHT = 500
WIDTH = 500
NUM_BALLS = 2
#setup
win = pygame.display.set_mode((WIDTH,HEIGHT))
pygame.display.set_caption("Simulation")
class Particle(object):
def __init__(self, x, y , radius):
self.x = x
self.y = y
self.radius = radius
self.xvel = random.randint(1,5)
self.yvel = random.randint(1,5)
self.colideCheck = []
def Draw_circle(self):
pygame.draw.circle(win, (255,0,0), (int(self.x), int(self.y)), self.radius)
def redrawGameWindow():
win.fill((0,0,0))
for ball in balls:
ball.Draw_circle()
pygame.display.update()
def bounce(v1,v2, mass1 = 1, mass2 = 1):
#i Tried my own bouncing mechanism, but doesn't work completly well, add yours here
multi1 = mass1/(mass1+mass2)
multi2 = mass2/(mass1+mass2)
deltaV2 = (multi1*v1[0]-multi2*v2[0],multi1*v1[1]-multi2*v2[1])
deltaV1 = (multi2*v2[0]-multi1*v1[0],multi2*v2[1]-multi1*v1[1])
print("preVelocities:",v1,v2)
return deltaV1,deltaV2
def checkCollide(circ1Cord,circ1Rad,circ2Cord,circ2Rad):
tryDist = circ1Rad+circ2Rad
actualDist = dist(circ1Cord,circ2Cord)
if dist(circ1Cord,circ2Cord) <= tryDist:
return True
return False
def dist(pt1,pt2):
dX = pt1[0]-pt2[0]
dY = pt1[1]-pt2[1]
return (dX**2 + dY**2)**0.5
balls = []
for turn in range(NUM_BALLS):
balls.append(Particle(random.randint(60,WIDTH-60),random.randint(60,HEIGHT-60),60))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for ball in balls:
if (ball.x + ball.radius) <= WIDTH and (ball.x - ball.radius) >= 0:
ball.x += ball.xvel
else:
ball.xvel *= -1
ball.x += ball.xvel
if (ball.y + ball.radius) <= HEIGHT and (ball.y - ball.radius) >= 0:
ball.y += ball.yvel
else:
ball.yvel *= -1
ball.y += ball.yvel
for ball in balls:
for secBallCheck in balls:
if secBallCheck not in ball.colideCheck and ball!= secBallCheck and checkCollide((ball.x,ball.y),ball.radius,(secBallCheck.x,secBallCheck.y),secBallCheck.radius):
print("COLLIDE")
vel1,vel2 = bounce((ball.xvel,ball.yvel),(secBallCheck.xvel,secBallCheck.yvel))
print(vel1,vel2)
ball.xvel = vel1[0]
ball.yvel = vel1[1]
ball.colideCheck.append(secBallCheck)
secBallCheck.xvel = vel2[0]
secBallCheck.yvel = vel2[1]
secBallCheck.colideCheck.append(ball)
elif not checkCollide((ball.x,ball.y),ball.radius,(secBallCheck.x,secBallCheck.y),secBallCheck.radius):
if ball in secBallCheck.colideCheck:
secBallCheck.colideCheck.remove(ball)
ball.colideCheck.remove(secBallCheck)
redrawGameWindow()
pygame.time.delay(20)
pygame.quit()

How can i make sprites spawn on random locations using pygame?

Ive been trying to make a twist on a dodger-like game, where some blocks make you grow and others make you shrink. I'm also planning to add a red one of which is supposed to make you lose a life(you will have 3 lives starting out), and various others with their own attributes. However Ive hit a bump in the road in having the falling blocks spawn randomly, which is something that would be required in a broad range of games.
My plan is basically that i would want to have the blocks re-spawn at random locations each time and at some point i want the amount of falling blocks to increase as well to further mount the difficulty.
Here is my current progress. Any input greatly appreciated:
import pygame
import random
pygame.init()
win_width = 800
win_height = 600
window = pygame.display.set_mode((win_width,win_height))
pygame.display.set_caption("Roger Dodger")
black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
orange = (255,127,0)
yellow = (255,255,0)
blue = (0,0,255)
clock = pygame.time.Clock()
class Collisions:
def __init__(self, x1,y1,w1,h1,x2,y2,w2,h2):
self.x1 = x1
self.y1 = y1
self.w1 = w1
self.h1 = h1
self.x2 = x2
self.y2 = y2
self.w2 = w2
self.h2 = h2
def checkCol(self):
if (self.x2 + self.w2 >= self.x1 >= self.x2 and self.y2 + self.h2 >= self.y1 >= self.y2):
return True
elif (self.x2 + self.w2 >= self.x1 + self.w1 >= self.x2 and self.y2 + self.h2 >= self.y1 >= self.y2):
return True
elif (self.x2 + self.w2 >= self.x1 >= self.x2 and self.y2 + self.h2 >= self.y1 + self.h1 >= self.y2):
return True
elif (self.x2 + self.w2 >= self.x1 + self.w1 >= self.x2 and self.y2 + self.h2 >= self.y1 + self.h1 >= self.y2):
return True
else:
return False
def yel_col(self):
if Collisions.checkCol(self):
return True
def ora_col(self):
if Collisions.checkCol(self):
return True
class Sprite:
def __init__(self,x,y,width,height, color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def render(self,):
pygame.draw.rect(window,self.color,(self.x,self.y,self.width,self.height))
Sprite1=Sprite(win_width/2 ,win_height-60,30,30, blue)
moveX = 0
sprite2x = random.randrange(30, win_width, 30)
sprite3x = random.randrange(30, win_width, 30)
falling_pos = 0
gameLoop=True
while gameLoop:
for event in pygame.event.get():
if (event.type==pygame.QUIT):
gameLoop=False
if (event.type==pygame.KEYDOWN):
if (event.key==pygame.K_LEFT):
moveX = -3
if (event.key==pygame.K_RIGHT):
moveX = 3
window.fill(white)
ground = pygame.draw.rect(window, black, ((0, win_height-30), (win_width, 30)))
Sprite1.x+=moveX
falling_pos += 3
Sprite2=Sprite(sprite2x,falling_pos,30,30, orange)
Sprite3=Sprite(sprite3x, falling_pos, 30, 30, yellow)
collisions1=Collisions(Sprite1.x,Sprite1.y,Sprite1.width,Sprite1.height,Sprite2.x,Sprite2.y,Sprite2.width,Sprite2.height)
collisions2=Collisions(Sprite1.x,Sprite1.y,Sprite1.width,Sprite1.height,Sprite3.x,Sprite3.y,Sprite3.width,Sprite3.height)
Sprite1.render()
Sprite2.render()
Sprite3.render()
if collisions2.checkCol() and collisions2.yel_col():
if Sprite1.width and Sprite1.height > 30:
Sprite1.width -= 5
Sprite1.height -= 5
Sprite1.y += 5
if collisions1.checkCol() and collisions1.ora_col():
if Sprite1.width and Sprite1.height < 300:
Sprite1.width += 5
Sprite1.height += 5
Sprite1.y -= 5
if Sprite1.x < 0:
Sprite1.x = win_width
elif Sprite1.x > win_width:
Sprite1.x = 0
if falling_pos > win_height:
falling_pos = 0
pygame.display.flip()
clock.tick(120)
pygame.quit()
To make them spawn in random locations use random.randint(a, b) to assign the start position.

Categories