Memory Game in Pygame [duplicate] - python

This question already has answers here:
how to handle time for different components in pygame
(1 answer)
Adding a particle effect to my clicker game
(1 answer)
How can I show explosion image when collision happens?
(2 answers)
Closed 2 years ago.
I am making a memory game for an assignment. When clicking on two tiles that are not the same, only the first tile shows but the second tile does not show and immediately both the tiles are flipped. Is there a way to slow it down so that both tiles are shown before they are flipped again? I have tried using pygame.time.delay() and time.sleep() but nothing seems to work.
class Game:
def handle_mouse_up(self, position):
# handles the events that take place when the mouse button is clicked
# - self is the Game
# - position is the position where the mouse button is clicked
if len(self.flipped) < 2:
for row in self.board:
for tile in row:
if tile.select(position) and not tile.covered:
self.flipped.append(tile)
if len(self.flipped) == 2:
self.check_matching()
def check_matching(self):
# checks whether both the tiles which are flipped are matching or not
# - self is the Game
if self.flipped[0].same_tiles(self.flipped[1]):
self.flipped[0].show_tile()
self.flipped[1].show_tile()
self.flipped.clear()
else:
self.flipped[0].hide_tile()
self.flipped[1].hide_tile()
self.flipped.clear()
class Tile:
def show_tile(self):
# shows the tile which is clicked
# - self is the Tile
self.covered = False
def hide_tile(self):
# hides the tile which is not clicked
# - self is the Tile
self.covered = True
def select(self, mouse_position):
# checks whether a tile has been clicked on or not
# - self is the Tile
# - mouse_position is the position where the mouse is clicked
mouse_click = False
if self.rect.collidepoint(mouse_position):
if self.covered:
mouse_click = True
self.show_tile()
else:
mouse_click = False
return mouse_click
def same_tiles(self, other_tile):
# checks whether both the tiles are same
# - self is the Tile
# - other_tile is the second tile which is flipped
return self.image == other_tile.image

Change the method hide tiles. Don't hide the tiles, but compute the time when the tile has to be hidden. Use pygame.time.get_ticks() to return the number of milliseconds since pygame.init() was called. Calculate the point in time after that the image has to be hidden. Add a method that evaluates if the time has exceeded and the tile has to be hidden
class Tile:
def __init__(self):
self.hide_time = None
# [...]
def hide_tile(self):
self.hide_time = pygame.time.get_ticks() + 1000 # 1000 milliseconds == 1 second
def hide_timed(self):
if self.hide_time != None and self.hide_time < pygame.time.get_ticks()
self.covered = True
self.hide_time = None
Add a method to Game that evaluate whether the tiles need to be hidden:
class Game:
# [...]
def hide_timed(self):
for row in self.board:
for tile in row:
tile.hide_timed()
Call hide_timed in the application loop in every frame.

Related

Implementing a collision detect feature between a Rect object and a ball in pygame [duplicate]

