Ok so for our couse,we have pretty much been thrown alot of code and told to make a project of it...been fixing bugs all week.Now i finally got a scrolling background working,but when I add it.The shoot functionality stops working,as well as the controls...I need help as to why this is happening? The project is due in a week and I'm running out of options..
""" Import al the necessary classes/sprites and pygame libs """
import pygame
from sys import exit
from LaserSprite import Laser
from PlayerShipSprite import PlayerShip
from EnenySprite import Asteroid
from Vector_Calc import Vector
from CursorSprite import Cursor
from ScoreBoardSprite import ScoreBoard
import math
""" Set up the screen size and sound mixer """
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.mixer.init()
""" This function updates the score board with
information retrieved from the player class """
def updateScoreBoard(RefreshScore):
(ship, scoreBoard) = RefreshScore
scoreBoard.text = ("Score: %d Fired: %d Shield: %d" %(ship.Get_Score(),ship.Get_Ammo(),ship.Get_Shield()))
""" This function updates the position of the ship on the
screen based on what keys are pressed """
def checkKeys(myData):
(event, ship) = myData
if event.key == pygame.K_LEFT:
print 'LEFT'
ship.MoveLeft()
if event.key == pygame.K_RIGHT:
print 'RIGHT'
ship.MoveRight()
if event.key == pygame.K_UP:
ship.MoveUp()
print 'UP'
if event.key == pygame.K_DOWN:
print 'DOWN'
ship.MoveDown()
""" This is the main game loop, when the game is over the player will
be returned to the introduction screen """
def game():
""" Set up the title of the game, the background size, color and then
blit to the screen """
pygame.display.set_caption("Asteroids Version 2.0")
background = pygame.Surface(screen.get_size())
background.fill((0, 0, 0))
screen.blit(background, (0, 0))
""" Set the mouse cursor to be hidden """
pygame.mouse.set_visible(False)
""" Create a new instance of a player and cursor, here
I have set up the cursor as a class the will be updated during the game """
ship = PlayerShip(screen)
cursor = Cursor()
""" Create a new instance of the ScoreBoard class, this is then
given a text value and a position on the screen. Pay note
as to how the score, ammo and shield are retrieved from the PlayerShip class"""
scoreBoard = ScoreBoard()
scoreBoard.text = ("Score: %d Fired: %d Shield: %d" %(ship.Get_Score(),ship.Get_Ammo(),ship.Get_Shield()))
scoreBoard.center = (320,470)
""" Create empty sprite groups as shown below """
all_sprites_list = pygame.sprite.Group()
laser_list = pygame.sprite.Group()
asteroid_list = pygame.sprite.Group()
""" Create 20 asteroids and add them to the asteroid list group """
for i in range(10):
asteroid = Asteroid()
asteroid_list.add(asteroid)
""" Add the ship, cursor, asteroid list group and the scoreBoard to the
all sprites list. In doing this we can have the transparent effect some
were looking for """
all_sprites_list.add(ship)
all_sprites_list.add(cursor)
all_sprites_list.add(asteroid_list)
all_sprites_list.add(scoreBoard)
""" Set up the refresh rate and key repeat for the game"""
clock = pygame.time.Clock()
pygame.key.set_repeat(10,10)
keepGoing = True
while keepGoing:
b1 = "ocean1.gif"
back = pygame.image.load(b1)
back2 = pygame.image.load(b1)
h = 0
screenWidth = 600
clock.tick(30)
""" Check for any events - keyboard, mouse etc. """
for event in pygame.event.get():
if event.type == pygame.QUIT:
keepGoing = False
screen.blit(back, (h,0))
screen.blit(back2, (h-screenWidth,0))
h = h + 0.5
if h == screenWidth:
h = 0
if event.type == pygame.KEYDOWN:
""" If a key is pressed, bundle the event and the ship
and pass it to the function that handles events """
myData = (event, ship)
checkKeys(myData)
if event.type == pygame.MOUSEBUTTONDOWN:
""" If a mouse event occurs check to see if it is the left click """
if event.button==1:
""" reduce the ammo of the ship
get the center of the ship sprite
get where the mouse was clicked on the screen
bundle the (x,y) position for the above
pass them to PlotVector function and have the
angle and vector returned
create a new instance a laser
pass to it the start position, angle and vector it must travel
add the laser to the laser_list group and the all sprites group """
ship.Set_Ammo();
shipCenter = ship.returnPosition()
mousePos = pygame.mouse.get_pos()
print "Ship: %s, Mouse: %s " %(shipCenter, mousePos)
data= (shipCenter, mousePos)
angle, vect = PlotVector(data)
laser = Laser()
laser.fire.play()
laser.AnglePoints(shipCenter, angle, vect)
laser_list.add(laser)
all_sprites_list.add(laser)
""" update all sprites """
all_sprites_list.update()
""" For every laser that was fired we are going to do the following: """
for laser in laser_list:
""" Create a list of asteroids that were hit by the laser """
asteroid_hit_list = pygame.sprite.spritecollide(laser, asteroid_list, False)
""" For each asteroid hit,
Update the ship score
play a bang sound
reset the asteroid to the top of the screen again
remove the laser from the laser list and all sprites """
for asteroid in asteroid_hit_list:
ship.Set_Score()
asteroid.bang.play()
asteroid.reset()
laser_list.remove(laser)
all_sprites_list.remove(laser)
""" Remove the laser if it flies up off the screen """
if laser.rect.y < -10 or laser.rect.y > screen.get_height():
laser_list.remove(laser)
all_sprites_list.remove(laser)
if laser.rect.x < -10 or laser.rect.x > screen.get_width():
laser_list.remove(laser)
all_sprites_list.remove(laser)
""" Now we are going to create a list of asteroids that hit the ship """
asteroid_hit_Ship = pygame.sprite.spritecollide(ship, asteroid_list, False)
""" For each asteroid that hits the ship,
Update the ship shields
play a bang sound
reset the asteroid to the top of the screen again """
for asteroid in asteroid_hit_Ship:
asteroid.bang.play()
ship.Set_Shield()
asteroid.reset()
""" if the ship's shields are less than 0 then the game will finish """
if ship.Get_Shield()<0:
keepGoung=False
break
""" Update the score and refresh the scoreboard using the updateScoreBoard function """
RefreshScore = (ship, scoreBoard)
updateScoreBoard(RefreshScore)
""" The last part refreshes the sprites """
asteroid_list.clear(screen, background)
asteroid_list.update()
asteroid_list.draw(screen)
all_sprites_list.clear(screen, background)
all_sprites_list.update()
all_sprites_list.draw(screen)
pygame.display.flip()
return ship.Get_Score()
def PlotVector(PlotVect):
"""
This PlotVector function handles the calculation of the new
vector and also the calculation of the angle the arrow/bullet/projectile
will be transformed and travel to.
REFER TO YOUR NOTES FROM LECTURE 5
"""
""" Un-bundle the data that is passed into this function """
(start, dest) = PlotVect
""" Create a new Vector object and call it vect """
vect = Vector()
""" Pass the start and dest coordinates and get back a new vector which the arrow must travel """
vect = Vector.from_points(start, dest)
""" Calculate the magnitude (Distance) between the two points """
mag = vect.get_magnitude()
""" Get the values for the vector, i.e. the change in x and change in y """
x = vect.x
y = vect.y
""" This variable will be used to calculate and store the angle between points """
angDEG = (math.atan2(y, x)*(180/math.pi))*-1
""" Print the coordinates and angle to the screen for testing """
print "Start: %s \nEnd: %s\nVector: %s\nMagnitude: %d" %(start, dest, vect, mag)
print "Angle : %d" %(angDEG)
""" Bundle and return the angle and vector which will be used in the TheArrow Sprite """
return (angDEG, vect)
"""
The next if statement runs the main function as it is in the primary program scope.
This was covered in lecture 5 available on Moodle
"""
def instructions(score):
shipIntro = PlayerShip(screen)
allSprites = pygame.sprite.Group(shipIntro)
insFont = pygame.font.SysFont(None, 30)
instructions = (
"Asteroids Version 2.0" ,
"Previous Score: %d" %score,
"Instructions: You must clear the asteroid",
"field so that you can reach your home-world",
"",
"Fly around the field and fire to hit the ",
"asteroids but be careful not to fly too close",
"to any asteroids. Your ship has a limited amount of ",
"off shielding and if your shielding drops below ",
"0 your done for",
"good luck!",
"",
"click to start, escape to quit..."
)
insLabels = []
for line in instructions:
tempLabel = insFont.render(line, 1, (255, 255, 0))
insLabels.append(tempLabel)
keepGoing = True
clock = pygame.time.Clock()
pygame.mouse.set_visible(False)
while keepGoing:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
keepGoing = False
donePlaying = True
if event.type == pygame.MOUSEBUTTONDOWN:
keepGoing = False
donePlaying = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
keepGoing = False
donePlaying = True
allSprites.update()
allSprites.draw(screen)
for i in range(len(insLabels)):
screen.blit(insLabels[i], (50, 30*i))
pygame.display.flip()
pygame.mouse.set_visible(True)
return donePlaying
def main():
donePlaying = False
score = 0
while not donePlaying:
donePlaying = instructions(score)
if not donePlaying:
score = game()
if __name__ == "__main__":
main()
Check indentions - you have if event.type == pygame.KEYDOWN: and if event.type == pygame.MOUSEBUTTONDOWN: outside of for event in pygame.event.get():. You have some screen.blit() betwin for and if with smaller indentions.
Maybe you put this in wrong place:
screen.blit(back, (h,0))
screen.blit(back2, (h-screenWidth,0))
h = h + 0.5
if h == screenWidth:
h = 0
EDIT:
Probably your code should look like this:
while keepGoing:
b1 = "ocean1.gif"
back = pygame.image.load(b1)
back2 = pygame.image.load(b1)
h = 0
screenWidth = 600
clock.tick(30)
""" Check for any events - keyboard, mouse etc. """
for event in pygame.event.get():
if event.type == pygame.QUIT:
keepGoing = False
elif event.type == pygame.KEYDOWN:
""" If a key is pressed, bundle the event and the ship
and pass it to the function that handles events """
myData = (event, ship)
checkKeys(myData)
elif event.type == pygame.MOUSEBUTTONDOWN:
""" If a mouse event occurs check to see if it is the left click """
if event.button == 1:
""" reduce the ammo of the ship
get the center of the ship sprite
get where the mouse was clicked on the screen
bundle the (x,y) position for the above
pass them to PlotVector function and have the
angle and vector returned
create a new instance a laser
pass to it the start position, angle and vector it must travel
add the laser to the laser_list group and the all sprites group """
ship.Set_Ammo();
shipCenter = ship.returnPosition()
mousePos = pygame.mouse.get_pos()
print "Ship: %s, Mouse: %s " %(shipCenter, mousePos)
data= (shipCenter, mousePos)
angle, vect = PlotVector(data)
laser = Laser()
laser.fire.play()
laser.AnglePoints(shipCenter, angle, vect)
laser_list.add(laser)
all_sprites_list.add(laser)
screen.blit(back, (h,0))
screen.blit(back2, (h-screenWidth,0))
h = h + 0.5
if h == screenWidth:
h = 0
# rest of main loop
btw: I use elif because event.type can't be pygame.QUIT, pygame.KEYDOWN and pygame.MOUSEBUTTONDOWN in one event.
Related
I'm a beginner programmer who is starting with python and I'm starting out by making a game in pygame.
The game basically spawns circles at random positions and when clicked, it gives you points.
Recently I've hit a roadblock when I want to spawn multiple instances of the same object (in this case circles) at the same time.
I've tried stuff like sleep() and some other code related to counters, but it always results in the next circle spawned overriding the previous one (i.e the program spawns circle 1, but when circle 2 comes in, circle 1 disappears).
Does anyone know a solution to this? I would really appreciate your help!
import pygame
import random
import time
pygame.init()
window = pygame.display.set_mode((800,600))
class circle():
def __init__(self, color, x, y, radius, width,):
self.color = color
self.x = x
self.y = y
self.radius = radius
self.width = width
def draw(self, win, outline=None):
pygame.draw.circle(win, self.color, (self.x, self.y, self.radius, self.width), 0)
run=True
while run:
window.fill((0, 0, 0))
pygame.draw.circle(window, (255, 255, 255), (random.randint(0, 800),random.randint(0, 600)), 20, 20)
time.sleep(1)
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run=False
pygame.quit()
quit()
It does not work that way. time.sleep, pygame.time.wait() or pygame.time.delay is not the right way to control time and gameplay within an application loop. The game does not respond while you wait. The application loop runs continuously. You have to measure the time in the loop and spawn the objects according to the elapsed time.
pygame.Surface.fill clears the entire screen. Add the newly created objects to a list. Redraw all of the objects and the entire scene in each frame.
See also Time, timer event and clock
You have 2 options. Use pygame.time.get_ticks() to measure the time. Define a time interval after which a new object should appear. Create an object when the point in time is reached and calculate the point in time for the next object:
object_list = []
time_interval = 500 # 500 milliseconds == 0.1 seconds
next_object_time = 0
while run:
# [...]
current_time = pygame.time.get_ticks()
if current_time > next_object_time:
next_object_time += time_interval
object_list.append(Object())
Minimal example:
repl.it/#Rabbid76/PyGame-TimerSpawnObjects
import pygame, random
pygame.init()
window = pygame.display.set_mode((300, 300))
class Object:
def __init__(self):
self.radius = 50
self.x = random.randrange(self.radius, window.get_width()-self.radius)
self.y = random.randrange(self.radius, window.get_height()-self.radius)
self.color = pygame.Color(0)
self.color.hsla = (random.randrange(0, 360), 100, 50, 100)
object_list = []
time_interval = 200 # 200 milliseconds == 0.2 seconds
next_object_time = 0
run = True
clock = pygame.time.Clock()
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
current_time = pygame.time.get_ticks()
if current_time > next_object_time:
next_object_time += time_interval
object_list.append(Object())
window.fill(0)
for object in object_list[:]:
pygame.draw.circle(window, object.color, (object.x, object.y), round(object.radius))
object.radius -= 0.2
if object.radius < 1:
object_list.remove(object)
pygame.display.flip()
pygame.quit()
exit()
The other option is to use the pygame.event module. Use pygame.time.set_timer() to repeatedly create a USEREVENT in the event queue. The time has to be set in milliseconds. e.g.:
object_list = []
time_interval = 500 # 500 milliseconds == 0.1 seconds
timer_event = pygame.USEREVENT+1
pygame.time.set_timer(timer_event, time_interval)
Note, in pygame customer events can be defined. Each event needs a unique id. The ids for the user events have to be between pygame.USEREVENT (24) and pygame.NUMEVENTS (32). In this case pygame.USEREVENT+1 is the event id for the timer event.
Receive the event in the event loop:
while run:
for event in pygame.event.get():
if event.type == timer_event:
object_list.append(Object())
The timer event can be stopped by passing 0 to the time argument of pygame.time.set_timer.
Minimal example:
repl.it/#Rabbid76/PyGame-TimerEventSpawn
import pygame, random
pygame.init()
window = pygame.display.set_mode((300, 300))
class Object:
def __init__(self):
self.radius = 50
self.x = random.randrange(self.radius, window.get_width()-self.radius)
self.y = random.randrange(self.radius, window.get_height()-self.radius)
self.color = pygame.Color(0)
self.color.hsla = (random.randrange(0, 360), 100, 50, 100)
object_list = []
time_interval = 200 # 200 milliseconds == 0.2 seconds
timer_event = pygame.USEREVENT+1
pygame.time.set_timer(timer_event, time_interval)
run = True
clock = pygame.time.Clock()
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == timer_event:
object_list.append(Object())
window.fill(0)
for object in object_list[:]:
pygame.draw.circle(window, object.color, (object.x, object.y), round(object.radius))
object.radius -= 0.2
if object.radius < 1:
object_list.remove(object)
pygame.display.flip()
pygame.quit()
exit()
I'm new to pygame, and I'm trying to practice skill with 'Plants and Zombies', but here comes a problem when I run the code: the next bullet does not move to the edge of the screen, it ends at a point at which the previous bullet collide with a zombie. I can NOT find out where is the problem, hope someone can help me out. Just run main.py, click on a top menu(weapon), right click on any point of a grass, u'll know the problem.
project directory:
helper.py:
import os
from PIL import Image
import pygame
current_dir = os.getcwd()
def get_path(image_name):
return os.path.join(current_dir, "resource", image_name)
def get_image_size(image_name):
with Image.open(get_path(image_name)) as im:
return im.size
def load_image_source(image_name, with_bullet=False):
if with_bullet:
bullet_name = "bullet_"+image_name.replace("shooter_", "")
return pygame.image.load(get_path(image_name)), pygame.image.load(get_path(bullet_name))
else:
return pygame.image.load(get_path(image_name))
def load_all_shooters():
weapon = {"selected": [], "unselected": []}
shooter_dir = os.path.join(current_dir, "resource")
for shooter in os.listdir(shooter_dir):
if "shooter" in shooter:
if "selected" in shooter:
weapon["selected"].append(shooter)
else:
weapon["unselected"].append(shooter)
weapon["selected"] = sorted(weapon["selected"])
weapon["unselected"] = sorted(weapon["unselected"])
return weapon
def get_nearest_position(source_list, target_number):
return min(source_list, key=lambda x: abs(x - target_number))
config.py:
LINES = 5
ZOMBIE_SPEED_X = 1
ZOMBIE_REFRESH_TIME = 3000
BULLET_REFRESH_TIME = 1000
BULLET_SPEED = 3
SHOOTER_SIZE = (50, 50)
main.py:
import pygame
import sys
import random
from ZombiesVSPlants.common.helper import get_nearest_position, load_image_source, load_all_shooters, get_image_size, get_path, get_path
from ZombiesVSPlants.config.config import BULLET_SPEED, BULLET_REFRESH_TIME, ZOMBIE_REFRESH_TIME, SHOOTER_SIZE, ZOMBIE_SPEED_X, LINES
pygame.init()
# top weapon menu
menu_height = 60
# get background grass and other resource path and size...
background_grass_path = get_path("background_grass.png")
zombie_path = get_path("enemy_zombie.png", )
single_line_height = get_image_size("background_grass.png")[1]
zombie_size = get_image_size("enemy_zombie.png")
# screen size
screen_height = LINES*single_line_height+menu_height
screen_width = 800
# background colour, zombie speed
background_color = 255, 255, 255
ZOMBIE_SPEED = [-ZOMBIE_SPEED_X, 0]
# others
LINES_LIST = [i for i in range(1, LINES+1)]
zombie_start_x = screen_width-zombie_size[0]
zombie_start_y = (single_line_height-zombie_size[1])/2
shooter_centered_position__list_y = [line*single_line_height+zombie_start_y+menu_height for line in range(5)]
# resource boom
boom = load_image_source("boom.png")
# dragging and other global variables
dragging = False
mouse_follow = None
mouse_follow_rect = None
added_shooters = []
shooter_bullets = []
bullets_rect = []
collide_zombies = []
collide_bullets = []
# screen size
screen = pygame.display.set_mode((screen_width, screen_height))
# load top weapon surface
res = load_all_shooters()
menu_start_position = 5, 5
menu_shooters = [load_image_source(unselected_shooter, with_bullet=True) for unselected_shooter in res["unselected"]]
menu_shooters_selected = [load_image_source(selected_shooter) for selected_shooter in res["selected"]]
menu_shooters_rect = [pygame.Rect(menu_start_position[0]+55*i, menu_start_position[1], SHOOTER_SIZE[0], SHOOTER_SIZE[1]) for i in range(len(menu_shooters))]
# background grass surface
grass = load_image_source(background_grass_path)
grass_rect = [pygame.Rect(0, i * single_line_height+menu_height, screen_width, single_line_height) for i in range(LINES)]
# the very first zombie surface
zombie = load_image_source(zombie_path)
zombies_rect = [pygame.Rect(zombie_start_x, zombie_start_y+menu_height, zombie_size[0], zombie_size[1])]
# ZOMBIE_REFRESH_TIME
NEW_ZOMBIE_EVENT = pygame.USEREVENT+1
pygame.time.set_timer(NEW_ZOMBIE_EVENT, ZOMBIE_REFRESH_TIME)
# BULLET_REFRESH_TIME
NEW_BULLET_EVENT = pygame.USEREVENT+2
pygame.time.set_timer(NEW_BULLET_EVENT, BULLET_REFRESH_TIME)
while 1:
screen.fill(background_color)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# left click on a top menu(weapon), dragging is enabled
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
for i in range(len(menu_shooters)):
if menu_shooters_rect[i].collidepoint(event.pos):
dragging = not dragging
mouse_follow = menu_shooters[i]
# right click when dragging, new weapon will be set on a point
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 3:
x = event.pos[0]
if event.pos[1] > menu_height and dragging:
dragging = not dragging
y = get_nearest_position(shooter_centered_position__list_y, event.pos[1])
added_shooters.append([mouse_follow[0], x, y])
shooter_bullets.append([x, y, mouse_follow[1]])
# spawn new bullet
if event.type == NEW_BULLET_EVENT:
for j in range(len(shooter_bullets)):
bullets_rect.append([shooter_bullets[j][2], pygame.Rect(shooter_bullets[j][0], shooter_bullets[j][1], 15, 15)])
# spawn new zombie
if event.type == NEW_ZOMBIE_EVENT:
# random in roads that new zombies will appear
new_zombies_count = random.randint(1, LINES)
new_zombies_lines = random.sample(LINES_LIST, new_zombies_count)
# add to zombies list
for line in new_zombies_lines:
new_zombie_rect = pygame.Rect(zombie_start_x, line * single_line_height + zombie_start_y + menu_height, zombie_size[0], zombie_size[1])
zombies_rect.append(new_zombie_rect)
# blit top weapons menu
for i in range(len(menu_shooters)):
menu_rect = menu_shooters_rect[i]
screen.blit(menu_shooters[i][0], menu_rect)
# blit selected weapon if mouse hover on a weapon menu
for i in range(len(menu_shooters)):
if menu_shooters_rect[i].collidepoint(pygame.mouse.get_pos()):
screen.blit(menu_shooters_selected[i], menu_shooters_rect[i])
# blit background grass
for r in grass_rect:
screen.blit(grass, r)
# blit all the weapons on the grass
for new in added_shooters:
shooter_rect = pygame.Rect(new[1], new[2], 50, 50)
screen.blit(new[0], shooter_rect)
# blit bullets
for j in range(len(bullets_rect)):
bullets_rect[j][1].x += 1
screen.blit(bullets_rect[j][0], bullets_rect[j][1])
# blit zombies
for i in range(len(zombies_rect)):
zombies_rect[i].x -= ZOMBIE_SPEED_X
screen.blit(zombie, zombies_rect[i])
# blit weapon follows mouse move position
if dragging and mouse_follow[0]:
pos_follow_mouse = pygame.mouse.get_pos()
mouse_follow_rect = pygame.Rect(pos_follow_mouse[0], pos_follow_mouse[1], 50, 50)
screen.blit(mouse_follow[0], mouse_follow_rect)
# collide between zombie and bullet
for i in range(len(bullets_rect)):
for j in range(len(zombies_rect)):
if bullets_rect[i][1].colliderect(zombies_rect[j]):
print("collide!")
screen.blit(boom, zombies_rect[j])
collide_bullets.append(bullets_rect[i])
collide_zombies.append(zombies_rect[j])
bullets_rect = [i for i in bullets_rect if i not in collide_bullets]
zombies_rect = [j for j in zombies_rect if j not in collide_zombies]
pygame.display.flip()
I now got a compromise like solution, when need to remove surface in a collide between 2 rects in 2 lists, just draw the 2 lists in 2 separate for loop and remove the collided rect in another for loop, the demo code would be:
# draw bullets
for b in bullets_rect:
b_r = b[1]
b_r.x += BULLET_SPEED
screen.blit(b[0], b_r)
# draw zombies
for z in zombies_rect:
z.x += -ZOMBIE_SPEED_X
screen.blit(zombie, z)
# when collide, use list.remove to remove collided rect
for i, j in itertools.product(bullets_rect, zombies_rect):
b_r = i[1]
z_r = j
if z_r.contains(b_r):
# when collide, remove the collided rect
screen.blit(boom, z_r)
bullets_rect.remove(i)
zombies_rect.remove(j)
FYI.
I have a problem I'm stuck now for last two days. In file game_function in nested loop I'm creating aliens, when I added randint to create random numbers of aliens in a row I run in to problem. Not always (just re-run the game) but sometimes when I detect sprite edge as a method of Alien class Aliens/sprites won't change the direction, follow x axis to the right and constantly dropping down Aliens each pass of check edge. I don't know what the heck is that. Before when I haven't been using randit to generate random numbers of aliens in a row, everything was just fine.
settings.py
class Settings():
"""A class to store all settings for Alien Invasion"""
def __init__(self):
"""Initialize the game settings"""
#Screen settings
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (230,230,230)
#ship settings
self.ship_speed_factor = 1.5
#Bullet settings
self.bullet_speed_factor = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60, 60, 60)
self.bullets_allowed = 3
# Alien settings
self.alien_speed_factor = 1
self.fleet_drop_speed = 10
# fleet_direction of 1 represents right; -1 represents left.
self.fleet_direction = 1
ship.py
import pygame
class Ship():
def __init__(self,ai_settings, screen):
"""Initialize the ship and sets the starting position."""
self.screen = screen
self.ai_settings = ai_settings
#load the ship image and get its rect
self.image = pygame.image.load('images/ship.png')
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
# Start each new ship at the bottom center of the screen
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
self.center = float(self.rect.centerx)
#Movement Flag
self.moving_right = False
self.moving_left = False
def update(self):
"""Update the ship's position based on the movement Flag."""
#Update the ship's center value, not the rect
if self.moving_right and self.rect.right < self.screen_rect.right:
self.center += self.ai_settings.ship_speed_factor
if self.moving_left and self.rect.left > 0:
self.center -= self.ai_settings.ship_speed_factor
#update rect object from self.center
self.rect.centerx = self.center
def blitme(self):
"""Draw the ship at its current location"""
self.screen.blit(self.image, self.rect)
In a function create_fleet(ai_settings, screen, ship, aliens) is nested loop, when instead of generating random number between 3-9 and then placing new instance in row I've used constant calculation which always gives 9 aliens in row everything runned just fine. Aliens changed everytime directions and just one time dropped down until next check_edge event passed the condition. So from one wall to another. Now when the randint is in place, NOT ALWAYS, when check_edge method confirms true, then call to function change_fleet_direction() is made and there I see problem, it just sometimes doesn't change the direction. where for direction is used just simple +1 or -1 and in calling for update in Alien class it should either decrease x axis or increase until edge event.
game_functions.py
import sys
import pygame
from bullet import Bullet
from alien import Alien
from random import randint
def create_fleet(ai_settings, screen, ship, aliens):
"""Create a full fleet of aliens."""
#Create an Alien and find the number of aliens in a row
name= 'First unused'
alien = Alien(ai_settings, screen,name)
number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width)
number_rows = get_number_rows(ai_settings, ship.rect.height,
alien.rect.height)
# Create the fleet of aliens.
for row_number in range(number_rows):
random_num = randint(3, number_aliens_x)
for alien_number in range(0, random_num):
create_alien(ai_settings, screen, aliens, alien_number,
row_number)
def get_number_aliens_x(ai_settings, alien_width):
"""Determine the number of aliens that fit in a row."""
available_space_x = ai_settings.screen_width - 2 * alien_width
number_aliens_x = int(available_space_x / (2 * alien_width))
return number_aliens_x
def get_number_rows(ai_settings, ship_height, alien_height):
"""Determine the number of rows of aliens that fit on the screen."""
available_space_y = (ai_settings.screen_height -
(3 * alien_height) - ship_height)
number_rows = int(available_space_y / (2 * alien_height))
return number_rows
def create_alien(ai_settings, screen, aliens, alien_number, row_number):
"""Create alien and place it in the row"""
name = "Alien number " + str(alien_number) + " in row " + str(row_number)
alien = Alien(ai_settings, screen, name)
alien_width = alien.rect.width
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
aliens.add(alien)
def check_keydown_events(event, ai_settings, screen, ship, bullets):
"""Respond to key presses"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
elif event.key == pygame.K_SPACE:
fire_bullet(ai_settings, screen, ship, bullets)
elif event.key == pygame.K_q:
sys.exit()
def fire_bullet(ai_settings, screen, ship, bullets):
# Create a new bullet and add it to the bullets group.
if len(bullets) < ai_settings.bullets_allowed:
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
def check_keyup_events(event,ship):
"""Respond to key releases"""
if event.key == pygame.K_RIGHT:
ship.moving_right = False
elif event.key == pygame.K_LEFT:
ship.moving_left = False
def check_events(ai_settings, screen, ship, bullets):
"""Respond to keypress and mouse events"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ai_settings, screen, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup_events(event, ship)
def update_screen(ai_settings, screen, ship, aliens, bullets):
"""Update images on the screen and flip to the new screen."""
# Redraw the screen during each pass through the loop.
screen.fill(ai_settings.bg_color)
# Redraw all bullets behind ship and aliens.
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
aliens.draw(screen)
# Make the most recently drawn screen visible.
pygame.display.flip()
def update_bullets(bullets):
"""Update position of bullets and get rid of old bullets."""
# Update bullet positions.
bullets.update()
# Get rid of bullets that have disappeared.
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
def check_fleet_edges(ai_settings, aliens):
"""Respond appropriately if any aliens have reached an edge."""
for alien in aliens.sprites():
if alien.check_edges():
print(alien.name)
change_fleet_direction(ai_settings, aliens)
break
def change_fleet_direction(ai_settings, aliens):
"""Drop the entire fleet and change the fleet's direction."""
print("old direction " + str(ai_settings.fleet_direction))
for alien in aliens.sprites():
alien.rect.y += ai_settings.fleet_drop_speed
if ai_settings.fleet_direction == 1:
ai_settings.fleet_direction = -1
else:
ai_settings.fleet_direction = 1
print("new direction" + str(ai_settings.fleet_direction))
def update_aliens(ai_settings, aliens):
"""
Check if the fleet is at an edge,
and then update the positions of all aliens in the fleet.
"""
check_fleet_edges(ai_settings, aliens)
aliens.update()
Main file alien_invasion.py
import pygame
from pygame.sprite import Group
from settings import Settings
from ship import Ship
import game_functions as gf
def run_game():
# Initialize game and create a screen object.
pygame.init()
ai_settings = Settings()
screen = pygame.display.set_mode(
(ai_settings.screen_width, ai_settings.screen_height))
pygame.display.set_caption("Alien Invasion")
ship = Ship(ai_settings,screen)
#Make a group to store bullets in
bullets = Group()
aliens = Group()
#Create the fleet of aliens
gf.create_fleet(ai_settings, screen, ship, aliens)
# Start the main loop for the game.
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_bullets(bullets)
gf.update_aliens(ai_settings, aliens)
gf.update_screen(ai_settings, screen, ship, aliens, bullets)
run_game()
bullet.py
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
"""A class to manage bullets fired from the ship"""
def __init__(self, ai_settings, screen, ship):
"""create a bullet object at the ship's current position"""
super().__init__()
self.screen = screen
#Create a bullet rect at (0, 0) and then set correct position
self.rect = pygame.Rect(0, 0, ai_settings.bullet_width,
ai_settings.bullet_height)
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
#Store the bullet position as a decimal value.
self.y = float(self.rect.y)
self.color = ai_settings.bullet_color
self.speed_factor = ai_settings.bullet_speed_factor
def update(self):
"""Move bullet up the scereen"""
#Update the decimal position of the bullet
self.y -= self.speed_factor
#Update the rect position
self.rect.y = self.y
def draw_bullet(self):
"""Draw the bullet to the screen"""
pygame.draw.rect(self.screen, self.color, self.rect)
alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""A class to represent a single alien in the fleet"""
def __init__(self, ai_settings, screen, name):
"""Initialize the alien and set its starting position"""
super().__init__()
self.screen = screen
self.ai_settings = ai_settings
#Load the alien image and set its rect attribute.
self.image = pygame.image.load('images/alien.bmp')
self.rect = self.image.get_rect()
#Start each alien near the top left of the screen
self.rect.x = self.rect.width
self.rect.y = self.rect.height
#Store the alien's exact position
self.x = float(self.rect.x)
self.name = name
# def blitme(self):
# """Draw the alien at its current location."""
# self.screen.blit(self.image, self.rect)
#
def check_edges(self):
"""Return True if alien is at edge of screen."""
screen_rect = self.screen.get_rect()
if self.rect.right >= screen_rect.right:
print("Right >= right screen" +' direction is' + str(self.ai_settings.fleet_direction))
return True
elif self.rect.left <= 0:
print("Left <= left")
return True
def update(self):
"""Move the alien right or left."""
self.x += (self.ai_settings.alien_speed_factor *
self.ai_settings.fleet_direction)
self.rect.x = self.x
I'm new to pygame and sprites, I don't understand it why randit has such impact. I've placed in the code when crucial changes are about to be made print statements to debug it from console (but I dunno if it is logic problem). Anyone who can shed a light to it I would appreciate so much. Lost 2 days of learning and programming already. Running python 3.4 from Eclipse Neon on win 10 Thanks very much.
Found the mistake, indentation in func change_fleet_direction(), if block: cannot be in for loop.
Just started learning Python/Pygame watching videos and reading to learn . I would like to see a example code to cycle 3 images on a rect button from a mouse press and return to first image. Really I want the pictures to be three options and return different results. So be able to cycle image be able to select option and option when triggered execute choice.
Example
import pygame
pygame.init()
screen = pygame.display.set_mode((300,200))
# three images
images = [
pygame.Surface((100,100)),
pygame.Surface((100,100)),
pygame.Surface((100,100)),
]
images[0].fill((255,0,0))
images[1].fill((0,255,0))
images[2].fill((0,0,255))
# image size and position
images_rect = images[0].get_rect()
# starting index
index = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
# check mouse position and pressed button
if event.button == 1 and images_rect.collidepoint(event.pos):
# cycle index
index = (index+1) % 3
screen.blit(images[index], images_rect)
pygame.display.flip()
pygame.quit()
Example using class - to create many buttons
import pygame
class Button(object):
def __init__(self, position, size):
self._images = [
pygame.Surface(size),
pygame.Surface(size),
pygame.Surface(size),
]
self._images[0].fill((255,0,0))
self._images[1].fill((0,255,0))
self._images[2].fill((0,0,255))
self._rect = pygame.Rect(position, size)
self._index = 0
def draw(self, screen):
screen.blit(self._images[self._index], self._rect)
def event_handler(self, event):
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1 and self._rect.collidepoint(event.pos):
self._index = (self._index+1) % 3
pygame.init()
screen = pygame.display.set_mode((320,110))
button1 = Button((5, 5), (100, 100))
button2 = Button((110, 5), (100, 100))
button3 = Button((215, 5), (100, 100))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
button1.event_handler(event)
button2.event_handler(event)
button3.event_handler(event)
button1.draw(screen)
button2.draw(screen)
button3.draw(screen)
pygame.display.flip()
pygame.quit()
If I understood the question correctly, you need a single button that changes the look every time you click on it, and changes its relative function accordingly.
You should be able to solve your problem by creating a class that takes two list and a counter
1) list of images
2) list of functions
3) the counter tells you which image/function is selected.
The functions needs to be built in the class, but you can provide the image that you want in the class argument (actually, you could pass them as arguments, but I don't think is worth it).
Here is the code, I commented some lines with their intended meaning (in line comments)
import pygame
import sys
pygame.init()
width = 600
height = 400
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Magic Buttons")
background = pygame.Surface(screen.get_size())
clock = pygame.time.Clock()
class Buttons:
def __init__(self, posX, posY, image1, image2, image3):
self.image_list = [image1, image2, image3] # static list of images
self.function_list = [self.button_function_1,self.button_function_2,self.button_function_3 ]
self.rect_position = (posX, posY) # this is a tuple to identify the upper left corner of the rectangle of the image
self.button_type = 0 # initial value of the button, both for the function and the image
self.image = pygame.image.load(self.image_list[0]) #default image, index number 0 of image_list
self.rect = pygame.Rect(posX, posY, self.image.get_width(), self.image.get_height()) # create a rectangle object same size of the images
def check(self, pos):
if self.rect.collidepoint(pos) ==True:
self.change_button()
else:
pass
def change_button(self):
self.button_type = (self.button_type +1)%3
self.image = pygame.image.load(self.image_list[self.button_type ]) # load the image relative to button_type
self.function_list[self.button_type -1]() # execute the function relative to the new value of button_type
self.draw_button()
def draw_button(self):
screen.blit(self.image, self.rect_position) # blit the new button image
def button_function_1(self):
print ("function 1 in action")
def button_function_2(self):
print ("function 2 in action")
def button_function_3(self):
print ("function 3 in action")
multibutton = Buttons(100,100,"button1.png","button2.png","button3.png") # create an istance of the button in the x=100, y = 100, with the three image button
while True:
background.fill((0,0,0))
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONUP:
pos = pygame.mouse.get_pos() # fetch the position of the mouse
multibutton.check(pos) # check if the mouse is on the button
multibutton.draw_button()
pygame.display.flip()
I'm trying to write this tutorial in Pygame(Python) and having problems about holding points between planes.
My code is: fiz2.py
Vector class: vector.py
If you move mouse on the Pygame screen, the planes will rotate. And when the planes are rotating, points are passing through planes and going outside.
I tried to fix points' positions on every iteration but they still passed the planes. I have no idea about where should I fix their positions.
NOTE: I know my code is a little bit messy, this is my first 2d program and I had really hard times getting used to Pygame's coordinate plane and vectors. I will re-write when I solve this.
NOTE2: Yes, I wrote the comment about how to hold points between planes on the tutorial, I understand the way he fixes positions but have no idea about how(and where, in code) to implement it.
Thanks.
I can't tell looking at the code. My guess is a variable-timestep, causing instability. But I can't verify if the math is right. Although, I have useful information :
Vectors
You can simplify code, by using vectors as a class vs list/tuple. (velocity, acceleration, location) are treated as one object, verses separate .x and .y values.
# example:
pos[0] += vel[0]
pos[1] += vel[1]
# vs
pos += vel
There is a python-only implementation: euclid.py You can use to compare with your vector.py.
Or use NumPy [ used for 3d graphics, in openGL. ] Is a popular, mature lib.
physics
(It looks like you want to learn by writing your own physics), but check out PyMunk
colors
You can use: pygame.Color
import pygame
from pygame import Color
color = Color('white')
color2 = Color('lightgray')
color3 = Color(0,128,128)
collisions
Look at pygame.sprite.*collide , and pygame.Rect.*collide
pygame Game loop with numpy vector's
Boilerplate I wrote
""" Pygame boilerplate. <ninmonkey>2011/04
pygame main Game() loop, and numpy for vector math.
note:
this might not be the most effecient way to use numpy as vectors, but it's an intro.
And this does not force fixed-timesteps. If you want a stable simulation, you need to use a fixed timestep.
see: http://gafferongames.com/game-physics/fix-your-timestep/
Keys:
ESC : exit
Space : game_init()
"""
import pygame
from pygame.locals import *
from pygame import Color, Rect
import numpy as np
def get_screen_size():
"""return screen (width, height) tuple"""
screen = pygame.display.get_surface()
return screen.get_size()
class Actor():
"""basic actor, moves randomly.
members:
loc = position vector
velocity = velocity vector
width, height
"""
def __init__(self, loc=None, velocity=None):
"""optional initial loc and velocity vectors"""
self.width = 50
self.height = 50
# if loc or velocity are not set: use random
if loc is None: self.rand_loc()
else: self.loc = loc
if velocity is None: self.rand_velocity()
else: self.velocity = velocity
def update(self):
"""update movement"""
self.loc += self.velocity
def rand_velocity(self):
"""set a random vector , based on random direction. Using unit circle:
x = cos(deg) * speed
"""
rad = np.radians( np.random.randint(0,360) )
speed = np.random.randint(1,15)
x = np.cos(rad)
y = np.sin(rad)
velocity = np.array( [x,y])
velocity *= speed
self.velocity = velocity
def rand_loc(self):
"""random location onscreen"""
width,height = get_screen_size()
x = np.random.randint(0,width)
y = np.random.randint(0,height)
self.loc = np.array([x,y])
def is_onscreen(self):
"""test is screen.colliderect(actor) true?"""
x,y = self.loc
w,h = get_screen_size()
screen = Rect(0, 0, w, h)
actor = Rect(x, y, self.width, self.height)
if screen.colliderect(actor): return True
else: return False
class GameMain():
"""game Main entry point. handles intialization of game and graphics."""
done = False
debug = False
color_gray = Color('lightgray')
def __init__(self, width=800, height=600, color_bg=None):
"""Initialize PyGame"""
pygame.init()
self.width, self.height = width, height
self.screen = pygame.display.set_mode(( self.width, self.height ))
pygame.display.set_caption( "boilerplate : pygame" )
self.clock = pygame.time.Clock()
self.limit_fps = True
self.limit_fps_max = 60
if color_bg is None: color_bg = Color(50,50,50)
self.color_bg = color_bg
self.game_init()
def game_init(self):
"""new game/round"""
self.actors = [Actor() for x in range(10)]
def loop(self):
"""Game() main loop"""
while not self.done:
self.handle_events()
self.update()
self.draw()
if self.limit_fps: self.clock.tick( self.limit_fps_max )
else: self.clock.tick()
def update(self):
"""update actors, handle physics"""
for a in self.actors:
a.update()
if not a.is_onscreen():
a.rand_loc()
def handle_events(self):
"""handle regular events. """
events = pygame.event.get()
# kmods = pygame.key.get_mods() # key modifiers
for event in events:
if event.type == pygame.QUIT: sys.exit()
elif event.type == KEYDOWN:
if (event.key == K_ESCAPE): self.done = True
elif (event.key == K_SPACE): self.game_init()
def draw(self):
"""render screen"""
# clear screen
self.screen.fill( self.color_bg )
# Actor: draw
for a in self.actors:
x,y = a.loc
w,h = a.width, a.height
r = Rect(x, y, w, h)
self.screen.fill(self.color_gray, r)
# will call update on whole screen Or flip buffer.
pygame.display.flip()
if __name__ == '__main__':
g = GameMain()
g.loop()