Inaccurate object movement with mouse in Pygame - python

I'm attempting to make a near carbon copy of this game:
https://yppedia.puzzlepirates.com/Carpentry
It uses pentominos, which are objects made of 5 blocks. I have a Piece class, that stores each of these blocks in a list. When I click on a Block to move it, I'm also moving every other block that shares the same parent Piece object, so I can drag the entire piece around with my mouse.
The problem I'm having is that when I click one of these blocks, the piece moves away from my cursor. The dragging is fine, but I want it to follow the cursor more precisely.
I am using a Mouse class so I can implement simple collisions between mouse clicks and the Blocks, but I think this is the cause of my problems.
Edit: I could probably resolve this by hardcoding changes to the x and y positions of each block, but ideally I'd prefer a more modular solution because I think I'm misunderstanding how the mouse position works in pygame.
import pygame
import sys
import collections
import random
pygame.init()
FPS = 25
fpsClock = pygame.time.Clock()
# -----------SCREEN----------------#
WIN_WIDTH = 680 # width of window
WIN_HEIGHT = 500 # height of window
DISPLAY = (WIN_WIDTH, WIN_HEIGHT) # variable for screen display
DEPTH = 32 # standard
FLAGS = 0 # standard
screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH)
pygame.display.set_caption('Carpentry')
# ---------------------------------#
# ---------------colours-----------#
WOOD = (182, 155, 76)
RED = (255, 0, 0)
BLACK = (0, 0, 0)
# ---------------------------------#
blocks = pygame.sprite.Group()
pieces = []
class Block(pygame.sprite.Sprite):
def __init__(self, x, y, parent):
super().__init__()
self.image = pygame.Surface([15, 15])
self.colour = WOOD
self.parent = parent
self.image.fill(self.colour)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.original_x = x
self.original_y = y
self.drag = False
class Mouse(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface([15, 15])
self.colour = RED
self.image.fill(self.colour)
self.rect = self.image.get_rect()
self.rect.x = pygame.mouse.get_pos()[0]
self.rect.y = pygame.mouse.get_pos()[1]
def update_mouse(self):
self.rect.x = pygame.mouse.get_pos()[0]
self.rect.y = pygame.mouse.get_pos()[1]
class Piece:
def __init__(self):
self.blocks = []
self.c = 0
def insert(self, template):
for direction in template:
self.blocks.append([])
x = 0
y = 0
for line in direction:
for character in line:
if character == "O":
my_block = Block(x, y, self)
self.blocks[self.c].append(my_block)
blocks.add(my_block)
x += 15
y += 15
x = 0
self.c += 1
J_TEMPLATE = [['..O..',
'..O..',
'..O..',
'.OO..',
'.....']]
templates = {}
my_piece = Piece()
my_piece.insert(J_TEMPLATE)
pieces.append(my_piece)
mouse = Mouse()
while True:
screen.fill(BLACK)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
bool = pygame.sprite.spritecollide(mouse, blocks, False)
if len(bool) > 0:
target = bool[0]
target.drag = True
for block in blocks:
if block.parent == target.parent: #if blocks are part of the same piece
block.drag = True
else:
for block in blocks:
block.drag = False
mouse.update_mouse()
for block in blocks:
if block.drag == True:
block.rect.x = mouse.rect.x + block.original_x
block.rect.y = mouse.rect.y + block.original_y
blocks.draw(screen)
pygame.display.update()
fpsClock.tick(FPS)

I'd do it in this way: If a block was clicked, assign its parent piece to a variable and calculate the offsets for the blocks like so: block.offset = block.rect.topleft - Vec(event.pos). At the top of the file you need to import from pygame.math import Vector2 as Vec. Now you can check for pygame.MOUSEMOTION events and move the blocks to the new event.pos + block.offset. The Mouse class is not needed anymore.
selected = None
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if selected: # If a piece was selected ...
selected = None # Deselect it.
else:
# Find a colliding block.
collided_sprites = [block for block in blocks
if block.rect.collidepoint(event.pos)]
if collided_sprites: # If we clicked a block ...
# Select the parent of the block.
selected = collided_sprites[0].parent
# Calculate the offsets for the blocks.
for block in selected.blocks[0]:
block.offset = block.rect.topleft - Vec(event.pos)
elif event.type == pygame.MOUSEMOTION:
if selected:
# Update the block positions by adding their offsets to the mouse pos.
for block in selected.blocks[0]:
block.rect.topleft = event.pos + block.offset
screen.fill(BLACK)
blocks.draw(screen)
pygame.display.update()
fpsClock.tick(25)

Related

Making a title screen for a game, and the 'QUIT' button doesn't close the while loop I have set up [duplicate]

I'm just making a title screen for a future project, and when I run it the "PLAY" and "QUIT" buttons work fine enough but when you click the surrounding background or the title image, the game freezes up and returns the error, "AttributeError: 'list' object has no attribute 'sprites'" I tried adding a blank audio file seperate from the "if play == 0:" in case it was just confused when it had nothing to do, but the problem stayed.
import pygame
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
YELLOW = (255,255,0)
score = 0
play = 0
class Sprite(pygame.sprite.Sprite):
def __init__(self,filename):
super().__init__()
self.image = pygame.image.load(filename).convert()
self.image.set_colorkey(BLACK)
self.rect = self.image.get_rect()
class Player(Sprite):
def update(self):
""" Method called when updating a sprite. """
# Get the current mouse position. This returns the position
# as a list of two numbers.
pos = pygame.mouse.get_pos()
# Now see how the mouse position is different from the current
# player position. (How far did we move?)
diff_x = self.rect.x - pos[0]
diff_y = self.rect.y - pos[1]
# Loop through each block that we are carrying and adjust
# it by the amount we moved.
# Now wet the player object to the mouse location
self.rect.x = pos[0]
self.rect.y = pos[1]
class Block(pygame.sprite.Sprite):
def __init__(self, color, width, height):
# Call the parent class (Sprite) constructor
super().__init__()
# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
self.image = pygame.Surface([width, height])
self.image.fill(color)
# Fetch the rectangle object that has the dimensions of the image
# image.
# Update the position of this object by setting the values
# of rect.x and rect.y
self.rect = self.image.get_rect()
# Initialize Pygame
pygame.init()
# Set the height and width of the screen
screen_width = 750
screen_height = 500
screen = pygame.display.set_mode([screen_width, screen_height])
pygame.display.set_caption("LARP")
# This is a list of 'sprites.' Each block in the program is
# added to this list. The list is managed by a class called 'Group.'
block_list = pygame.sprite.Group()
quit_block = pygame.sprite.Group()
start_block = pygame.sprite.Group()
title_image = pygame.image.load("title.png").convert_alpha()
quit_image = pygame.image.load("quit.png").convert_alpha()
logo_image = pygame.image.load("logo.png").convert_alpha()
player_image = pygame.image.load("player.png").convert_alpha()
background_image = pygame.image.load("title_screen.png").convert_alpha()
bomp = pygame.mixer.Sound("bump.wav")
# This is a list of every sprite.
# All blocks and the player block as well.
player_list = pygame.sprite.Group()
all_sprites_list = pygame.sprite.Group()
#for i in range(50):
#This represents a block
#block = Block(BLACK, 20, 15)
#Set a random location for the block
#block.rect.x = random.randrange(screen_width)
#block.rect.y = random.randrange(screen_height)
#Add the block to the list of objects
#block_list.add(block)
#all_sprites_list.add(block)
title = Sprite("title.png")
title.rect.x = 340
title.rect.y = 230
all_sprites_list.add(title)
start_block.add(title)
quitg = Sprite("quit.png")
quitg.rect.x = 340
quitg.rect.y = 350
all_sprites_list.add(quitg)
quit_block.add(quitg)
logo = Sprite("logo.png")
logo.rect.y = 90
logo.rect.x = 310
all_sprites_list.add(logo)
block_list.add(logo)
player = Player("player.png")
player_list.add(player)
all_sprites_list.add(player)
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# Hide the mouse cursor
pygame.mouse.set_visible(False)
# -------- Main Program Loop -----------
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.MOUSEBUTTONDOWN:
# When the mouse button is pressed, see if we are in contact with
# other sprites:
if play == 0:
start_block = pygame.sprite.spritecollide(player, start_block, True)
if len(start_block) >= 1:
play += 1
quit_block = pygame.sprite.spritecollide(player, quit_block, True)
if len(quit_block) >= 1:
done = True
#print(score)
#blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True)
#if len(blocks_hit_list) >= 1:
#score +=1
# Set the list of blocks we are in contact with as the list of
# blocks being carried.
#player.carry_block_list = blocks_hit_list
elif event.type == pygame.MOUSEBUTTONUP:
# When we let up on the mouse, set the list of blocks we are
# carrying as empty.
player.carry_block_list = []
all_sprites_list.update()
# Clear the screen
screen.fill(WHITE)
# Draw all the spites
if play == 0:
screen.blit(background_image, [0,0])
all_sprites_list.draw(screen)
screen.blit(player_image, [player.rect.x, player.rect.y])
# Limit to 60 frames per second
clock.tick(60)
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
pygame.quit()
start_block and quit_block are pygame.sprite.Group(). However, pygame.sprite.spritecollide(). Therefore an instruction like:
start_block = pygame.sprite.spritecollide(player, start_block, True)
makes no sense because the group start_block is overwritten with the list start_block
Change the name of the variable the gets the return value. e.g.:
start_block_hit = pygame.sprite.spritecollide(player, start_block, False)
Note, you don't need to store the return values at all. The doKill argument must be False, otherwise your button will be destroyed when you click it:
if pygame.sprite.spritecollide(player, start_block, False):
play += 1
if pygame.sprite.spritecollide(player, quit_block, False):
done = True

Pygame returns an error when you click a sprite with another sprite that isn't listed

I'm just making a title screen for a future project, and when I run it the "PLAY" and "QUIT" buttons work fine enough but when you click the surrounding background or the title image, the game freezes up and returns the error, "AttributeError: 'list' object has no attribute 'sprites'" I tried adding a blank audio file seperate from the "if play == 0:" in case it was just confused when it had nothing to do, but the problem stayed.
import pygame
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
YELLOW = (255,255,0)
score = 0
play = 0
class Sprite(pygame.sprite.Sprite):
def __init__(self,filename):
super().__init__()
self.image = pygame.image.load(filename).convert()
self.image.set_colorkey(BLACK)
self.rect = self.image.get_rect()
class Player(Sprite):
def update(self):
""" Method called when updating a sprite. """
# Get the current mouse position. This returns the position
# as a list of two numbers.
pos = pygame.mouse.get_pos()
# Now see how the mouse position is different from the current
# player position. (How far did we move?)
diff_x = self.rect.x - pos[0]
diff_y = self.rect.y - pos[1]
# Loop through each block that we are carrying and adjust
# it by the amount we moved.
# Now wet the player object to the mouse location
self.rect.x = pos[0]
self.rect.y = pos[1]
class Block(pygame.sprite.Sprite):
def __init__(self, color, width, height):
# Call the parent class (Sprite) constructor
super().__init__()
# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
self.image = pygame.Surface([width, height])
self.image.fill(color)
# Fetch the rectangle object that has the dimensions of the image
# image.
# Update the position of this object by setting the values
# of rect.x and rect.y
self.rect = self.image.get_rect()
# Initialize Pygame
pygame.init()
# Set the height and width of the screen
screen_width = 750
screen_height = 500
screen = pygame.display.set_mode([screen_width, screen_height])
pygame.display.set_caption("LARP")
# This is a list of 'sprites.' Each block in the program is
# added to this list. The list is managed by a class called 'Group.'
block_list = pygame.sprite.Group()
quit_block = pygame.sprite.Group()
start_block = pygame.sprite.Group()
title_image = pygame.image.load("title.png").convert_alpha()
quit_image = pygame.image.load("quit.png").convert_alpha()
logo_image = pygame.image.load("logo.png").convert_alpha()
player_image = pygame.image.load("player.png").convert_alpha()
background_image = pygame.image.load("title_screen.png").convert_alpha()
bomp = pygame.mixer.Sound("bump.wav")
# This is a list of every sprite.
# All blocks and the player block as well.
player_list = pygame.sprite.Group()
all_sprites_list = pygame.sprite.Group()
#for i in range(50):
#This represents a block
#block = Block(BLACK, 20, 15)
#Set a random location for the block
#block.rect.x = random.randrange(screen_width)
#block.rect.y = random.randrange(screen_height)
#Add the block to the list of objects
#block_list.add(block)
#all_sprites_list.add(block)
title = Sprite("title.png")
title.rect.x = 340
title.rect.y = 230
all_sprites_list.add(title)
start_block.add(title)
quitg = Sprite("quit.png")
quitg.rect.x = 340
quitg.rect.y = 350
all_sprites_list.add(quitg)
quit_block.add(quitg)
logo = Sprite("logo.png")
logo.rect.y = 90
logo.rect.x = 310
all_sprites_list.add(logo)
block_list.add(logo)
player = Player("player.png")
player_list.add(player)
all_sprites_list.add(player)
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# Hide the mouse cursor
pygame.mouse.set_visible(False)
# -------- Main Program Loop -----------
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.MOUSEBUTTONDOWN:
# When the mouse button is pressed, see if we are in contact with
# other sprites:
if play == 0:
start_block = pygame.sprite.spritecollide(player, start_block, True)
if len(start_block) >= 1:
play += 1
quit_block = pygame.sprite.spritecollide(player, quit_block, True)
if len(quit_block) >= 1:
done = True
#print(score)
#blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True)
#if len(blocks_hit_list) >= 1:
#score +=1
# Set the list of blocks we are in contact with as the list of
# blocks being carried.
#player.carry_block_list = blocks_hit_list
elif event.type == pygame.MOUSEBUTTONUP:
# When we let up on the mouse, set the list of blocks we are
# carrying as empty.
player.carry_block_list = []
all_sprites_list.update()
# Clear the screen
screen.fill(WHITE)
# Draw all the spites
if play == 0:
screen.blit(background_image, [0,0])
all_sprites_list.draw(screen)
screen.blit(player_image, [player.rect.x, player.rect.y])
# Limit to 60 frames per second
clock.tick(60)
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
pygame.quit()
start_block and quit_block are pygame.sprite.Group(). However, pygame.sprite.spritecollide(). Therefore an instruction like:
start_block = pygame.sprite.spritecollide(player, start_block, True)
makes no sense because the group start_block is overwritten with the list start_block
Change the name of the variable the gets the return value. e.g.:
start_block_hit = pygame.sprite.spritecollide(player, start_block, False)
Note, you don't need to store the return values at all. The doKill argument must be False, otherwise your button will be destroyed when you click it:
if pygame.sprite.spritecollide(player, start_block, False):
play += 1
if pygame.sprite.spritecollide(player, quit_block, False):
done = True

A surface shown unexpectedly with pygame

I'm in the process of making a snake game, it mostly went well so far, but it displayed a block of the snake at the top left that I can't get rid of.
I checked that I didn't draw the surface there(0,0). I'm stuck. Please help me out, thanks!!
BTW it's my first time asking a question so any suggestion on that is also appreciated.
Edit: I found that using a regular class instead of sprite solved the problem, but I need the collide and other functions in sprite.
import pygame
class snake(pygame.sprite.Sprite):
speed=5
init_length=10
direction=0
x=[]
y=[]
updateCountMax = 2
updateCount = 0
length=10
# image=pygame.Surface((11,11)).convert().fill((0,128,255))
def __init__(self,init_x,init_y,image,screen):
pygame.sprite.Sprite.__init__(self)
for i in range(0,self.init_length):
self.x.append(init_x)
self.y.append(init_y)
# for i in range(0,self.length):
# print(f"{self.x[i]},{self.y[i]}")
for x in self.x:
print(x)
for y in self.y:
print(y)
self.image=image
self.screen=screen
self.rect=self.image.get_rect()
# self.rect.center=(self.x,self.y)
def move_R(self):
# self.x+=self.speed
self.direction=0
def move_L(self):
# self.x-=self.speed
self.direction=1
def move_U(self):
# self.y-=self.speed
self.direction=2
def move_D(self):
# self.y+=self.speed
self.direction=3
def update(self):
# self.updateCount = self.updateCount + 1
# if self.updateCount < self.updateCountMax:
for i in range(self.length-1,0,-1):
# print("self.x[" + str(i) + "] = self.x[" + str(i-1) + "]")
self.x[i] = self.x[i-1]
self.y[i] = self.y[i-1]
if(self.direction==0):
self.x[0]+=self.speed
elif(self.direction==1):
self.x[0]-=self.speed
elif(self.direction==2):
self.y[0]-=self.speed
elif(self.direction==3):
self.y[0]+=self.speed
# self.rect.center=(self.x,self.y)
# self.updateCount = 0
# for i in range(0,self.length):
# print(f"{self.x[i]},{self.y[i]}")
self.draw()
def draw(self):
for i in range(0,self.length):
self.screen.blit(self.image,(self.x[i],self.y[i]))
# print(f"rendered at {self.x[i]},{self.y[i]}")
# self.rect.center=(self.x[i],self.y[i])
class app:
width=1200
height=900
title="Snake"
done=False
def __init__(self):
pygame.init()
self.image=pygame.Surface((11,11))
self.image.fill((0,128,255))
pygame.display.set_caption(self.title)
self.screen = pygame.display.set_mode((self.width, self.height))
self.screen.fill((0,0,0))
self.clock=pygame.time.Clock()
self.snakes=pygame.sprite.Group()
self.player1=snake(500,10,self.image,self.screen)
self.snakes.add(self.player1)
self.background = pygame.Surface(self.screen.get_size())
self.background = self.background.convert()
self.background.fill((255,255,255))
def loop(self):
while(not self.done):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
pygame.event.pump()
keys = pygame.key.get_pressed()
if (keys[pygame.K_RIGHT]):
self.player1.move_R()
if (keys[pygame.K_LEFT]):
self.player1.move_L()
if (keys[pygame.K_UP]):
self.player1.move_U()
if (keys[pygame.K_DOWN]):
self.player1.move_D()
if (keys[pygame.K_ESCAPE]):
self.done = True
self.screen.blit(self.background,(0,0))
self.screen.fill((0,0,0))
self.player1.update()
self.snakes.draw(self.screen)
pygame.display.update()
self.clock.tick(60)
pygame.quit()
if __name__ == "__main__" :
theApp = app()
theApp.loop()
You add player1 to the snakes sprite group and draw that with self.snakes.draw(self.screen). However, you also draw the player in self.player1.update(), in the last line.
Remove self.snakes.draw(self.screen) to get rid of the phantom snake.
BTW: you create and set a self.background but you immediately overwrite it with self.screen.fill((0,0,0)), so you don't need a background at all.
What you see in the top left corner is the self.image of the player1 sprite. The draw method of sprite groups blits the images at the rect.topleft coordinates of the sprites and since you never move the player1.rect, the image will be blitted at the default (0, 0) coordinates. So just remove the line self.snakes.draw(self.screen) to fix this.
I also suggest that you use pygame.Rects instead of the self.x and self.y lists. You can create rect instances with the init_x and init_y coords as the topleft attribute and put them into a self.rects list. That allows you to simplify the update and draw methods, and the rects can also be used for the collision detection. I've already refactored your code (it kind of became a mini code review):
import pygame
class Snake(pygame.sprite.Sprite): # Use upper camelcase names for classes (see PEP 8).
def __init__(self, init_x, init_y, image,screen):
pygame.sprite.Sprite.__init__(self)
# These are instance attributes now (use class attributes if
# the values should be shared between the instances).
self.speed = 5
self.init_length = 10
self.direction = 0
self.updateCountMax = 2
self.updateCount = 0
self.length = 10
# The body parts are rects now.
self.rects = []
for i in range(self.init_length):
# Append pygame.Rect instances.
self.rects.append(pygame.Rect(init_x, init_y, 11, 11))
self.image = image
self.screen = screen
self.rect = self.rects[0] # I use the first rect as the self.rect.
def update(self):
for i in range(self.length-1, 0, -1):
# Update the topleft (x, y) positions of the rects.
self.rects[i].topleft = self.rects[i-1].topleft
if self.direction == 0:
self.rects[0].x += self.speed
elif self.direction == 1:
self.rects[0].x -= self.speed
elif self.direction == 2:
self.rects[0].y -= self.speed
elif self.direction == 3:
self.rects[0].y += self.speed
def draw(self):
# Iterate over the rects to blit them (I draw the outlines as well).
for rect in self.rects:
self.screen.blit(self.image, rect)
pygame.draw.rect(self.screen, (0, 255, 0), rect, 1)
class App:
width = 1200
height = 900
title = "Snake"
done = False
def __init__(self):
pygame.init()
self.image = pygame.Surface((11, 11))
self.image.fill((0, 128, 255))
pygame.display.set_caption(self.title)
self.screen = pygame.display.set_mode((self.width, self.height))
self.clock = pygame.time.Clock()
self.snakes = pygame.sprite.Group()
self.player1 = Snake(500, 10, self.image, self.screen)
self.snakes.add(self.player1)
def loop(self):
while not self.done:
# Handle the events.
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
keys = pygame.key.get_pressed()
# In Python we simply set the values of the
# attributes directly instead of using getter
# and setter methods.
if keys[pygame.K_RIGHT]:
self.player1.direction = 0
if keys[pygame.K_LEFT]:
self.player1.direction = 1
if keys[pygame.K_UP]:
self.player1.direction = 2
if keys[pygame.K_DOWN]:
self.player1.direction = 3
if keys[pygame.K_ESCAPE]:
self.done = True
# Update the game.
self.player1.update()
# Draw everything.
self.screen.fill((0, 0, 0))
self.player1.draw()
pygame.draw.rect(self.screen, (255, 0, 0), self.player1.rect, 1)
pygame.display.update()
self.clock.tick(60)
pygame.quit()
if __name__ == "__main__" :
the_app = App()
the_app.loop()

The MOUSEBUTTONDOWN command activates itself without me clicking

so i have started making my own game for a project and after the spaceship shoots when you click the mouse it fires again without me pressing anything and it goes on forever. I have included what i have so far below
from livewires import games
import pygame
games.init(screen_width = 840, screen_height = 480, fps = 50)
class Spaceship(games.Sprite):
"""Creates the Spaceship"""
ship_image = games.load_image("spaceship.bmp")
def __init__(self):
super(Spaceship, self).__init__(image = Spaceship.ship_image,
x = games.mouse.x,
bottom = games.screen.height)
def update(self):
""" Move to mouse x position. """
self.x = games.mouse.x
if self.left < 0:
self.left = 0
if self.right > games.screen.width:
self.right = games.screen.width
#Makes the laser spawn on the spaceship
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
new_laser = Laser(self.x, self.y)
games.screen.add(new_laser)
class Laser(games.Sprite):
"""Creates the laser"""
laser_image = games.load_image("laser1.png")
def __init__(self,spaceship_x, spaceship_y):
super(Laser, self).__init__(image = Laser.laser_image,
x = spaceship_x, y = spaceship_y,
dy = -6)
#class enemy(game.Sprite):
def main():
"""Runs the game"""
background = games.load_image("featured-space-policy.jpg", transparent = False)
games.screen.background = background
ship = Spaceship()
games.screen.add(ship)
games.mouse.is_visible = False
games.screen.mainloop()
main()
Just think about it,
Since you set up an event listen for MOUSEBUTTONDOWN, so this will trigger the Laser function but how it will stop?,
So, you need to set up another listener for MOUSEBUTTONUP which will stop the firing of the laser, something like this:
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
new_laser = Laser(self.x, self.y)
games.screen.add(new_laser)
else event.type == pygame.MOUSEBUTTONUP:
remove_laser()#something like that, I'm not sure if it's the right method.

Blit object from a Class into a Function (Pygame)

some of you may have seen my previous questions regarding a Pygame project I'm currently working on, but I decided to do a rewrite and follow a proper object oriented programming since it wasn't really working out.
This is what I have so far:
###### Import & Init ######
import pygame
import os, random, math, copy, sys
pygame.init()
###### Variables ######
displayWidth, displayHeight = 600, 600
shipWidth, shipHeight = 50, 50
# Variables that will be used to centre the ship.
startX = displayWidth / 2
startY = displayHeight - 40
screen = pygame.display.set_mode((displayWidth, displayHeight))
pygame.display.set_caption('Space Arcader')
####### Colours #######
# Colour list containing most common colours.
# Colour R G B
red = (255, 0, 0)
green = ( 0, 255, 0)
blue = ( 0, 0, 255)
grey = (100, 100, 100)
black = ( 0, 0, 0)
white = (255, 255, 255)
# Create a list from the colours in order to call it later.
colourList = [red, green, blue, black, white]
####### Classes #######
# Ship class
class Ship(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("assets/ship.png").convert_alpha()
self.image = pygame.transform.scale(self.image,(shipWidth, shipHeight))
self.transform = self.image
self.rect = self.image.get_rect()
self.rect.centerx = startX
self.rect.centery = startY
# Where the arrow will be pointing on game start
self.angle = 90
def update(self, direction):
if direction == 'right' and self.angle > 20:
self.angle -= 4
elif direction == 'left' and self.angle < 160:
self.angle += 4
self.transform = pygame.transform.rotate(self.image, self.angle)
self.rect = self.transform.get_rect()
self.rect.centerx = startX
self.rect.centery = startY
def draw(self):
screen.blit(self.transform, self.rect)
# Score class
class Score(object):
def __init__(self):
self.total = 0
self.font = pygame.font.SysFont('Helvetica', 15)
self.render = self.font.render('Score: ' + str(self.total), True, white)
self.rect = self.render.get_rect()
self.rect.left = 5
self.rect.bottom = displayHeight - 2
self.render.set_colorkey((0,0,0))
def update(self, delete_scoreList):
self.total += ((len(delete_scoreList)) * 50)
self.render = self.font.render('Score: ' + str(self.total), True, white)
def draw(self):
screen.blit(self.render, self.rect)
# Game class
class MainGame(object):
def __init__(self):
self.score = 0
self.game_over = False
def controls(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
Menu.terminate()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
direction = 'left'
elif event.key == pygame.K_RIGHT:
direction = 'right'
elif event.type == pygame.KEYUP:
direction = None
if event.key == pygame.K_SPACE:
bullet = Bullet()
bullet.rect.x = arrow.rect.x
bullet.rect.y = arrow.rect.y
all_sprites_list.add(bullet)
bulletList.add(bullet)
elif event.key == pygame.K_ESCAPE:
running = False
MenuInit()
def displayInit(self, screen):
# Set screen width and height.
display = pygame.display.set_mode((displayWidth, displayHeight))
# Set the background image of the window.
background = pygame.image.load("assets/background.jpg")
# Blit the background onto the screen.
screen.blit(background, (0, 0))
# Disable mouse visibility.
pygame.mouse.set_visible(False)
# Code to redraw changing/moving objects.
pygame.display.flip()
# Menu class
class Menu:
hovered = False
def __init__(self, text, pos):
self.text = text
self.pos = pos
self.set_rect()
self.draw()
def draw(self):
self.set_rend()
screen.blit(self.rend, self.rect)
def set_rend(self):
menu_font = pygame.font.SysFont('Helvetica', 40)
self.rend = menu_font.render(self.text, True, self.get_color())
def get_color(self):
if self.hovered:
return (white)
else:
return (grey)
def set_rect(self):
self.set_rend()
self.rect = self.rend.get_rect()
self.rect.topleft = self.pos
def terminate():
pygame.quit()
sys.exit()
####### Functions #######
def MenuInit():
# Set the background image of the window.
background = pygame.image.load("assets/menuBackground.jpg")
options = [Menu("Start game", (200, 250)), Menu("Quit", (265, 300))]
# Enable mouse visibility.
pygame.mouse.set_visible(True)
while True:
for option in options:
if option.rect.collidepoint(pygame.mouse.get_pos()):
option.hovered = True
else:
option.hovered = False
option.draw()
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
for option in options:
if option.hovered and option.text == "Start game":
MainInit()
elif option.hovered and option.text == "Quit":
Menu.terminate()
pygame.display.update()
screen.blit(background,(0,0))
def MainInit():
# Manage the refresh rate
clock = pygame.time.Clock()
# Loop the game until the user closes the application.
running = True
# Open an instance of the Game class
game = MainGame()
ship = Ship()
score = Score()
# Main Loop.
while running:
draw = ship.draw()
ship.update(direction)
# ship.update(direction)
# ship.draw()
controls = game.controls()
game.displayInit(screen)
# Refresh rate speed (frames per second).
clock.tick(60)
# Open the menuInit() function which brings up the main menu.
if __name__ == '__main__':
MenuInit()
So my problem is trying to blit the ship and score onto the MainInit() function which calls the game class object as you can see above. Calling the game class object works fine because the background image changes and the controls work perfectly. However, when I follow the same method for ship and score, it doesn't seem to work. In the commented out comments, you can see I tried a few things but I got various errors such as "NameError: global name 'direction' is not defined" or NameError: global name 'update' is not defined
Any pointers? :)
Thank you very much.
The problem is caused by an out-of-scope variable - exactly as the error is telling you: global name 'direction' is not defined".
You use direction in your def MainInit(), but direction is never defined in that function. The place you define/set a direction-variable, is in class MainGame.controls().
The problem is, however, that the direction-variable created in class MainGame.controls() is local only. It will only exist within that specific function, MainGame.controls(). When that function is not used any longer, the value of direction will cease to exist - which is why there is no such thing as direction defined in def MainInit(). It's out of scope.
To fix this problem, you can choose to use direction as a global variable. It requires you to define the direction value outside any functions, so at the very beginning should work.
Whenever you want to read/modify that specific global variable, you should use the global keyword, to tell your Python function that you want to use/modify a global variable, and not a local one
global direction
This might be of interest to you: https://stackoverflow.com/questions/423379/using-global-variables-in-a-function-other-than-the-one-that-created-them
Personally I would not use global variables, but rather store a direction member variable in the Ship-class and directly change that.
Global variables can become quite a mess.

Categories