This question already has an answer here:
Sometimes the ball doesn't bounce off the paddle in pong game
(1 answer)
Closed 2 years ago.
import pygame, random, time
# main function where we call all other functions, start the game loop, quit pygame and clean up the window. Inside we create a game object, display surface, and start the game loop by calling the play method on the game object. There is also a set caption with the title of the game.
def main():
pygame.init()
size =(500,400)
surface=pygame.display.set_mode(size)
pygame.display.set_caption('Pong v2')
game = Game(surface)
game.play()
pygame.quit()
# This is where we define the class game
class Game():
# an object in this class represents a complete game
# here we initialize a game. self is the game that is initialized surface is the display window surface object we also set default values for continuing the game and closing the window. we also define what fps we are running the game at, and define the velocity color position and radius of the ball
def __init__(self,surface):
# defining surface, fps, background color
self.surface=surface
self.FPS=120
self.bg_color=pygame.Color('black')
screen_width = surface.get_width()
screen_height = surface.get_height()
# defining ball attributes
ball_radius=10
ball_pos = [random.randint(ball_radius, screen_width-ball_radius),
random.randint(ball_radius, screen_height-ball_radius)]
ball_color=pygame.Color('white')
ball_velocity=[2,1]
self.ball=Ball(ball_pos,ball_radius,ball_color,ball_velocity,surface)
# defining paddle attributes
rect_left=[50,450]
rect_top=225
rect_height=60
rect_width=10
self.Paddle1=Rect(rect_left[0],rect_top,rect_width,rect_height,surface)
self.Paddle2=Rect(rect_left[1],rect_top,rect_width,rect_height,surface)
self.game_Clock=pygame.time.Clock()
self.close_clicked=False
self.continue_game=True
self.score1=0
self.score2=0
self.frame_counter=0
def play(self):
# game is played until player clicks close
while not self.close_clicked:
self.handle_events()
self.draw()
# if nothing sets this to false the game will continue to update
if self.continue_game:
self.update()
self.game_Clock.tick(self.FPS)
# score is drawn onto the screen (unimportant this is just playing with a feature for the next version), we define color font background etc of the score message and update score upon points being scored
def draw_score(self):
font_color = pygame.Color("white")
font_bg = pygame.Color("black")
font = pygame.font.SysFont("arial", 18)
text_img = font.render("Score for Player 1: " + str(self.score1) + ' Score for Player 2: ' + str(self.score2), True, font_color, font_bg)
text_pos = (0,0)
self.surface.blit(text_img, text_pos)
# ball, surface, score, and two paddles are drawn, pygame also updates this drawing once per frame
def draw(self):
self.surface.fill(self.bg_color)
self.draw_score()
#pygame.draw.rect(self.surface,pygame.Color('blue'),(50,225,10,50))
#pygame.draw.rect(self.surface,pygame.Color('red'),(450,225,10,50))
self.Paddle1.draw()
self.Paddle2.draw()
self.ball.draw()
pygame.display.update()
# score value set to default of 0 we tell ball to move and add 1 to frame counter upon each update. update game object for the next frame
def update(self):
self.ball.move()
self.score=0
self.frame_counter+=self.frame_counter+1
# here we setup an event loop and figure out if the action to end the game has been done
def handle_events(self):
events=pygame.event.get()
for event in events:
if event.type== pygame.QUIT:
self.close_clicked=True
# user defined class ball
class Ball:
# self is the ball to intialize. color/center/radius are defined for the ball that is initialized
def __init__(self,center,radius,color,velocity,surface):
self.center=center
self.radius=radius
self.color=color
self.velocity=velocity
self.surface=surface
# screen size is determined and edge of ball is checked that it is not touching the edge. if it is touching the edge it bounces and reverses velocity
def move(self):
screen_width=self.surface.get_width()
screen_height=self.surface.get_height()
screen_size=(screen_width,screen_height)
for i in range(0,len(self.center)):
self.center[i]+=self.velocity[i]
if (self.center[i]<=0 + self.radius or self.center[i]>=screen_size[i] - self.radius):
self.velocity[i]=-self.velocity[i]
# ball is drawn
def draw(self):
pygame.draw.circle(self.surface,self.color,self.center,self.radius)
class Rect:
def __init__(self,left,top,width,height,surface):
#self.left=left
#self.top=top
#self.width=width
#self.height=height
self.surface=surface
self.rect=pygame.Rect(left,top,width,height)
def draw(self):
pygame.draw.rect(self.surface,pygame.Color('red'),self.rect)
def collide(self):
if pygame.Rect.collide(x,y) == True:
return True
else:
return False
main()
Above is my work so far basically its supposed to be the retro arcade game pong where the ball bounces off of the edges of the paddles and if it doesn't the opposite side scores a point upon it hitting the edge of the window. So specifically this part of the project requires me to make the ball bounce off of the front of the paddles and I'm confused as to how to do this. My idea originally was to use the collidepoint method inside of the class Rect that if it returns true would reverse the balls velocity. However, I don't have access to the centre coordinates of the ball inside of the class or inside of the method play in the class game where I intended to make this work on the specific instances of ball and paddle1,paddle2 so I don't know how to do this.
Evaluate if the ball hits the left paddle at the right, respectively the right paddle at the left in Game.update.
If the ball hits the paddle the the score can be incremented:
class Game():
# [...]
def update(self):
self.ball.move()
# evaluate if Ball hits the left paddle (Paddle1) at the right
if self.ball.velocity[0] < 0 and self.Paddle1.rect.top <= self.ball.center[1] <= self.Paddle1.rect.bottom:
if self.Paddle1.rect.right <= self.ball.center[0] <= self.Paddle1.rect.right + self.ball.radius:
self.ball.velocity[0] = -self.ball.velocity[0]
self.ball.center[0] = self.Paddle1.rect.right + self.ball.radius
self.score1 += 1
# evaluate if Ball hits the right paddle (Paddle2) at the left
if self.ball.velocity[0] > 0 and self.Paddle2.rect.top <= self.ball.center[1] <= self.Paddle2.rect.bottom:
if self.Paddle2.rect.left >= self.ball.center[0] >= self.Paddle2.rect.left - self.ball.radius:
self.ball.velocity[0] = -self.ball.velocity[0]
self.ball.center[0] = self.Paddle2.rect.left - self.ball.radius
self.score2 += 1
Get the state of the keys by pygame.key.get_pressed() and change the position of the paddles is Game.handle_events.
e.g. Move the left paddle by w / s and the right paddle by UP / DOWN:
class Game():
# [...]
def handle_events(self):
events=pygame.event.get()
for event in events:
if event.type== pygame.QUIT:
self.close_clicked=True
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
self.Paddle1.rect.top = max(0, self.Paddle1.rect.top - 3)
if keys[pygame.K_s]:
self.Paddle1.rect.bottom = min(self.surface.get_height(), self.Paddle1.rect.bottom + 3)
if keys[pygame.K_UP]:
self.Paddle2.rect.top = max(0, self.Paddle2.rect.top - 3)
if keys[pygame.K_DOWN]:
self.Paddle2.rect.bottom = min(self.surface.get_height(), self.Paddle2.rect.bottom + 3)

The pong ball is just stopped in the initial point by unknown issue

