pygame: Random direction of a ball at the start of the game - python

I'm doing the Pong game. However instead of 2 players, I'm doing it for 4 (one on each side of the screen). I need to have the ball randomly "choose" the direction it has to go.
import pygame
import random
class Ball(object):
#classmethod
def init(cls, SCREEN_WIDTH, SCREEN_HEIGHT):
cls.radius = 20
cls.centerx = SCREEN_WIDTH*0.5
cls.centery = SCREEN_HEIGHT*0.5
cls.rect = pygame.Rect(cls.centerx - cls.radius,
cls.centery - cls.radius,
cls.radius * 2,
cls.radius * 2)
# x , y
cls.direction = [random.choice([1, -1]), random.choice([1, -1])]
cls.speed = [5, 8] # x, y
# left, right, top, bottom
cls.hit_edge = [False, False, False, False]
#classmethod
def update(cls, player1, player2, player3, player4, SCREEN_WIDTH,
SCREEN_HEIGHT):
cls.centerx += cls.direction[0] * cls.speed[0]
cls.centery += cls.direction[1] * cls.speed[1]
cls.rect.center = (cls.centerx, cls.centery)
#detects if someone losses
if cls.rect.left <= 0:
cls.hit_edge[0] = True
elif cls.rect.right >= SCREEN_WIDTH-1:
cls.hit_edge[1] = True
elif cls.rect.top <= 0:
cls.hit_edge[2] = True
elif cls.rect.bottom >= SCREEN_HEIGHT-1:
cls.hit_edge[3] = True
#detects collision between players & the ball
if cls.rect.colliderect(player1.rect):
cls.direction[0] = 1
cls.up_speed()
elif cls.rect.colliderect(player2.rect):
cls.direction[0] = -1
cls.up_speed()
elif cls.rect.colliderect(player3.rect):
cls.direction[1] = 1
cls.up_speed()
elif cls.rect.colliderect(player4.rect):
cls.direction[1] = -1
cls.up_speed()
#classmethod
def up_speed(cls):
cls.speed[0] += random.uniform(0, 0.25)
cls.speed[1] += random.uniform(0, 0.25)
#classmethod
def render(cls, SCREEN, color):
pygame.draw.circle(SCREEN, color, cls.rect.center, cls.radius, 0)
To take into account: I had the idea to add a "0" in every random.choice(), although if I do this only function at the beginning, then it will not be able to move in the axis where the "0" . Also I have two types of speeds in X and Y, could be solved by putting a "0.1" in random.choice () but this would make when the game starts the ball goes very slow. As you would do for the ball to start in a random direction (taking into account that the speed of the ball at the start must be the same for all players. If the ball goes at the beginning to the left,and later (in another game) when it starts but the ball goes up has to go at the same speed)

This may be a little over-complicating things, but if you know the speed you want the ball to start with overall, you could use something like this:
Generate random number between 0-1
angle = 360 * random number
xSpeed = startSpeed * sin(angle)
ySpeed = startSpeed * cos(angle)
This will mean that your ball will always travel at the same speed. The only thing that is random is the direction it travels in.

I recommend to use vectors. For the velocity you can just pick an arbitrary start speed like (8, 0) and then rotate the vector by a random angle.
position = pg.math.Vector2(100, 200)
velocity = pg.math.Vector2(8, 0).rotate(random.randrange(360))
To update the position:
position += velocity
Here's an example program that spawns balls with random color and velocity.
import sys
import math
from random import randrange
import pygame as pg
class Ball(pg.sprite.Sprite):
def __init__(self, pos, *groups):
super().__init__(groups)
self.image = pg.Surface((30, 30), pg.SRCALPHA)
col = randrange(256), randrange(256), randrange(256)
pg.draw.circle(self.image, col, (15, 15), 15)
self.rect = self.image.get_rect(center=pos)
self.vel = pg.math.Vector2(8, 0).rotate(randrange(360))
self.pos = pg.math.Vector2(pos)
def update(self):
self.pos += self.vel
self.rect.center = self.pos
if self.rect.left < 0 or self.rect.right > 640:
self.vel.x *= -1
if self.rect.top < 0 or self.rect.bottom > 480:
self.vel.y *= -1
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
sprite_group = pg.sprite.Group()
ball = Ball((320, 240), sprite_group)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEBUTTONDOWN:
sprite_group.add(Ball((320, 240)))
sprite_group.update()
screen.fill((30, 30, 30))
sprite_group.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()

Related

Python pygame sprites collision detection. How to define which sprite withing group collided and effect it's attributes by reducing a point