I am new to Pygame, and doing a assigned homework. I tried to make an program called Pong by python3. The ball was moving correctly, but after I write something new in the program, the ball just stopped at the initial point. I already check many times of my code, but I can't find what's wrong with it.
Here is my code below:
# Pong Version 2
# Display the ball and both paddles, with the ball bouncing from the window edges.
# The ball bounce off the paddles. It go through the back of paddles.
# The paddles do not move, but score is updated.
# The game ends when a score is 11 or until the player closes the window.
import pygame, random
def main():
# initialize all pygame modules (some need initialization)
pygame.init()
# create a pygame display window
pygame.display.set_mode((1000, 800))
# set the title of the display window
pygame.display.set_caption('Pong')
# get the display surface
w_surface = pygame.display.get_surface()
# create a game object
game = Game(w_surface)
#init text
pygame.font.init()
# start the main game loop by calling the play method on the game object
game.play()
# quit pygame and clean up the pygame window
pygame.quit()
class Game:
# An object in this class represents a complete game.
def __init__(self, surface):
# Initialize a Game.
# - self is the Game to initialize
# - surface is the display window surface object
# === objects that are part of every game that we will discuss
self.surface = surface
self.bg_color = pygame.Color('black')
self.FPS = 60
self.game_Clock = pygame.time.Clock()
self.close_clicked = False
self.continue_game = True
self.num_board1 = '0'
self.num_board2 = '0'
# === game specific objects
#ball elements
#(ball_color,ball_radius,ball_center,ball_velocity,surface)
self.ball = Ball('white', 10, [500, 400], [10,20], self.surface)
#rectangle elements
#(rect_left_top,rect_width_height,rect_surface,rect_color)
self.paddle1 = Rect((150,350),(10,100),self.surface,'white')
self.paddle2 = Rect((840,350),(10,100),self.surface,'white')
#board elements
#(name,size,content,color,center,screen)
#board size is (57,104)
self.board1 = Board("",150,self.num_board1,(100,100,100),(10,10),self.surface)
self.board2 = Board("",150,self.num_board2,(100,100,100),(933,10),self.surface)
def play(self):
# Play the game until the player presses the close box.
# - self is the Game that should be continued or not.
while not self.close_clicked: # until player clicks close box
# play frame
self.handle_events()
self.draw()
if self.continue_game:
self.update()
self.decide_continue()
self.game_Clock.tick(self.FPS) # run at most with FPS Frames Per Second
def handle_events(self):
# Handle each user event by changing the game state appropriately.
# - self is the Game whose events will be handled
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
self.close_clicked = True
def draw(self):
# Draw all game objects.
# - self is the Game to draw
self.surface.fill(self.bg_color) # clear the display surface first
# Draw game elements ball
self.ball.draw()
# Draw game elements paddles
self.paddle1.draw()
self.paddle2.draw()
# Draw game elements boards
self.board1.draw()
self.board2.draw()
pygame.display.update() # make the updated surface appear on the display
def update(self):
# Update the game objects.
# - self is the Game to update
self.ball.move()
def decide_continue(self):
# Check and remember if the game should continue
# - self is the Game to check
pass
if self.num_board1 or self.num_board2 == '11':
self.continue_game = False
class Ball:
# An object in this class represents a ball that moves
def __init__(self, ball_color, ball_radius, ball_center, ball_velocity, surface):
# Initialize a ball.
# - self is the ball to initialize
# - color is the pygame.Color of the ball
# - center is a list containing the x and y int
# coords of the center of the ball
# - radius is the int pixel radius of the ball
# - velocity is a list containing the x and y components
# - surface is the window's pygame.Surface object
self.color = pygame.Color(ball_color)
self.radius = ball_radius
self.center = ball_center
self.velocity = ball_velocity
self.surface = surface
def move(self):
# Change the location of the ball by adding the corresponding
# speed values to the x and y coordinate of its center
# - self is the ball
size = self.surface.get_size() # size is a tuple (width,height)
for index in range(0,2):
self.center[index] = self.center[index] + self.velocity[index]
if self.center[index] < self.radius: # left or top
self.velocity[index] = -self.velocity[index] # bounce the ball
if self.center[index]+ self.radius > size[index]:# right of bottom
self.velocity[index] = -self.velocity[index] # bounce the ball
def draw(self):
# Draw the ball on the surface
# - self is the ball
pygame.draw.circle(self.surface, self.color, self.center, self.radius)
class Rect:
def __init__(self,rect_left_top,rect_width_height,rect_surface,rect_color):
#set elements
# - rect_left_top is the distance from edge
# - rect_width_height is width and height of the rectangle
# - rect_surface is the surface of rectangle
# - rect_color is color of the rectangle
self.left_top = rect_left_top
self.width_height = rect_width_height
self.surface = rect_surface
self.color = pygame.Color(rect_color)
self.rect = (self.left_top,self.width_height)
def draw(self):
# draw the rectangle
pygame.draw.rect(self.surface,self.color,self.rect)
class Board:
def __init__(self,name,size,content,color,text_left_right,surface):
# - name is the typeface of the text
# - size is the size of the text
# - content is the content fo the text
# - color is the color of text, it looks like (x,x,x), which is combined by three basic color)
# - text_left_right is a the left and right distance from edge
# - surface is a the display.set_mode, used to blit text
self.name = name
self.size = size
self.content = content
self.color = color
self.text_left_right = text_left_right
self.surface = surface
def draw(self):
#to show the text
font = pygame.font.SysFont(self.name,self.size)
text = font.render(self.content,True,self.color)
self.surface.blit(text,self.text_left_right)
main()
The problem with decide_continue method.
When I remove call of decide_continue from play method ball start moving:
def play(self):
# Play the game until the player presses the close box.
# - self is the Game that should be continued or not.
while not self.close_clicked: # until player clicks close box
# play frame
self.handle_events()
self.draw()
if self.continue_game:
self.update()
self.game_Clock.tick(self.FPS) # run at most with FPS Frames Per Second
So the problem with decide_continue method. You should review it.
Going by the test done by Yurii (see other answer), since you didn't mention which change you had made before the program didn't work properly anymore.
decide_continue(self) has the following if-clause:
if self.num_board1 or self.num_board2 == '11':
This means it evaluates whether self.num_board1 is equivalent to True (i.e., not False, 0, empty string and the like), which is probably always the case, or it compares self.num_board2 to the string '11'.
Important note: the string '0', the initial value for the boards, does not evaluate to False: it's not an empty string, nor an integer 0.
Because of the first condition, this if-statement will practically always evaluate in being true.
You probably want:
if self.num_board1 == '11' or self.num_board2 == '11':
or something like that. You should also consider whether you want a '11' string, or that this should be a number (integer) instead, given that you call the variable num_board.
Further, since no manipulations with num_board1/2 appear to be done, it's value will likely never change from the initial value '0', in which case it'll, at this moment, never be equal to '11'.