What am trying to do is to create an Arkanoid game, where the bricks have 3 points of strength each and then they die. The issue is that instead of just the particular brick that gets hit, to lose the points, the whole brick_sprite group is loosing the points. And when one suppose to die, all the previous within the list up to that one dies. I think the issue is that it loops considering the update on line #240. Please check line 65 at def collision(self): under Brick Class. The issue is somewhere there.
"""This is a simple version of arkanoid game"""
import sys
import pygame
import random
# Set colors R G B
white = (255, 255, 255)
black = (0, 0, 0)
orange = (255, 100, 10)
light_blue = (0, 144, 255)
shadow = (192, 192, 192)
purple = (152, 0, 152)
# Display
display_height = 999
display_width = 444
pygame.display.set_caption = ("Arkanoid 1.0")
FPS = 60
# Movement speed
speed = display_width // 60
# Movements
left = (-speed, 0)
right = (speed, 0)
up = (0, speed)
diagonal_left = [-speed, -speed]
diagonal_right = [speed, -speed]
# Game objects dimentions
base_dimentions = (display_width // 5, display_height // 100)
[brick_width, brick_height] = [display_width // 20 * 2, display_height // 100]
brick_dimentions = [brick_width, brick_height]
ball_dimentions = (display_height // 100, display_height // 100)
# Initializing text font
pygame.font.init()
txt_font = pygame.font.SysFont("Score: ", display_height//44)
# Initializing sprite lists
all_sprites = pygame.sprite.Group()
brick_sprites = pygame.sprite.Group()
class Brick(pygame.sprite.Sprite):
def __init__(self, point_value, center):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(brick_dimentions)
self.image.fill(purple)
self.rect = self.image.get_rect()
self.rect.center = center
self.point_value = point_value
def update(self):
self.collision()
def collision1(self): #This works no issue.
# If brick is hit, loses a point
collision = pygame.sprite.spritecollide(ball, brick_sprites, True)
return collision
def collision(self): #Here is the issue.
# If brick is hit, loses a point
collision = pygame.sprite.spritecollide(ball, brick_sprites, False)
if collision:
self.point_value -= 1
if self.point_value == 0:
self.kill() ## BUGGISH ##"""
class Ball(pygame.sprite.Sprite):
"""Initiates a moving ball and its' attributes"""
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(ball_dimentions)
self.image.fill(light_blue)
self.rect = self.image.get_rect()
self.rect.center = self.init_position()
self.direction = random.choice([diagonal_left, diagonal_right])
self.score = 0
def update(self):
self.movement()
def init_position(self):
# Initialize position of the ball
init_position = (board.rect.center[0],
(board.rect.center[1] - (base_dimentions[1] / 2)
- (ball_dimentions[1] / 2)))
return init_position
def collision(self):
# If hit bricks
collision = pygame.sprite.spritecollideany(ball, brick_sprites)
if collision:
self.direction[1] *= -1
self.score += 1
enter code here
def movement(self):
self.containment()
self.rect[1] += self.direction[1]
self.rect[0] += self.direction[0]
self.deflect()
self.ball_loss()
self.collision()
def containment(self):
if self.rect.right >= display_width or self.rect.left <= 0:
self.direction[0] *= -1
if self.rect.top <= 0:
self.direction[1] *= -1
def ball_loss(self):
if self.rect.bottom >= display_height:
self.reset()
bricks_reset()
def reset(self):
self.rect.center = self.init_position()
self.direction[1] *= -1
self.score = 0
def deflect(self):
# If hit base_board, deflect
if (self.rect.bottom == board.rect.top and
(board.rect.left <= self.rect.left <= board.rect.right or
board.rect.left <= self.rect.right <= board.rect.right)):
self.direction[1] *= -1
self.board_ball_interaction()
def board_ball_interaction(self):
# When board is moving, effects balls direction/speed
keystate = pygame.key.get_pressed()
if keystate[pygame.K_LEFT] and board.rect.left > 0:
self.direction[0] -= speed // 2
elif keystate[pygame.K_RIGHT] and board.rect.right < display_width:
self.direction[0] += speed // 2
class Base_board(pygame.sprite.Sprite):
"""Initiates base_board class and it's attributes"""
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(base_dimentions)
self.image.fill(orange)
self.rect = self.image.get_rect()
self.rect.center = (display_width // 2,
display_height - 2 * base_dimentions[1])
self.x_direction = 0
def update(self):
# Up-dates classes' position according to user's imput
self.x_direction = 0
self.movement()
self.rect.x += self.x_direction
def movement(self):
# Creates movement and constrains object within screen dimentions
keystate = pygame.key.get_pressed()
if keystate[pygame.K_LEFT]:
if not self.rect.left <= 0:
self.x_direction = -speed
elif keystate[pygame.K_RIGHT]:
if not self.rect.right >= display_width:
self.x_direction = speed
def shoot(self):
pass
def enlogate(self):
pass
def control():
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# and adding all sprites on lists
board = Base_board()
ball = Ball()
all_sprites.add(board)
all_sprites.add(ball)
def bricks_list_creator():
# Creates and adds bricks into a list
i = 9
point_value = 2 ####
coordinates = [display_width // 20 + brick_width / 6, display_height // 20]
while i > 0:
brick = Brick(point_value, (coordinates)) ####
coordinates[0] += brick_width * 1.1
brick_sprites.add(brick)
i -= 1
return brick_sprites
def bricks_reset():
# Reset brick list
brick_sprites.empty()
bricks_list_creator()
return brick_sprites
def render_text(screen):
text = txt_font.render("Score: {0}".format(ball.score), 1, (0, 0, 0))
return screen.blit(text, (5, 10))
def render_main(screen):
all_sprites.draw(screen)
brick_sprites.draw(screen)
render_text(screen)
# Game main
def main():
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode((display_width, display_height))
bricks_list_creator()
while True:
# Events
clock.tick(FPS)
control()
# Update
brick_sprites.update()
all_sprites.update()
# Render
screen.fill(shadow)
render_main(screen)
pygame.display.flip()
pygame.display.update()
main()
I think the issue is in the update() of your Brick class calling the collision.
The sprite update function is typically used for changing the position or look of your sprite, and is called every frame. So it's not a good place to check for collisions.
A Brick only needs to know its point_value, it doesn't move (AFAIK).
class Brick(pygame.sprite.Sprite):
def __init__(self, point_value, center):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(brick_dimentions)
self.image.fill(purple)
self.rect = self.image.get_rect()
self.rect.center = center
self.point_value = point_value
def takeHit( self, ball_sprite ):
# the ball has collided with *this* brick
self.point_value -= 1
if self.point_value == 0:
self.kill()
Then in Ball.collision() use the pygame.sprite.spritecollide() to get the list of Bricks the Ball has collided with, and reduce their hit points:
class Ball:
# [...]
def collision(self):
# calculate the list of bricks hit
hit_list = pygame.sprite.spritecollide( self, brick_sprites, False )
for brick in hit_list:
brick.takeHit() # may (or may not) kill the brick
Most of the time the hit_list is going to be a single Brick, but depending on the size of the ball, perhaps occasionally it's two bricks.

How to make a collision detector in pygame

I am making a networked game that involves generating random blocks. Players should not be able to move inside the blocks so I created a collision detection function to stop a player moving inside a block. The function is able to stop a player moving into a block from the left and the top but players can still move in from the right and bottom. I also have one more problem that ever since I generated the block the player moves extremely slow.
This is the code for the Player class:
class player:
def __init__(self, x, y, width, height, colour):
self.width = width # dimensions of player
self.height = height # dimensions of player
self.x = x # position on the screen
self.y = y # position on the screen
self.colour = colour # players colour
self.rect = (x, y, width, height) # all the players properties in one
self.speed = 2 # how far/fast you move with each key press
self.direction = ""
def draw(self, win):
pygame.draw.rect(win, self.colour, self.rect)
def move(self, platforms):
keys = pygame.key.get_pressed() # dictionary of keys - values of 0/1
if keys[pygame.K_LEFT]: # move left: minus from x position value
if self.x <= 5:
pass
else:
self.x -= self.speed
self.direction = "Left"
elif keys[pygame.K_RIGHT]: # move right: add to x position value
if self.x == 785:
pass
else:
self.x += self.speed
self.direction = "right"
elif keys[pygame.K_UP]: # move up: minus from y position value
if self.y <= 5:
pass
else:
self.y -= self.speed
self.direction = "up"
elif keys[pygame.K_DOWN]: # move down from
if self.y >= 785:
pass
else:
self.y += self.speed
self.direction = "down"
def update(self, platforms, direction):
self.collision_with_platform(platforms, direction)
self.rect = (self.x, self.y, self.width, self.height) # redefine where the player is
self.update(platforms, self.direction)
This is the collision detector:
def collision_with_platform(self, platforms, direction):
# evaluates if the player is inside a platform, moving the player to it's edge if so
for platform in platforms:
# checks if player's coordinates are inside the platform, for each platform
if platform.rect.left - 20 < self.x < platform.rect.left + 50:
if platform.rect.top - 20 < self.y < platform.rect.top + 50:
# if moving horizontally
if direction == "left" or direction == "right": # evaluates which side of the platform to move the player to
if direction == "left":
self.x = platform.rect.left + 10 # move to the right side
if direction == "right":
self.x = platform.rect.left - 10 # move to the left side
# if moving vertically
elif direction == "down" or direction == "up": # evaluates which side of the platform to move the player to
if direction == "down":
self.y = platform.rect.top - 10 # move to the top side
elif direction == "up":
self.y_pos = platform.rect.top + 50 # move to the bottom side
This is how I generate platforms:
class Platform:
# represents a platform which the player can stand on
def __init__(self, left, top, width, height, colour):
self.rect = pygame.Rect(left, top, width, height) # square representing a single platform
self.colour = colour # platform's colour
def generate_platforms(probability):
# generates platforms randomly
points, platforms = [], []
# generates a list of all possible platform coordinates
# format of points : [(0,0), (0,1), (0,2), ..., (1,0), (1,1), (1,2), ...]
for x in range(0, 600):
for y in range(0, 600):
points.append((x, y))
# randomly selects platforms to create out of list of all possible platforms
for point in points:
if random.randint(1, 100) <= probability: # probability of each individual platform being created
platforms.append(Platform(point[0] * 50, point[1] * 50, 50, 50, (0, 0, 225)))
return platforms # returns the list of generated platforms
This is the main game loop:
def main(): # asking server for updates, checking for events
run = True
n = Network()
startPos = read_pos(n.getPos())
p = player(startPos[0], startPos[1], 10, 10, (255, 0, 0)) # connect, get starting position
p2 = player(0, 0, 10, 10, (0, 0, 255))
platforms = generate_platforms(25)
clock = pygame.time.Clock()
while run:
clock.tick(60)
p2Pos = read_pos(n.send(make_pos((p.x, p.y))))
p2.x = p2Pos[0]
p2.y = p2Pos[1]
p2.update(platforms, p.direction)
for event in pygame.event.get():
if event.type == pygame.QUIT: # quitting condition
run = False
pygame.quit()
p.move(platforms) # move character based off what keys are being pressed
redrawWindow(win, p, p2, platforms)
The problem is you have way more platforms than you think, there is a 25% chance to have a platform on every pixel that is 50 x 50. you have a lot of overlapping platforms. I ran it and got a length of 62516 platforms. I think what you want it
for x in range(0, screen_Width//50):
for y in range(0, screen_Height//50):
if random.randint(1, 100) <= probability: # probability of each individual platform being created
platforms.append(Platform(x * 50, y * 50, 50, 50, (0, 0, 225)))
This only creates platforms every 50 pixels, so none overlap. Running this i got a length of 22, a lot lower and still looks about the same. This will speed up your program considerably. I was getting 1.5 fps but now maxed out fps at 60.
To fix your collisions.
Change your player rect to a pygame rect object
self.rect = pygame.Rect(x, y, width, height) # all the players properties in one
and change the rect when you move
if keys[pygame.K_LEFT]: # move left: minus from x position value
if self.x > 5:
self.x -= self.speed
self.rect.x -= self.speed
self.direction = "left" # lowercase l as everything else was lowercase
then for the collisions:
if self.rect.colliderect(platform.rect):
if direction == "left":
self.rect.left = platform.rect.right # move to the right side
elif direction == "right":
self.rect.right = platform.rect.left # move to the left side
# if moving vertically
elif direction == "down":
self.rect.bottom = platform.rect.top # move to the top side
elif direction == "up":
self.rect.top = platform.rect.bottom # move to the bottom side
also In player.update you dont need to call it again inside it. So just this will do
def update(self, platforms):
self.collision_with_platform(platforms, self.direction)

pygame - problem with killing a sprite in a group

I am trying to create a game using pygame for a school project. I would like obstacles(in this case boxes) which get in the way of the player. The player is able to destroy the boxes which would result in another box to be spawned in a random location at the same height.
I've split the code into 3 seperate modules seperating the sprites, the main code and the settings(game variables).
main:
import pygame as pg
import random
from sprites import *
from settings import *
import os
import sys
import time
class Game:
def __init__(init):#initialising the games properties(window,sound,speed,etc).
pg.init()
pg.mixer.init()
init.clock = pg.time.Clock()
init.screen = pg.display.set_mode((WIDTH,HEIGHT))
pg.display.set_caption(TITLE)
init.clock = pg.time.Clock()
init.running = True
init.font_name = pg.font.match_font(FONT_NAME)
init.data()
def data(load):
load.dir = os.path.dirname(__file__)
def new(new):#starts the game again.
new.score = 0
new.obstacles = pg.sprite.Group()
new.platforms = pg.sprite.Group()
new.bullets = pg.sprite.Group()
new.all_sprites = pg.sprite.Group()
new.player = Player(new)
new.all_sprites.add(new.player)
for plat in PLATFORM_LIST:
p = Platform(*plat)
new.all_sprites.add(p)
new.platforms.add(p)
for obs in OBSTACLE_LIST:
new.obstacle = Obstacle(*obs)
new.all_sprites.add(new.obstacle)
new.obstacles.add(new.obstacle)
new.run()
def run(run):
run.playing = True
while run.playing:
run.cooldown = 0
run.clock.tick(FPS)
run.events()
run.update()
run.draw()
def update(update):
bullet = Bullet
#game update.
update.all_sprites.update()
#spawning obstacles lower half
while len(update.obstacles) < 3:
width = random.randrange(50, 100)
update.obstacle = Obstacle(random.randrange(0, WIDTH - width),HEIGHT-100,100,50)
update.obstacles.add(update.obstacle)
update.all_sprites.add(update.obstacle)
#spawning obstacles randomly throughout the middle half
#spawning obstacles randomly throughout the map upper half
#check if bullet collides with an obstacles.
collide = pg.sprite.groupcollide(update.bullets,update.obstacles,True,False)
if collide:
update.obstacle.obs_health = update.obstacle.obs_health - bullet.damage
if update.obstacle.obs_health == 0:
update.obstacle.kill()
#check if player hits the sides of an obstacle.
if update.player.velocity.x >0:#when moving right.
collide = pg.sprite.spritecollide(update.player,update.obstacles,False)
if collide:
if update.player.pos.y >= collide[0].rect.centery+20:#if the player is above the platform.
update.player.pos.x = collide[0].rect.left - (PLAYER_WIDTH/2)
update.player.velocity.x = 0
update.player.acceleration.y = 0
if update.player.velocity.x <0:#when moving left.
collide = pg.sprite.spritecollide(update.player,update.obstacles,False)
if collide:
if update.player.pos.y >= collide[0].rect.centery:
update.player.pos.x = collide[0].rect.right + (PLAYER_WIDTH/2)
update.player.velocity.x = 0
#check if player hits side of platforms
if update.player.velocity.x >0 and (update.player.velocity.y < 0):#when moving right.
collide = pg.sprite.spritecollide(update.player,update.platforms,False)
if collide:
if update.player.pos.y < collide[0].rect.centery+50:#if the player is below the obstacle.
update.player.pos.x = collide[0].rect.left - (PLAYER_WIDTH/2)
update.player.velocity.x = 0
if update.player.velocity.x <0:#when moving left.
collide = pg.sprite.spritecollide(update.player,update.obstacles,False)
if collide:
if update.player.pos.y > collide[0].rect.centery:
update.player.pos.x = collide[0].rect.right + (PLAYER_WIDTH/2)
update.player.velocity.x = 0
#check if player hits a platform while ascending:
if update.player.velocity.y <0:#only when moving up.
collide = pg.sprite.spritecollide(update.player,update.platforms,False)
if collide:
if update.player.pos.y > collide[0].rect.bottom:
update.player.pos.y = collide[0].rect.bottom + (PLAYER_HEIGHT/2) + PLAYER_JUMP
update.player.velocity.y = 0
#check if a player hits a platform while falling.
if update.player.velocity.y >0:#only while falling will this apply.
collide = pg.sprite.spritecollide(update.player,update.platforms,False)#false allows you to avoid deleting the object you jump into.
if collide:
if update.player.pos.y < collide[0].rect.centery:#if the player is above the center of the platform.
update.player.pos.y = collide[0].rect.top +1
update.player.velocity.y = 0
collide = pg.sprite.spritecollide(update.player,update.obstacles,False)
if collide:
if update.player.pos.y < collide[0].rect.centery:
update.player.pos.y = collide[0].rect.top +1
update.player.velocity.y = 0
#spawning obstacles randomly throughout the map upper half
#spawning obstacles randomly throughout the middle half
def events(events):
events.cooldown += events.clock.get_time()
#processes inputs.
for event in pg.event.get():
#check for window closing.
if event.type == pg.QUIT:#if the 'x' button is clicked
if events.playing:
events.playing = False#stop the game loop.
events.running = False#stop the main loop.
if event.type == pg.KEYDOWN:
if event.key == pg.K_UP:
events.player.jump()
if event.key == pg.K_SPACE:
events.player.bullet_list.append(events.player.shoot())
#print(len(events.player.bullet_list))
def draw(draw):
draw.screen.fill(GREY)# creates a black screen.
draw.draw_text(str(draw.player.PLAYER_HEALTH),24,BLACK,WIDTH/32,HEIGHT /32)
draw.all_sprites.draw(draw.screen)#draws the sprites in the group all_sprites.
#after drawing the screen is flipped.
pg.display.flip()
def start_screen(start):#screen displayed when the game is started.
start.screen.fill(BGCOLOR)
start.draw_text(TITLE,48,WHITE,WIDTH/2,HEIGHT /4)
start.draw_text("Arrows to move,UP to jump", 22,WHITE,WIDTH/2,HEIGHT/2)
start.draw_text("Press a key to play",22,WHITE,WIDTH/2,HEIGHT*3/4)
pg.display.flip()
start.any_key()#temporary key to start system.
def any_key(wait):
waiting = True
while waiting:#a loop is used for the start screen until an action is done.
wait.clock.tick(FPS)#allows animations to
for event in pg.event.get():
if event.type == pg.QUIT:#if the 'x' button is pressed during the start screen.
waiting = False
wait.running = False#stops the main loop.
if event.type == pg.KEYUP:#if any key is released.
waiting = False
def over_screen(over):#displayed when the game ends.
if not over.running:
return#skips the over screen when 'x' button is pressed.
over.screen.fill(BGCOLOR)
over.draw_text('GAME OVER',48,WHITE,WIDTH/2,HEIGHT /4)
def draw_text(self, text, size, color, x, y):
font = pg.font.Font(self.font_name, size)#selects the chosen font.
text_surface = font.render(text, True, color)#creates the text with anti aliasing and the color chosen.
text_rect = text_surface.get_rect()
text_rect.midtop = (x, y)#position of text.
self.screen.blit(text_surface, text_rect)#renders text on screen.
g = Game()
g.start_screen()
while g.running:#the main loop.
g.new()
g.over_screen()
pg.quit()#closes the window.
sprites:
#Sprite class
import random
import pygame as pg
from settings import *
vec = pg.math.Vector2 #creates a 2D Vector which stores the x an y cordinates for the sprites.
class Player(pg.sprite.Sprite):
def __init__(self, game):#create initialise the properties of the sprite.
self.game = game#reference to variable in game class.
pg.sprite.Sprite.__init__(self)#provides functions for the sprite in other functions.
self.image = pg.Surface((PLAYER_WIDTH,PLAYER_HEIGHT))#creates a square for the Player to be used as a hitbox.
self.image.fill(GREEN)#place holder for the player.
self.rect = self.image.get_rect()
self.rect.center = (WIDTH/2),(HEIGHT/4)# allows you to move the character.
self.pos = vec(WIDTH/2,HEIGHT/2)#the center of the sprite.
self.velocity = vec(0,0)#the speed of the player.
self.acceleration = vec(0,0)#allows for the change in speed.
self.facing = 0 #direction the player is looking.
self.current = 0#current direction facing.
self.PLAYER_HEALTH = 1000
self.bullet_list = []
def jump(self):
hits = pg.sprite.spritecollide(self, self.game.platforms, False)
if hits:#only able to jump when colliding with platform.
self.velocity.y += -PLAYER_JUMP
collide = hits = pg.sprite.spritecollide(self, self.game.obstacles, False)
if collide:
self.velocity.y += -PLAYER_JUMP
def shoot(self):
#if game.cooldown > 400:
#cooldown = 0
self.bullet = Bullet(self,self.current,self.rect.centerx, self.rect.top)
self.bullet_list.append(self.bullet)
for bullet in self.bullet_list:
#self.bullet = Bullet(self.current,self.rect.centerx, self.rect.top)#creates bullet postioned in center.
self.game.all_sprites.add(self.bullet)
self.game.bullets.add(self.bullet)
#self.bullet = Bullet(self.current,self.rect.centerx, self.rect.top)#creates bullet postioned in center.
#self.game.all_sprites.add(self.bullet)
#self.game.bullets.add(self.bullet)
def update(self):
self.acceleration = vec(0,PLAYER_GRAV)#resets the position of player when not moving.
keys = pg.key.get_pressed()#inputs a pressed key.
if keys[pg.K_LEFT]:
self.acceleration.x = -PLAYER_ACC
self.facing = -1
self.current = self.facing
if keys[pg.K_RIGHT]:
self.acceleration.x = PLAYER_ACC
self.facing = 1
self.current = self.facing
if self.acceleration.x == 0:#if standing, the previous direction is saved
self.facing = self.current
#print(self.current)
#friction.
self.acceleration.x += self.velocity.x * PLAYER_FRICTION
#equation for displacment.
self.velocity += self.acceleration
self.pos += self.velocity + 0.5 * self.acceleration#moves thes players position to the new x,y co-ordinate.
#boundaries of screen.
if self.rect.right > WIDTH:
self.pos.x = WIDTH -(PLAYER_WIDTH * 0.5)#avoids overlapping the boundary.
self.velocity.x = 0 #avoids player sticking to walls by capping speed at boundaries.
self.acceleration.x = 0 #reduces the amount of 'jitter' when trying to move past boundaries.
if self.rect.left < 0 :
self.pos.x = (PLAYER_WIDTH * 0.5)#avoids overlapping the boundary.
# have to add half the player width to avoid player going into walls.
self.velocity.x = 0 #avoids player sticking to walls by stopping player at boundaries.
self.rect.midbottom = self.pos#tracks the position of the players center.
class Platform(pg.sprite.Sprite,):
def __init__(self, x, y, w, h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w,h))
self.image.fill(BLACK)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
class Obstacle(pg.sprite.Sprite):
def __init__(self,x,y,w,h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w,h))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.obs_health = 100
class Bullet(pg.sprite.Sprite):
damage = 25
def __init__(self,player,current, x, y):
self.player = player#allows the bullet class to use player variables.
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((20,10))
self.image.fill(LBLUE)
self.rect = self.image.get_rect()
self.rect.right = x
self.rect.centery = y + (PLAYER_HEIGHT/2)
#self.damage = 25
self.velocity = vec(0,0)
if current == -1:#when looking left.
self.velocity = vec(-10,0)
if current == 1:#when looking right.
self.velocity = vec(10,0)
def update(self):
self.rect.right += self.velocity.x
#remove when moves of off screen.
if self.rect.right > WIDTH:
self.kill()
for bullet_amount in self.player.bullet_list:
self.player.bullet_list.pop(self.player.bullet_list.index(bullet_amount))
if self.rect.left <0:
self.kill()
for bullet_amount in self.player.bullet_list:
self.player.bullet_list.pop(self.player.bullet_list.index(bullet_amount))
#print(self.rect.x)
settings:
#settings
TITLE = "downpour"
WIDTH = 900
HEIGHT = 500
FPS = 60
FONT_NAME = 'Ariel'
#platforms
PLATFORM_LIST = [(0, HEIGHT - 50, WIDTH, 50),
(WIDTH -225 ,HEIGHT * 3/4 -50,200, 40),#(x,y,width,height)of the platforms.
(0 +25 ,HEIGHT * 3/4 -50,200, 40),
(0 +350,HEIGHT * 3/4 -150,200, 40)]
OBSTACLE_LIST = [(WIDTH/2,HEIGHT -50-50,100,50),(WIDTH/3,HEIGHT -50-50,100,50),(WIDTH/2,HEIGHT -50-50,100,50)]
#player properties
PLAYER_WIDTH = 50
PLAYER_HEIGHT = 50
PLAYER_ACC = 0.55
PLAYER_FRICTION = -0.05
PLAYER_GRAV = 0.8
PLAYER_JUMP = 15
#colors defines
WHITE = (255,255,255)
BLACK = (0,0,0)
GREY = (211,211,211)
RED = (255,0,0)
GREEN = (0,255,0)
BLUE = (0,0,255)
LBLUE = (132,112,255)
BGCOLOR = LBLUE
The problem I have encountered is with spawning a new box after destroying one of the multiple boxes. A box can be destroyed by depleting its health through shooting at it.
Lets say I have 3 boxes: A,B and C. when I try to destroy B or C, box A is the one that is destroyed and respawned.
I feel like it's an obvious answer...
code relating to the obstacle:
creating the class:
class Obstacle(pg.sprite.Sprite):
def __init__(self,x,y,w,h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w,h))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.obs_health = 100
adding it to a Sprite group:
for obs in OBSTACLE_LIST:
new.obstacle = Obstacle(*obs)
new.all_sprites.add(new.obstacle)
new.obstacles.add(new.obstacle)
collisions:
collide = pg.sprite.groupcollide(update.bullets,update.obstacles,True,False)
if collide:
update.obstacle.obs_health = update.obstacle.obs_health - bullet.damage
if update.obstacle.obs_health == 0:
update.obstacle.kill()
spawning a new obstacle:
while len(update.obstacles) < 3:
width = random.randrange(50, 100)
update.obstacle = Obstacle(random.randrange(0, WIDTH - width),HEIGHT-100,100,50)
update.obstacles.add(update.obstacle)
update.all_sprites.add(update.obstacle)
First of all, for all instance methods, it would help the reader if you used the name self instead of all the custom names you're using such as new or update for the first argument.
After that rewrite, you code will look like follows:
collide = pg.sprite.groupcollide(self.bullets,self.obstacles,True,False)
if collide:
self.obstacle.obs_health = self.obstacle.obs_health - bullet.damage
if self.obstacle.obs_health == 0:
self.obstacle.kill()
Now ask yourself, why does the program know that the self.obstacle is the one that collided? Should self.obstacle even exist? It looks like self.obstacle was just used a temporary local variable upon creation of the Game class to add Obstacle's to self.obstacles.
If so just use a local variable as follows:
for obs in OBSTACLE_LIST:
obstacle = Obstacle(*obs)
self.all_sprites.add(obstacle)
self.obstacles.add(obstacle)
At this point, hopefully the error message will make it clear that referencing self.obstacle isn't going to work. pg.sprite.groupcollide returns you a Sprite_dict, so you need to extract the obstacle from collide to figure out what has collided.
#KentShikama Thanks for pointing that out.
I have fixed the issue by using the dictionary.
for obstacles, bullets in collide.items():
obstacles.obs_health = obstacles.obs_health - bullet.damage
if obstacles.obs_health == 0:
obstacles.kill()

Pygame particle effects

So I have this mini particle effect that produces circles that move up. I want to make it look like smoke. I'm having a lot of problems though.
First off I want to make it recursive so that when the particle reaches the top it
returns to its original spot and begins again, what I have works a little but not exactly.
The other thing is I don't feel like it really looks like smoke can anyone suggest changes to make it look better?
Also I want to add this into my game, how would I make this callable for my game so I could just call particle and give it a location and it appears there. Can anyone help with any of this?
My Code
import pygame,random
from pygame.locals import *
xmax = 1000 #width of window
ymax = 600 #height of window
class Particle():
def __init__(self, x, y, dx, dy, col):
self.x = x
self.y = y
self.col = col
self.ry = y
self.rx = x
self.dx = dx
self.dy = dy
def move(self):
if self.y >= 10:
if self.dy < 0:
self.dy = -self.dy
self.ry -= self.dy
self.y = int(self.ry + 0.5)
self.dy -= .1
if self.y < 1:
self.y += 500
def main():
pygame.init()
screen = pygame.display.set_mode((xmax,ymax))
white = (255, 255, 255)
black = (0,0,0)
grey = (128,128,128)
particles = []
for part in range(25):
if part % 2 > 0: col = black
else: col = grey
particles.append( Particle(random.randint(500, 530), random.randint(0, 500), 0, 0, col))
exitflag = False
while not exitflag:
for event in pygame.event.get():
if event.type == QUIT:
exitflag = True
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
exitflag = True
screen.fill(white)
for p in particles:
p.move()
pygame.draw.circle(screen, p.col, (p.x, p.y), 8)
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
main()
I have made some major edits to your code. For starters, I cleaned up your class a great deal. Let's start with the arguments and the __init__ function.
First of all, instead of going down 500 to reset, particles go to the location that was set as their starting point. It's location that it starts out in is now chosen randomly in the __init__ function rather than in the game. I got rid of some of your unnecessary arguments as well.
In the move function of your class, I simplified quite a bit. In order for the particle to detect if it should reset, it simply sees if it's above 0. The going up is just a simple decrease of the y by 1. A change I added in is that the x changes randomly and goes to the right and left. This will make the smoke look a lot better / more realistic.
I didn't make many changes to the rest of your code. I changed your calling of the Particle class to fit the new arguments. I made a ton more particles, once again for visual effect. I also massively decreased the size of the circles drawn for (can you guess it?) visual effect. I added in a clock as well to keep the particles from going at supersonic speed.
Here is the final code. I hope you like it.
import pygame,random
from pygame.locals import *
xmax = 1000 #width of window
ymax = 600 #height of window
class Particle():
def __init__(self, startx, starty, col):
self.x = startx
self.y = random.randint(0, starty)
self.col = col
self.sx = startx
self.sy = starty
def move(self):
if self.y < 0:
self.x=self.sx
self.y=self.sy
else:
self.y-=1
self.x+=random.randint(-2, 2)
def main():
pygame.init()
screen = pygame.display.set_mode((xmax,ymax))
white = (255, 255, 255)
black = (0,0,0)
grey = (128,128,128)
clock=pygame.time.Clock()
particles = []
for part in range(300):
if part % 2 > 0: col = black
else: col = grey
particles.append( Particle(515, 500, col) )
exitflag = False
while not exitflag:
for event in pygame.event.get():
if event.type == QUIT:
exitflag = True
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
exitflag = True
screen.fill(white)
for p in particles:
p.move()
pygame.draw.circle(screen, p.col, (p.x, p.y), 2)
pygame.display.flip()
clock.tick(50)
pygame.quit()
if __name__ == "__main__":
main()
update
In order to add particles into your code, just do what you did in the code above. It works fine. If you wanted to do something to show the smoke starting, just put a pause time into your arguments and inhibit the movement of the smoke until that amount of time has passed. New class with that added in:
class Particle():
def __init__(self, startx, starty, col, pause):
self.x = startx
self.y = starty
self.col = col
self.sx = startx
self.sy = starty
self.pause = pause
def move(self):
if self.pause==0:
if self.y < 0:
self.x=self.sx
self.y=self.sy
else:
self.y-=1
self.x+=random.randint(-2, 2)
else:
self.pause-=1
The code you will need to create new particles:
for part in range(1, A):
if part % 2 > 0: col = black
else: col = grey
particles.append( Particle(515, B, col, round(B*part/A)) )
A and B are variables (I reccomend around 300 for A, B will be the Y value)
The new code will make particles spawn at the start location, and rise continously with no breaks. Hope you enjoy.
I have made a lot of changes to your code, especially in the Particle class.
Although, there are rather puzzling things in this code, it will be more flexible than your current code.
Here,
I have quite literately rewritten you Particle class.
Other than changing the __init__, to take many arguments (7 to be exact),
I have used trigonometry and the math module to move the particle, making it easier to manage (if you are good at math!). And I have also added bounce and draw methods to the Particle, and made the code more readable.
Just like #PygameNerd, I have added a clock, for restricting the max fps.
I haven't changed any event handling, but have used the bounce and draw funcs in the for p in particles: loop.
import pygame, random, math
def radians(degrees):
return degrees*math.pi/180
class Particle:
def __init__(self, (x, y), radius, speed, angle, colour, surface):
self.x = x
self.y = y
self.speed = speed
self.angle = angle
self.radius = 3
self.surface = surface
self.colour = colour
self.rect = pygame.draw.circle(surface,(255,255,0),
(int(round(x,0)),
int(round(y,0))),
self.radius)
def move(self):
""" Update speed and position based on speed, angle """
# for constant change in position values.
self.x += math.sin(self.angle) * self.speed
self.y -= math.cos(self.angle) * self.speed
# pygame.rect likes int arguments for x and y
self.rect.x = int(round(self.x))
self.rect.y = int(round(self.y))
def draw(self):
""" Draw the particle on screen"""
pygame.draw.circle(self.surface,self.colour,self.rect.center,self.radius)
def bounce(self):
""" Tests whether a particle has hit the boundary of the environment """
if self.x > self.surface.get_width() - self.radius: # right
self.x = 2*(self.surface.get_width() - self.radius) - self.x
self.angle = - self.angle
elif self.x < self.radius: # left
self.x = 2*self.radius - self.x
self.angle = - self.angle
if self.y > self.surface.get_height() - self.radius: # bottom
self.y = 2*(self.surface.get_height() - self.radius) - self.y
self.angle = math.pi - self.angle
elif self.y < self.radius: # top
self.y = 2*self.radius - self.y
self.angle = math.pi - self.angle
def main():
xmax = 640 #width of window
ymax = 480 #height of window
white = (255, 255, 255)
black = (0,0,0)
grey = (128,128,128)
pygame.init()
screen = pygame.display.set_mode((xmax,ymax))
clock = pygame.time.Clock()
particles = []
for i in range(1000):
if i % 2:
colour = black
else:
colour = grey
# for readability
x = random.randint(0, xmax)
y = random.randint(0, ymax)
speed = random.randint(0,20)*0.1
angle = random.randint(0,360)
radius = 3
particles.append( Particle((x, y), radius, speed, angle, colour, screen) )
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
break
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
done = True
break
if done:
break
screen.fill(white)
for p in particles:
p.move()
p.bounce()
p.draw()
clock.tick(40)
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
main()

Artificial inteligence in python and pygame for Pong clone

I'm making a program that clones pong based off a tutorial and I already have the program to where it is multiplayer with two separate people. I want to add an AI in the program instead of a player 2. I've been stuck on this for quite some time and would appreciate any help! Here is the code currently:
import sys, os, math, random, pygame
from pygame.locals import *
class paddle(pygame.sprite.Sprite):
def __init__(self, xy):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('assets', 'pong_paddle.gif'))
self.rect = self.image.get_rect()
self.rect.centerx, self.rect.centery = xy
self.movementspeed = 5
self.velocity = 0
def up(self):
# increases vertical velocity
self.velocity -= self.movementspeed
def down(self):
# decreases vertical velocity
self.velocity += self.movementspeed
def move(self, dy):
# moves the paddle y, doesn't go out of top or bottom
if self.rect.bottom + dy > 400:
self.rect.bottom = 400
elif self.rect.top + dy < 0:
self.rect.top = 0
else:
self.rect.y += dy
def update(self):
# makes the paddle move every frame
self.move(self.velocity)
class aiplayer(object):
def __init__(self):
self.bias = random.random() - 0.5
self.hit_count = 0
def update(self, paddle, game,):
if (paddle.rect.centerx < game.bounds.centerx and game.ball.rect.centerx < game.bounds.centerx) or (paddle.rect.centerx > game.bounds.centerx and game.ball.rect.centerx > game.bounds.centerx):
delta = (paddle.rect.centery + self.bias * paddle.rect.height) - game.ball.rect.centery
if abs(delta) > paddle.velocity:
if delta > 0:
paddle.direction = -1
else:
paddle.direction = 1
else:
paddle.direction = 0
else:
paddle.direction = 0
def hit(self):
self.hit_count += 1
if self.hit_count > 6:
self.bias = random.random() - 0.5
self.hit_count = 0
def lost(self):
self.bias = random.random() - 0.5
def won(self):
pass
def render(self, surface):
x, y = self.location
w, h = self.image.get_size()
surface.blitz(self.image, (x-w/2, y-h/2))
class Ball(pygame.sprite.Sprite):
def __init__(self, xy):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('assets', 'pong_ball.gif'))
self.rect = self.image.get_rect()
self.rect.centerx, self.rect.centery = xy
self.maxspeed = 10
self.servespeed = 5
self.velx = 0
self.vely = 0
def reset(self):
self.rect.centerx, self.rect.centery = 400, 200
self.velx = 0
self.vely = 0
def serve(self):
angle = random.randint(-45, 45)
if abs(angle) < 5 or abs(angle-180) < 5:
angle = random.randint(10, 20)
if random.random() > .5:
angle += 180
# this gets the velocity for the x and y coords
x = math.cos(math.radians(angle))
y = math.sin(math.radians(angle))
self.velx = self.servespeed * x
self.vely = self.servespeed * y
class Game(object):
def __init__(self):
pygame.init()
# creates the window
self.window = pygame.display.set_mode((800, 400))
# makes a clock
self.clock = pygame.time.Clock()
# window title
pygame.display.set_caption("Pong")
# tells pygame to watch for these certain events so we can close window
pygame.event.set_allowed([QUIT, KEYDOWN, KEYUP])
self.background = pygame.Surface((800, 400))
self.background.fill((55, 255, 85))
pygame.draw.line(self.background, (0,0,0), (400, 0), (400, 400), 2)
self.window.blit(self.background, (0,0))
#lets the background show up
pygame.display.flip()
#renders the sprites so that they actually show up
self.sprites = pygame.sprite.RenderUpdates()
# makes the paddles, adds to sprite group
self.leftpaddle = paddle((50, 200))
self.sprites.add(self.leftpaddle)
self.rightpaddle = paddle((750, 200))
self.sprites.add(self.rightpaddle)
# makes the ball
self.ball = Ball((400, 200))
self.sprites.add(self.ball)
def run(self):
# this lets the game run using a loop so its always active and never closes
running = True
while running:
self.clock.tick(60)
# pygame event, if user closes the game, then stop running
running = self.handleEvents()
pygame.display.set_caption("Pong %d fps" % self.clock.get_fps())
self.manageBall()
# updates the sprites(paddles, ball)
for sprite in self.sprites:
sprite.update()
# renders the sprites
self.sprites.clear(self.window, self.background)
dirty = self.sprites.draw(self.window)
pygame.display.update(dirty)
def handleEvents(self):
for event in pygame.event.get():
if event.type == QUIT:
return False
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
return False
# controls the right paddle
if event.key == K_w:
self.leftpaddle.up()
if event.key == K_s:
self.leftpaddle.down()
if event.key == K_UP:
self.rightpaddle.up()
if event.key == K_DOWN:
self.rightpaddle.down()
# serves the ball
if event.key == K_SPACE:
if self.ball.velx == 0 and self.ball.vely == 0:
self.ball.serve()
elif event.type == KEYUP:
if event.key == K_w:
self.leftpaddle.down()
if event.key == K_s:
self.leftpaddle.up()
if event.key == K_UP:
self.rightpaddle.down()
if event.key == K_DOWN:
self.rightpaddle.up()
elif event.type ==
return True
def manageBall(self):
# this moves the ball
self.ball.rect.x += self.ball.velx
self.ball.rect.y += self.ball.vely
if self.ball.rect.top < 0:
self.ball.rect.top = 1
# makes the ball bounce
self.ball.vely *= -1
elif self.ball.rect.bottom > 400:
self.ball.rect.bottom = 399
# makes ball bounce off bottom
self.ball.vely *= -1
# resets the ball if it hits the left or right screen
if self.ball.rect.left < 0:
self.ball.reset()
return
elif self.ball.rect.right > 800:
self.ball.reset()
return
collision = pygame.sprite.spritecollide(self.ball, [self.leftpaddle, self.rightpaddle], dokill = False)
if len(collision) > 0:
hitpaddle = collision[0]
# sends the ball back
self.ball.velx *= -1
# makes sure the ball doesn't get stuck in the paddle
self.ball.rect.x += self.ball.velx
# makes the game and runs it
if __name__ == '__main__':
game = Game()
game.run()
Make a function AI in the aiplayer and have it return up or down
int AI(self.position,ball.position):
if self.y>ball.y:
return -1
elif self.y<ball.y:
return 1
then, in the update() code for aiplayer, do something similar to this
self.y += movespeed*AI(self.position,ball.position)
Then, it either moves the paddle up or down depending on where the ball is ( you might need to switch if you add or subtract the movespeed to get the paddle to go the right direction). Also, it might be more effective to use the center of the paddle so that it won't put the top or bottom edge of the paddle at the ball.

Categories