Python: Creating a slot based inventory system

I am trying to create an inventory system in Python using the module pygame. The inventory system is similar to Minecraft. It is a slot-based inventory system, meaning that each item is attached to a slot, and can be moved around the inventory.
This is the code:
#Module that allows me to create a screen and add images to the screen
import pygame
pygame.init()
win = pygame.display.set_mode((800,600))
#Variable that will keep track of the index of what slot the player is
#selecting
selectedSlot = None
#Function that checks whether there is collision or not
def collision(x,y,x2,y2,w):
if x + w > x2 > x and y+w > y2 > y:
return True
else:
return False
#Slot Class
class slotClass:
def __init__(self,x,y):
self.x = x
self.y = y
def draw(self,win):
#Draws the slots using the x and y values
pygame.draw.rect(win,(255,0,0),(self.x,self.y,50,50))
#Uses a function to test for collision with the mouse's x and y values
if collision(self.x,self.y,mx,my,66):
global selectedSlot
pygame.draw.rect(win,(128,0,0),(self.x,self.y,50,50))
#This will set an integer value to a varaible, dependent on what the index of the element the player selecting is
selectedSlot = slotArray.index(self)
#Problem with code:
#When the following 2 lines are uncommmented, the variable selectedSlot is set to "None", regardless of whether there is collision or not
#else:
#selectedSlot = None
#Slot array
slotArray = []
#Slot information
slotCount = 9
#Populates the slotArray with the desired slotCount
while len(slotArray) != slotCount:
slotArray.append(slotClass(100+len(slotArray)*70,50))
#main loop
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
print(selectedSlot)
#Mouse x and y value to set to the vars mx and my
mx,my = pygame.mouse.get_pos()
win.fill((0,0,0))
#For every element in the slotArray, the function draw is called from the slotClass
for i in slotArray:
i.draw(win)
pygame.display.update()
pygame.quit()
How it works is I have a class, which will hold the information for each slot. Such as the x value, y value and what item is in every slot.
Then I have an array, which will contain every slot instance. I also defined how many slots I want entirely.
To populate this array with the slots, I first started by constantly appending to this array of slots until it equals the desired amount of slots in each row. I multiply 55 by the number of elements in the array to spread apart the slots.
The problem I am having comes when trying to create the ability for the player to mouse over/select each slot. What I want is for the player to simply be able to hover over a slot, that slot will turn a different colour, and then the player can select an item out of said slot.
I created a collision function for that and I'm calling that function within the draw function inside the slotClass. I also have a variable called slotSelected, which keeps track of the index of the slot that the player is mousing over with/hovering over.
The problem I am experiencing is, that whenever the player hovers over a slot and then stops hovering over any slots, the slot index that I am setting still remains to be the index of the slot the player was just on. When I add an else statement to check if there is no collision with a slot, and set the var slotSelected to something like None for example (to show that the player isn't colliding with any slot) the var slotSelected is constantly set to None, regardless of whether there is collision or not. I have a print statement that prints the value of slotSelected. You'll notice that it prints the index of the slot the player is colliding with. However, when you uncomment the else statement I have contained in the slotClass, the var slotSelected will always be set to None.
Any suggestions on how to fix this?
Perhaps you could try testing for another collision with the background as well. I'm not an expert at pygame, but this is the pseudo-code I would use:
if collision("with game window"): # If mouse is within your game screen
global selectedSlot
if collision(self.x, self.y, mx, my, 66): # mouse is on screen AND on a box
selectedSlot = slotArray.index(self)
else: # mouse on screen, but not on box -> return None
selectedSlot = None
So that you can assign None when the mouse is in your game window but not on an item slot.
EDIT
I figured out why it is responding as such. You have the lines:
for i in slotArray:
i.draw(win)
Which will go to slot 0, see it is selected -> set selectedSlot = 0, then go to slot 1, see it is unselected -> set selectedSlot = None OVERWRITING the value you had previously set. You will need to break your loop if selectedSlot is not None!! Here is code to solve that:
#Module that allows me to create a screen and add images to the screen
import pygame
pygame.init()
win = pygame.display.set_mode((800,600))
#Variable that will keep track of the index of what slot the player is
#selecting
selectedSlot = None
#Function that checks whether there is collision or not
def collision(x,y,x2,y2,w):
if x + w > x2 > x and y+w > y2 > y:
return True
else:
return False
#Slot Class
class slotClass:
def __init__(self,x,y):
self.x = x
self.y = y
def draw(self, win):
#Draws the slots using the x and y values
pygame.draw.rect(win, (255, 0, 0), (self.x, self.y, 50, 50))
#Uses a function to test for collision with the mouse's x and y values
if collision(self.x, self.y, mx, my, 66):
global selectedSlot
pygame.draw.rect(win, (128, 0, 0), (self.x, self.y, 50, 50))
#This will set an integer value to a varaible, dependent on what the index of the element the player selecting is
selectedSlot = slotArray.index(self)
#Problem with code:
#When the following 2 lines are uncommmented, the variable selectedSlot is set to "None", regardless of whether there is collision or not
else:
selectedSlot = None
#Slot array
slotArray = []
#Slot information
slotCount = 9
#Populates the slotArray with the desired slotCount
while len(slotArray) != slotCount:
slotArray.append(slotClass(100+len(slotArray)*70,50))
#main loop
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
#Mouse x and y value to set to the vars mx and my
mx,my = pygame.mouse.get_pos()
win.fill((0,0,0))
#For every element in the slotArray, the function draw is called from the slotClass
selectedSlot = None
for i in slotArray:
i.draw(win)
if selectedSlot is not None:
break
print(selectedSlot)
pygame.display.update()
pygame.quit()
The problem is you've combined drawing the items and selecting them into one function (very bad). So now when the loop is broken the rest of the boxes are not drawn!!

Overlay tiles onto sprite in pygame

I am creating a tile-based 2d overworld for a game - heavily influenced by Pokemon - using pygame/python, Tiled for .tmx files, and the tmx library by Richard Jones. The code I'm using is mostly based on this wonderful demo of Pallet Town in python.
The game runs just fine, however, I am having problems with making tiles on the map (e.g. houses, trees) overlap the player sprite when it would make sense for the player sprite to disappear behind them. For example: in the image here, principles of depth perception would tell us that the house in the foreground should occlude the player in the background, but because the map is 2D there is no depth and therefore no occlusion. I would love to add depth, but seeing as I'm very new to pygame (and python in general), I'm at a loss at how to draw the relevant foreground objects over the sprite.
Luckily I'm not alone in this problem and plenty of documentation on possible solutions exist. For example:
this StackExchange question
this LibGDX tutorial
this Unity tutorial
However, this code isn't typically written for python and I'm not sure how to implement it in my situation. Sorting/drawing by z position (or by a 'depth' property) seems like the most sensible thing to do, but looking at the tmx library I can only find x and y values mentioned. Adding the player sprite to an empty object layer in Tiled is also a solution, but once again I'm unsure of how to do this and all my attempts have led to error messages. (Attempts not detailed here because I honestly don't know what I did and it didn't work anyway.)
My current code is as follows:
class Player(pygame.sprite.Sprite):
def __init__(self, location, collStart, orientation, *groups):
super(Player, self).__init__(*groups)
self.image = pygame.image.load('sprites/player.png')
self.imageDefault = self.image.copy()
self.rect = pygame.Rect(location, (26,26))
self.collider = pygame.Rect(collStart, (13,13))
self.orient = orientation
self.holdTime = 0
self.walking = False
self.dx = 0
self.step = 'rightFoot'
# Set default orientation
self.setSprite()
self.speed = pygame.time.get_ticks() + 50 # slows down walking speed
by .5 sec (current time + 50 ms)
def setSprite(self):
# this function contains information about where to find which sprite
in the sprite sheet, probably not relevant here.
def update(self, dt, game):
key = pygame.key.get_pressed()
if pygame.time.get_ticks() >= self.speed:
self.speed = pygame.time.get_ticks() + 50
# Setting orientation and sprite based on key input, removed the
#code here because it wasn't relevant
#[....]
# Walking mode enabled if a button is held for 0.1 seconds
if self.holdTime >= 100:
self.walking = True
lastRect = self.rect.copy()
lastColl = self.collider.copy() # collider covers the bottom section of the sprite
# Code for walking in the direction the player is facing, not relevant here
#[....]
# Collision detection:
# Reset to the previous rectangle if player collides
# with anything in the foreground layer
if len(game.tilemap.layers['triggers'].collide(self.collider,
'solid')) > 0:
self.rect = lastRect
self.collider = lastColl
# Area entry detection, loads dialog screen from the dialog file:
elif len(game.tilemap.layers['triggers'].collide(self.collider,
'entry')) > 0:
entryCell = game.tilemap.layers['triggers'].find('entry')[0]
game.fadeOut()
run()
pygame.quit()
quit()
return
if self.dx == 16:
# Makes the player appear to take steps w/ different feet, not relevant here
#[....]
# After traversing 32 pixels, the walking animation is done
if self.dx == 32:
self.walking = False
self.setSprite()
self.dx = 0
game.tilemap.set_focus(self.rect.x, self.rect.y)
class Game(object):
def __init__(self, screen):
self.screen = screen
def initArea(self, mapFile):
"""Load maps and initialize sprite layers for each new area"""
self.tilemap = tmx.load(mapFile, screen.get_size())
self.players = tmx.SpriteLayer()
self.objects = tmx.SpriteLayer()
# In case there is no sprite layer for the current map
except KeyError:
pass
else:
self.tilemap.layers.append(self.objects)
# Initializing player sprite
startCell = self.tilemap.layers['triggers'].find('playerStart')[0]
self.player = Player((startCell.px, startCell.py), (startCell.px,
startCell.bottom-4),
startCell['playerStart'], self.players)
self.tilemap.layers.append(self.players)
self.tilemap.set_focus(self.player.rect.x, self.player.rect.y)
def main(self):
clock = pygame.time.Clock()
self.initArea('test tilemap.tmx')
while 1:
dt = clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
return
self.tilemap.update(dt, self)
screen.fill((0,0,0))
self.tilemap.draw(self.screen)
pygame.display.flip()
Once again, I'm using the tmx library found here. Maybe something needs to be changed there? Hopefully, someone can help me figure this out. It's definitely possible, as shown by this pokemon clone in python (no source code available, sadly).
Also, first-time StackOverflow user so let me know if I'm committing any faux-passes :)
Figured it out! As suggested by Kingsley in the comments, the solution was to change the draw order of the layers. Layers were being drawn in order of a list in the Layers class, with the player sprite having the highest index and thus being drawn on top of everything else. Moving the player between the background and foreground layer in the list made it appear behind the foreground objects.
To do this I added the following code to the initArea function in the Game class:
def initArea(self, mapFile):
"""Load maps and initialize sprite layers for each new area"""
self.tilemap = tmx.load(mapFile, screen.get_size())
self.players = tmx.SpriteLayer()
self.objects = tmx.SpriteLayer()
# Initializing player sprite
startCell = self.tilemap.layers['triggers'].find('playerStart')[0]
self.player = Player((startCell.px, startCell.py), (startCell.px, startCell.bottom-4),
startCell['playerStart'], self.players)
foregroundItem = self.tilemap.layers.__getitem__("foreground") # finds the layer called foreground
foregroundIndex = self.tilemap.layers.index(foregroundItem) # finds the position of the foreground layer in the Layers list (Layers class specified in .tmx file)
self.tilemap.layers.insert(foregroundIndex-1, self.players) # move the Player layer one layer below the foreground layer
self.tilemap.set_focus(self.player.rect.x, self.player.rect.y)
I'll experiment a bit more tonight, but for now this solution seems to work. Thanks for the help!

Understand object orientated python - initialization of objects in pygame

I'm learning Object Orientated Python and understand the main principals of classes and creating objects from classes however I need something explained Re: the pygame code below. I'm struggling to get my head around what's happening when sprite lists are being created and the two lines of code under the code which creates the ball object (allsprites.add etc). In other words what are sprites and why are lists of them created? Why isn't the ball object just created from the class on its own? Why does it need to be added to a sprite list?? What's going on? Any explanation would be greatly appreciated.
"""
Sample Breakout Game
Sample Python/Pygame Programs
Simpson College Computer Science
http://programarcadegames.com/
http://simpson.edu/computer-science/
"""
# --- Import libraries used for this program
import math
import pygame
# Define some colors
black = (0, 0, 0)
white = (255, 255, 255)
blue = (0, 0, 255)
# Size of break-out blocks
block_width = 23
block_height = 15
class Block(pygame.sprite.Sprite):
"""This class represents each block that will get knocked out by the ball
It derives from the "Sprite" class in Pygame """
def __init__(self, color, x, y):
""" Constructor. Pass in the color of the block,
and its x and y position. """
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
# Create the image of the block of appropriate size
# The width and height are sent as a list for the first parameter.
self.image = pygame.Surface([block_width, block_height])
# Fill the image with the appropriate color
self.image.fill(color)
# Fetch the rectangle object that has the dimensions of the image
self.rect = self.image.get_rect()
# Move the top left of the rectangle to x,y.
# This is where our block will appear..
self.rect.x = x
self.rect.y = y
class Ball(pygame.sprite.Sprite):
""" This class represents the ball
It derives from the "Sprite" class in Pygame """
# Speed in pixels per cycle
speed = 10.0
# Floating point representation of where the ball is
x = 0.0
y = 180.0
# Direction of ball (in degrees)
direction = 200
width = 10
height = 10
# Constructor. Pass in the color of the block, and its x and y position
def __init__(self):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
# Create the image of the ball
self.image = pygame.Surface([self.width, self.height])
# Color the ball
self.image.fill(white)
# Get a rectangle object that shows where our image is
self.rect = self.image.get_rect()
# Get attributes for the height/width of the screen
self.screenheight = pygame.display.get_surface().get_height()
self.screenwidth = pygame.display.get_surface().get_width()
def bounce(self, diff):
""" This function will bounce the ball
off a horizontal surface (not a vertical one) """
self.direction = (180 - self.direction) % 360
self.direction -= diff
def update(self):
""" Update the position of the ball. """
# Sine and Cosine work in degrees, so we have to convert them
direction_radians = math.radians(self.direction)
# Change the position (x and y) according to the speed and direction
self.x += self.speed * math.sin(direction_radians)
self.y -= self.speed * math.cos(direction_radians)
# Move the image to where our x and y are
self.rect.x = self.x
self.rect.y = self.y
# Do we bounce off the top of the screen?
if self.y <= 0:
self.bounce(0)
self.y = 1
# Do we bounce off the left of the screen?
if self.x <= 0:
self.direction = (360 - self.direction) % 360
self.x = 1
# Do we bounce of the right side of the screen?
if self.x > self.screenwidth - self.width:
self.direction = (360 - self.direction) % 360
self.x = self.screenwidth - self.width - 1
# Did we fall off the bottom edge of the screen?
if self.y > 600:
return True
else:
return False
class Player(pygame.sprite.Sprite):
""" This class represents the bar at the bottom that the player controls. """
def __init__(self):
""" Constructor for Player. """
# Call the parent's constructor
pygame.sprite.Sprite.__init__(self)
self.width = 75
self.height = 15
self.image = pygame.Surface([self.width, self.height])
self.image.fill((white))
# Make our top-left corner the passed-in location.
self.rect = self.image.get_rect()
self.screenheight = pygame.display.get_surface().get_height()
self.screenwidth = pygame.display.get_surface().get_width()
self.rect.x = 0
self.rect.y = self.screenheight-self.height
def update(self):
""" Update the player position. """
# Get where the mouse is
pos = pygame.mouse.get_pos()
# Set the left side of the player bar to the mouse position
self.rect.x = pos[0]
# Make sure we don't push the player paddle
# off the right side of the screen
if self.rect.x > self.screenwidth - self.width:
self.rect.x = self.screenwidth - self.width
# Call this function so the Pygame library can initialize itself
pygame.init()
# Create an 800x600 sized screen
screen = pygame.display.set_mode([800, 600])
# Set the title of the window
pygame.display.set_caption('Breakout')
# Enable this to make the mouse disappear when over our window
pygame.mouse.set_visible(0)
# This is a font we use to draw text on the screen (size 36)
font = pygame.font.Font(None, 36)
# Create a surface we can draw on
background = pygame.Surface(screen.get_size())
# Create sprite lists
blocks = pygame.sprite.Group()
balls = pygame.sprite.Group()
allsprites = pygame.sprite.Group()
# Create the player paddle object
player = Player()
allsprites.add(player)
# Create the ball
ball = Ball()
allsprites.add(ball)
balls.add(ball)
# The top of the block (y position)
top = 80
# Number of blocks to create
blockcount = 32
# --- Create blocks
# Five rows of blocks
for row in range(5):
# 32 columns of blocks
for column in range(0, blockcount):
# Create a block (color,x,y)
block = Block(blue, column * (block_width + 2) + 1, top)
blocks.add(block)
allsprites.add(block)
# Move the top of the next row down
top += block_height + 2
# Clock to limit speed
clock = pygame.time.Clock()
# Is the game over?
game_over = False
# Exit the program?
exit_program = False
# Main program loop
while exit_program != True:
# Limit to 30 fps
clock.tick(30)
# Clear the screen
screen.fill(black)
# Process the events in the game
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit_program = True
# Update the ball and player position as long
# as the game is not over.
if not game_over:
# Update the player and ball positions
player.update()
game_over = ball.update()
# If we are done, print game over
if game_over:
text = font.render("Game Over", True, white)
textpos = text.get_rect(centerx=background.get_width()/2)
textpos.top = 300
screen.blit(text, textpos)
# See if the ball hits the player paddle
if pygame.sprite.spritecollide(player, balls, False):
# The 'diff' lets you try to bounce the ball left or right
# depending where on the paddle you hit it
diff = (player.rect.x + player.width/2) - (ball.rect.x+ball.width/2)
# Set the ball's y position in case
# we hit the ball on the edge of the paddle
ball.rect.y = screen.get_height() - player.rect.height - ball.rect.height - 1
ball.bounce(diff)
# Check for collisions between the ball and the blocks
deadblocks = pygame.sprite.spritecollide(ball, blocks, True)
# If we actually hit a block, bounce the ball
if len(deadblocks) > 0:
ball.bounce(0)
# Game ends if all the blocks are gone
if len(blocks) == 0:
game_over = True
# Draw Everything
allsprites.draw(screen)
# Flip the screen and show what we've drawn
pygame.display.flip()
pygame.quit()
You don't need to add the balls and blocks to sprite lists - it's just a matter of convenience. You could manually check each ball for a collision, but it's easier to just tell pygame to check them all for you
# See if the ball hits the player paddle
if pygame.sprite.spritecollide(player, balls, False):
# The 'diff' lets you try to bounce the ball left or right
# depending where on the paddle you hit it
diff = (player.rect.x + player.width/2) - (ball.rect.x+ball.width/2)
# Set the ball's y position in case
# we hit the ball on the edge of the paddle
ball.rect.y = screen.get_height() - player.rect.height - ball.rect.height - 1
ball.bounce(diff)
You could draw each thing to the screen separately on each frame, but it's easier just to tell pygame to do it for you:
# Draw Everything
allsprites.draw(screen)
Things can be in more than one list as required, for example a ball is added to the balls list so that you can easily check for collisions, but also added to the allsprites list so that you can easily draw everything on the screen
# Create the ball
ball = Ball()
allsprites.add(ball)
balls.add(ball)
Edit:
An important distinction is that allsprites is actually a sprite.Group. It has a list of sprites inside it, but it also has other methods like draw.
To address your question of "what is a Sprite", it's simply a thing that gets drawn on screen. pygame methods like sprite.Group.draw expect a list of things with certain attributes - such as update. The easiest way to make sure that you provide all of those attributes with the right names is to subclass Sprite, however this is also a (strongly recommended) convenience thing - for instance, this is from the pygame source code:
While it is possible to design sprite and group classes that don't
derive from the Sprite and AbstractGroup classes below, it is strongly
recommended that you extend those when you add a Sprite or Group
class.
So what specifically does subclassing Sprite get you? Let's take a look at the source. Here's how to find the source code for a python module:
>>> import pygame.sprite
>>> pygame.sprite.__file__
'c:\\Python27\\lib\\site-packages\\pygame\\sprite.py'
>>>
Every python module has a __file__ attribute that tells you where the source is located (well not quite every). If you open it up in your editor, and scroll down, you see the class definition for Sprite:
class Sprite(object):
"""simple base class for visible game objects
pygame.sprite.Sprite(*groups): return Sprite
The base class for visible game objects. Derived classes will want to
override the Sprite.update() and assign a Sprite.image and
Sprite.rect attributes. The initializer can accept any number of
Group instances to be added to.
When subclassing the Sprite, be sure to call the base initializer before
adding the Sprite to Groups.
"""
def __init__(self, *groups):
self.__g = {} # The groups the sprite is in
if groups: self.add(groups)
def add(self, *groups):
"""add the sprite to groups
Sprite.add(*groups): return None
Any number of Group instances can be passed as arguments. The
Sprite will be added to the Groups it is not already a member of.
"""
has = self.__g.__contains__
for group in groups:
if hasattr(group, '_spritegroup'):
if not has(group):
group.add_internal(self)
self.add_internal(group)
else: self.add(*group)
def remove(self, *groups):
"""remove the sprite from groups
Sprite.remove(*groups): return None
Any number of Group instances can be passed as arguments. The Sprite will
be removed from the Groups it is currently a member of.
"""
has = self.__g.__contains__
for group in groups:
if hasattr(group, '_spritegroup'):
if has(group):
group.remove_internal(self)
self.remove_internal(group)
else: self.remove(*group)
def add_internal(self, group):
self.__g[group] = 0
def remove_internal(self, group):
del self.__g[group]
def update(self, *args):
"""method to control sprite behavior
Sprite.update(*args):
The default implementation of this method does nothing; it's just a
convenient "hook" that you can override. This method is called by
Group.update() with whatever arguments you give it.
There is no need to use this method if not using the convenience
method by the same name in the Group class.
"""
pass
def kill(self):
"""remove the Sprite from all Groups
Sprite.kill(): return None
The Sprite is removed from all the Groups that contain it. This won't
change anything about the state of the Sprite. It is possible to continue
to use the Sprite after this method has been called, including adding it
to Groups.
"""
for c in self.__g.keys():
c.remove_internal(self)
self.__g.clear()
def groups(self):
"""list of Groups that contain this Sprite
Sprite.groups(): return group_list
Return a list of all the Groups that contain this Sprite.
"""
return self.__g.keys()
def alive(self):
"""does the sprite belong to any groups
Sprite.alive(): return bool
Returns True when the Sprite belongs to one or more Groups.
"""
return (len(self.__g) != 0)
def __repr__(self):
return "<%s sprite(in %d groups)>" % (self.__class__.__name__, len(self.__g))
So in summary, you don't have to subclass Sprite - you could just provide all of these methods on your own - but it's easier if you do ;)

Categories

Resources