I need help figuring out tile collisions for a platformer I'm currently making in Pygame. I have movement, with gravity working, as well as a tile map, but I don't really understand how to get collisions with the sprites working. I did make a list that appends all of the tiles that aren't 0 (so 1 or 2) into a list called tile_collisions (see line 113) but I don't really know what to do with that. Also, you can ignore the commented-out code, that was just a failed attempt. I'm trying to make this as simple as possible, without classes, because those aren't allowed for this assignment.
https://github.com/Night-28/Treble-Quest.git
^ The "main.py" file won't run without all of the pngs so if you do want to run it, you might have to download all of them, sorry!
import pygame as py
from tile_map import map
# Pygame setup
py.init()
clock = py.time.Clock()
# COLOURS
bg_colour = (110, 121, 228)
sky_colour = (190, 220, 255)
start_colour = (225, 225, 225)
screen_width = 1280
screen_height = 720
screen = py.display.set_mode((screen_width, screen_height))
p_sprite = py.image.load("plant_drone.png")
p_rect = p_sprite.get_rect()
p_rect.centery = screen_height - 32
grass_block = py.image.load("grass_block.png")
dirt_block = py.image.load("dirt_block.png")
# def collisions(rect, tiles):
# collider_list = []
# for tile in tiles:
# if rect.colliderect(tile):
# collider_list.append(tile)
# return collider_list
#
# def move(rect, movement, tiles):
# collision_types = {"top": False, "bottom": False, "right": False, "left": False}
#
# rect.x += movement[0]
# collider_list = collisions(rect, tile)
# for tile in collider_list:
# if movement[0] > 0:
# rect.right = tile.left
# collision_types["right"] = True
# elif movement[0] < 0:
# rect.left = tile.right
# collision_types["left"] = True
#
# rect.y += movement[1]
# collider_list = collisions(rect, tile)
# for tile in collider_list:
# if movement[1] > 0:
# rect.bottom = tile.top
# collision_types["bottom"] = True
# elif movement[1] < 0:
# rect.top = tile.bottom
# collision_types["top"] = True
#
# return rect, collision_types
# MAIN MENU
def menu():
py.display.set_caption("Game Menu")
while True:
start()
py.display.update()
clock.tick(60)
# START OPTION (BLINKING TEXT)
def start():
start_font = py.font.Font('Halogen.otf', 50)
bg = py.image.load("GM Treble Quest V2.png")
bg_rect = py.Rect((0, 0), bg.get_size())
screen.blit(bg, bg_rect)
n = 0
while True:
start = start_font.render(("Press Enter To Play"), True, start_colour)
if n % 2 == 0:
screen.blit(start, (450, 625))
clock.tick(50000)
else:
screen.blit(bg, bg_rect)
n += 0.5
py.display.update()
clock.tick(3)
for event in py.event.get():
if event.type == py.QUIT:
exit()
elif event.type == py.KEYDOWN:
if event.key == py.K_RETURN:
play()
# GAME
def play():
py.display.set_caption("Treble Quest")
player_y, player_x = p_rect.bottom, 32
velocity_x, velocity_y = 5, 0
ground = 480
gravity_factor = 0.35
acl_factor = -12
while True:
clock.tick(100)
vertical_acl = gravity_factor
screen.fill(sky_colour)
screen.blit(p_sprite, p_rect)
# TILE MAP
tile_collisions = []
y = 0
for row in map:
x = 0
for tile in row:
if tile == 1:
screen.blit(dirt_block, (x * 32, y * 32))
if tile == 2:
screen.blit(grass_block, (x * 32, y * 32))
if tile != 0:
tile_collisions.append(py.Rect(x * 32, y * 32, 32, 32))
x += 1
y += 1
screen.blit(p_sprite, p_rect)
# player_movement = [0, 0]
# if moving_right == True:
# player_movement[0] += 2
# if moving_left == True:
# player_movement[0] -= 2
# player_movement[1] += player
# MOVEMENT
for event in py.event.get():
if event.type == py.QUIT:
exit()
if event.type == py.KEYDOWN:
if velocity_y == 0 and event.key == py.K_w:
vertical_acl = acl_factor
velocity_y += vertical_acl
player_y += velocity_y
if player_y > ground:
player_y = ground
velocity_y = 0
vertical_acl = 0
p_rect.bottom = round(player_y)
keys = py.key.get_pressed()
player_x += (keys[py.K_d] - keys[py.K_a]) * velocity_x
p_rect.centerx = player_x
py.display.update()
menu()
Your code is almost there.
A simple way to do collisions is to iterate through the bunch of rectangles created when you process the map tiles. Check each rectangle to see if it would intersect the player's position.
When your player needs to move, calculate where the move intends to go (don't actually move the player yet). Then check if the target location will overlap with any of the blocker-tiles. If there is no overlap, then move the player. Otherwise the player stops.
In the example code below, I've implemented a illustrative version of this. While this method is simple, and works (mostly), it suffers from a few issues:
If the velocity is high enough, the player's "next position" may jump right through a blocker tile. This is because the code only tests the destination, not the in-between locations.
Also when a potential collision happens, the player just stops. But at high speeds, the player rectangle may stop a few pixels before the collision tile, rather than against the tile, which would be more what a player would expect.
Really you should test to cover these failures. The second issue is easy to fix by calculating how far the player can move in the desired direction, and only move this lesser amount.
Note that I didn't really understand your movement algorithm. There seems to be a velocity, but no stopping. Given I was just illustrating collisions, I hacked it into some kind of working state that suited what I needed to show.
import pygame as py
#from tile_map import map
map = [ [ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,1,2,1,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0 ],
[ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 ] ]
# Pygame setup
py.init()
clock = py.time.Clock()
# COLOURS
bg_colour = (110, 121, 228)
sky_colour = (190, 220, 255)
start_colour = (225, 225, 225)
screen_width = 1280
screen_height = 720
screen = py.display.set_mode((screen_width, screen_height))
def fakeBitmap( width, height, back_colour, text=None, text_colour=(100,100,100) ):
""" Quick function to replace missing bitmaps """
bitmap = py.Surface( ( width, height ) )
bitmap.fill( back_colour )
if ( text != None and len( text.strip() ) > 0 ):
font = py.font.SysFont( None, 10 )
text_bitmap = font.render( text, True, text_colour, back_colour )
bitmap.blit( text_bitmap, text_bitmap.get_rect(center = bitmap.get_rect().center ) )
return bitmap
#p_sprite = py.image.load("plant_drone.png")
p_sprite = fakeBitmap( 32, 32, ( 128,128,0), "PLAYER" )
p_rect = p_sprite.get_rect()
p_rect.centery = screen_height - 32
#grass_block = py.image.load("grass_block.png")
#dirt_block = py.image.load("dirt_block.png")
grass_block = fakeBitmap( 32, 32, ( 0,200,0 ), "grass_block.png" )
dirt_block = fakeBitmap( 32, 32, ( 102,49, 0 ), "dirt_block.png")
# MAIN MENU
def menu():
py.display.set_caption("Game Menu")
while True:
start()
py.display.update()
clock.tick(60)
# START OPTION (BLINKING TEXT)
def start():
#start_font = py.font.Font('Halogen.otf', 50)
start_font = py.font.SysFont( None, 16 )
#bg = py.image.load("GM Treble Quest V2.png")
bg = fakeBitmap( 1280, 1024, ( 0,0,128 ), "GM Treble Quest V2.png" )
bg_rect = py.Rect((0, 0), bg.get_size())
screen.blit(bg, bg_rect)
n = 0
while True:
start = start_font.render(("Press Enter To Play"), True, start_colour)
if n % 2 == 0:
screen.blit(start, (450, 625))
clock.tick(50000)
else:
screen.blit(bg, bg_rect)
n += 0.5
py.display.update()
clock.tick(3)
for event in py.event.get():
if event.type == py.QUIT:
exit()
elif event.type == py.KEYDOWN:
if event.key == py.K_RETURN:
play()
def playerCanMoveTo( player_next, blockers ):
""" Is a player allowed to move to player_next, or will that mean
colliding with any of the rectangles defined in blockers """
can_move = True
# Simple check, is the destination blocked
for block_rect in blockers:
print( "is Player [%d,%d %d %d] inside Block [%d,%d %d %d]" % ( player_next.x, player_next.y, player_next.width, player_next.height, block_rect.x, block_rect.y, block_rect.width, block_rect.height ) )
if ( block_rect.colliderect( player_next ) ):
can_move = False
break # don't need to check further
return can_move
# GAME
def play():
py.display.set_caption("Treble Quest")
player_y, player_x = p_rect.bottom, 32
velocity_x, velocity_y = 5, 0
ground = 480
gravity_factor = 0.35
acl_factor = -12
# Only need to do this once, moved away from main loop
tile_collisions = []
y = 0
for row in map:
x = 0
for tile in row:
if tile != 0:
tile_collisions.append(py.Rect(x * 32, y * 32, 32, 32))
x += 1
y += 1
while True:
clock.tick(100)
vertical_acl = gravity_factor
screen.fill(sky_colour)
screen.blit(p_sprite, p_rect)
# TILE MAP
y = 0
for row in map:
x = 0
for tile in row:
if tile == 1:
screen.blit(dirt_block, (x * 32, y * 32))
if tile == 2:
screen.blit(grass_block, (x * 32, y * 32))
x += 1
y += 1
screen.blit(p_sprite, p_rect)
# MOVEMENT
for event in py.event.get():
if event.type == py.QUIT:
exit()
elif event.type == py.KEYDOWN:
if velocity_y == 0 and event.key == py.K_w:
vertical_acl = acl_factor
velocity_y += vertical_acl
player_y += velocity_y
if player_y > ground:
player_y = ground
velocity_y = 0
vertical_acl = 0
p_rect.bottom = round(player_y)
keys = py.key.get_pressed()
# Accelerate left/right [A] <-> [D]
if ( keys[py.K_a] ):
velocity_x -= 1
velocity_x = max( -5, velocity_x )
elif ( keys[py.K_d] ):
velocity_x += 1
velocity_x = min( 5, velocity_x )
# Is the player allowed to move?
target_rect = p_rect.copy()
target_rect.centerx += velocity_x
# Check where the player would move to, is allowed
if ( playerCanMoveTo( target_rect, tile_collisions ) ):
player_x += velocity_x
p_rect.centerx = player_x
print( "moving %d" % ( velocity_x ) )
else:
velocity_x = 0
print( "blocked" )
py.display.update()
menu()
I'm currently in the process of creating a Snake game and I want to create a food generator that generates an apple every 10 seconds based on my in-game timer. The timer counts down from 60 to 0(when the game ends) and I want an new apple to generate every 10 seconds, keeping the old one even if it hasn't been eaten. I don't know how to approach this and could use some help. Here is my full program.
Edit: this is a beginner Computer Science school project so the more basic the better.
import random
import pygame
pygame.init()
#---------------------------------------#
#
# window properties #
width = 640 #
height = 480
game_window=pygame.display.set_mode((width,height))
black = ( 0, 0, 0) #
#---------------------------------------#
# snake's properties
outline=0
body_size = 9
head_size = 10
apple_size = 8
speed_x = 8
speed_y = 8
dir_x = 0
dir_y = -speed_y
segx = [int(width/2.)]*3
segy = [height, height + speed_y, height + 2*speed_y]
segments = len(segx)
apple_counter = 0
grid_step = 8
regular_font = pygame.font.SysFont("Andina",18)
blue = [11, 90, 220]
clock = pygame.time.Clock()
time = 60
fps = 25
time = time + 1.0/fps
text = regular_font.render("Time from start: "+str(int(time)), 1, blue)
text2 = regular_font.render("Score: "+str(int(apple_counter)), 1, blue)
apple_x = random.randrange(0, 640, grid_step)
apple_y = random.randrange(0, 480, grid_step)
apple_colour = (255,0,0)
def redraw_game_window():
game_window.fill(black)
for i in range(segments):
segment_colour = (random.randint(1,50),random.randint(100,150),random.randint(1,50))
head_colour = (random.randint(180,220),random.randint(180,220),random.randint(1,15))
apple_colour = (255,0,0)
pygame.draw.circle(game_window, segment_colour, (segx[i], segy[i]), body_size, outline)
pygame.draw.circle(game_window, head_colour, (segx[0], segy[0]), head_size, outline)
game_window.blit(text, (530, 20))
game_window.blit(text2, (30, 20))
pygame.draw.circle(game_window, apple_colour, (apple_x, apple_y), apple_size, outline)
pygame.display.update()
exit_flag = False
print "Use the arrows and the space bar."
print "Hit ESC to end the program."
########################################################## TIMER/CONTROLS
while exit_flag == False:
redraw_game_window()
clock.tick(fps)
time = time - 1.00/fps
text = regular_font.render("Time: "+str(int(time)), 1, blue)
text2 = regular_font.render("Score: "+str(int(apple_counter)), 1, blue)
if time < 0.1:
print "Game Over"
exit_flag = True
pygame.event.get()
keys = pygame.key.get_pressed()
if time ==
if keys[pygame.K_ESCAPE]:
exit_flag = True
if keys[pygame.K_LEFT] and dir_x != speed_x:
dir_x = -speed_x
dir_y = 0
if keys[pygame.K_RIGHT] and dir_x != -speed_x:
dir_x = speed_x
dir_y = 0
if keys[pygame.K_UP] and dir_y != speed_x:
dir_x = 0
dir_y = -speed_y
if keys[pygame.K_DOWN] and dir_y != -speed_x:
dir_x = 0
dir_y = speed_y
############################################################ SNAKE MOVEMENT
for i in range(segments-1,0,-1):
segx[i]=segx[i-1]
segy[i]=segy[i-1]
segx[0] = segx[0] + dir_x
segy[0] = segy[0] + dir_y
############################################################ COLLISION
for i in range(segments-1, 3, -1):
if segments > 3:
if segx[0] == segx[i] and segy[0] == segy[i]:
print "You have collided into yourself, Game Over."
exit_flag = True
############################################################# BORDERS
if segx[0] > 640 or segx[0] < 0:
print "Game Over, you left the borders."
break
if segy[0] > 480 or segy[0] < 0:
print "Game Over, you left the borders."
break
############################################################# APPLE DETECT
for i in range (0 , 13):
if segx[0] == apple_x + i and segy[0] == apple_y + i:
segments = segments + 1
segx.append(segx[-1])
segy.append(segy[-1])
apple_counter = apple_counter + 1
if segx[0] == apple_x - i and segy[0] == apple_y - i:
segments = segments + 1
segx.append(segx[-1])
segy.append(segy[-1])
apple_counter = apple_counter + 1
#############################################################
pygame.quit()
You either
A) use pygame.time.set_timer to call a function every 10 seconds to spawn food, and every 60 seconds to end the round.
or
B) compare get_ticks()
def new_round():
last_apple = pygame.time.get_ticks() + 10*1000
while true:
now = pygame.time.get_ticks()
if now - last_apple >= 1000:
spawn_apple()
last_apple = now
if now - round_start >= 60*1000:
round_end()
Here is a function from my program, it called when a key is pressed. What is supposed to happen is the key is pressed and a corresponding note is played, and a sine wave appears on the screen also. The sound plays fine, so I won't post any more code for the sound, but it is just the visual side of things that don't work, why won't this wave show?
WINDOWWIDTH = 640 # width of the program's window, in pixels
WINDOWHEIGHT = 480 # height in pixels
WIN_CENTERX = int(WINDOWWIDTH / 2) # the midpoint for the width of the window
WIN_CENTERY = int(WINDOWHEIGHT / 2) # the midpoint for the height of the window
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
fontObj = pygame.font.SysFont('freesansbold.ttf', 16)
FPSCLOCK = pygame.time.Clock()
# set up a bunch of constants
BLUE = ( 0, 0, 255)
WHITE = (255, 255, 255)
DARKRED = (128, 0, 0)
DARKBLUE = ( 0, 0, 128)
RED = (255, 0, 0)
GREEN = ( 0, 255, 0)
DARKGREEN = ( 0, 128, 0)
YELLOW = (255, 255, 0)
DARKYELLOW = (128, 128, 0)
BLACK = ( 0, 0, 0)
BGCOLOR = WHITE
FPS = 160 # frames per second to run at
pause = False
# making text Surface and Rect objects for various labels
sinLabelSurf = fontObj.render('sine', True, RED, BGCOLOR)
squareLabelSurf = fontObj.render('square', True, BLUE, BGCOLOR)
sinLabelRect = sinLabelSurf.get_rect()
squareLabelRect = squareLabelSurf.get_rect()
def MakeSineWave(freq=1000):
#### visual part ####
xPos = 0
step = 0
AMPLITUDE = 80 # how many pixels tall the waves with rise/fall.
posRecord = {'sin': [], 'line': []}
yPos = -1 * math.sin(step) * AMPLITUDE
posRecord['sin'].append((int(xPos), int(yPos) + WIN_CENTERY))
# draw the sine ball and label
pygame.draw.circle(DISPLAYSURF, DARKRED, (int(xPos), int(yPos) + WIN_CENTERY), 10)
sinLabelRect.center = (int(xPos), int(yPos) + WIN_CENTERY + 20)
DISPLAYSURF.blit(sinLabelSurf, sinLabelRect)
# draw the waves from the previously recorded ball positions
for x, y in posRecord['sin']:
pygame.draw.circle(DISPLAYSURF, DARKRED, (x, y), 4)
# draw the border
pygame.draw.rect(DISPLAYSURF, BLACK, (0, 0, WINDOWWIDTH, WINDOWHEIGHT), 1)
pygame.display.update()
FPSCLOCK.tick(FPS)
if not pause:
xPos += 0.5
if xPos > WINDOWWIDTH:
xPos = 0
posRecord = {'sin': []}
step = 0
else:
step += 0.008
#### audial part ####
return MakeSound(SineWave(freq))
You run MakeSineWave only once - when button is pressed - but code which draws wave have to be run all the time in all loop. It draws longer wave in every loop.
It seems you ask in another question how to draw all wave and move only red ball - and answer need differen changes in MakeSineWave then changes for this question.
EDIT: It is working code - but there is a mess - it needs less code directly in mainloop but more code in some new functions.
import pygame
from pygame.locals import *
import math
import numpy
#----------------------------------------------------------------------
# functions
#----------------------------------------------------------------------
def SineWave(freq=1000, volume=16000, length=1):
num_steps = length * SAMPLE_RATE
s = []
for n in range(num_steps):
value = int(math.sin(n * freq * (6.28318/SAMPLE_RATE) * length) * volume)
s.append( [value, value] )
return numpy.array(s)
#-------------------
def SquareWave(freq=1000, volume=100000, length=1):
num_steps = length * SAMPLE_RATE
s = []
length_of_plateau = int( SAMPLE_RATE / (2*freq) )
print num_steps, length_of_plateau
counter = 0
state = 1
for n in range(num_steps):
value = state * volume
s.append( [value, value] )
counter += 1
if counter == length_of_plateau:
counter = 0
state *= -1
return numpy.array(s)
#-------------------
def MakeSound(arr):
return pygame.sndarray.make_sound(arr)
#-------------------
def MakeSquareWave(freq=1000):
return MakeSound(SquareWave(freq))
#-------------------
def MakeSineWave(freq=1000):
return MakeSound(SineWave(freq))
#-------------------
def DrawSineWave():
# sine wave
yPos = -1 * math.sin(step) * AMPLITUDE
posRecord['sin'].append((int(xPos), int(yPos) + WIN_CENTERY))
if showSine:
# draw the sine ball and label
pygame.draw.circle(screen, RED, (int(xPos), int(yPos) + WIN_CENTERY), 10)
sinLabelRect.center = (int(xPos), int(yPos) + WIN_CENTERY + 20)
screen.blit(sinLabelSurf, sinLabelRect)
# draw the waves from the previously recorded ball positions
if showSine:
for x, y in posRecord['sin']:
pygame.draw.circle(screen, DARKRED, (x, y), 4)
#-------------------
def DrawSquareWave():
# square wave
posRecord['square'].append((int(xPos), int(yPosSquare) + WIN_CENTERY))
if showSquare:
# draw the square ball and label
pygame.draw.circle(screen, GREEN, (int(xPos), int(yPosSquare) + WIN_CENTERY), 10)
squareLabelRect.center = (int(xPos), int(yPosSquare) + WIN_CENTERY + 20)
screen.blit(squareLabelSurf, squareLabelRect)
# draw the waves from the previously recorded ball positions
if showSquare:
for x, y in posRecord['square']:
pygame.draw.circle(screen, BLUE, (x, y), 4)
#----------------------------------------------------------------------
# constants - (uppercase name)
#----------------------------------------------------------------------
# set up a bunch of constants
WHITE = (255, 255, 255)
DARKRED = (128, 0, 0)
RED = (255, 0, 0)
BLACK = ( 0, 0, 0)
GREEN = ( 0, 255, 0)
BLUE = ( 0, 0, 255)
BGCOLOR = WHITE
WINDOWWIDTH = 1200 # width of the program's window, in pixels
WINDOWHEIGHT = 720 # height in pixels
WIN_CENTERX = int(WINDOWWIDTH / 2) # the midpoint for the width of the window
WIN_CENTERY = int(WINDOWHEIGHT / 2) # the midpoint for the height of the window
FPS = 160 # frames per second to run at
AMPLITUDE = 80 # how many pixels tall the waves with rise/fall.
#-------------------
SAMPLE_RATE = 22050 ## This many array entries == 1 second of sound.
SINE_WAVE_TYPE = 'Sine'
SQUARE_WAVE_TYPE = 'Square'
#----------------------------------------------------------------------
# main program
#----------------------------------------------------------------------
#-------------------
# variables (which don't depend on pygame)
#-------------------
sound_types = {SINE_WAVE_TYPE:SQUARE_WAVE_TYPE, SQUARE_WAVE_TYPE:SINE_WAVE_TYPE}
current_type = SINE_WAVE_TYPE
current_played = { 'z': None, 'c': None }
current_drawn = None
#-------------------
# variables that track visibility modes
showSine = True
showSquare = True
xPos = 0
step = 0 # the current input f
posRecord = {'sin': [], 'square': []} # keeps track of the ball positions for drawing the waves
yPosSquare = AMPLITUDE # starting position
#-------------------
# start program
#-------------------
pygame.init()
screen = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), pygame.HWSURFACE | pygame.DOUBLEBUF)
pygame.display.set_caption('Nibbles!')
# making text Surface and Rect objects for various labels
pygame.display.set_caption('Trig Waves')
fontObj = pygame.font.Font('freesansbold.ttf', 16)
### HERE
squareLabelSurf = fontObj.render('square', True, BLUE, BGCOLOR)
squareLabelRect = squareLabelSurf.get_rect()
sinLabelSurf = fontObj.render('sine', True, RED, BGCOLOR)
sinLabelRect = sinLabelSurf.get_rect()
#-------------------
# mainloop
#-------------------
fps_clock = pygame.time.Clock()
_running = True
while _running:
#-------------------
# events
#-------------------
for event in pygame.event.get():
if event.type == pygame.QUIT:
_running = False
# some keys don't depend on `current_type`
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
_running = False
if event.key == K_RETURN:
current_type = sound_types[current_type] #Toggle
print 'new type:', current_type
# some keys depend on `current_type`
if current_type == SINE_WAVE_TYPE:
if event.type == KEYDOWN:
#lower notes DOWN
if event.key == K_z:
print current_type, 130.81
current_played['z'] = MakeSineWave(130.81)
current_played['z'].play()
current_drawn = DrawSineWave
elif event.key == K_c:
print current_type, 180.81
current_played['c'] = MakeSineWave(180.81)
current_played['c'].play()
current_drawn = DrawSineWave
elif event.type == KEYUP:
#lower notes UP
if event.key == K_z:
current_played['z'].fadeout(350)
current_drawn = None
#sine - reset data
xPos = 0
posRecord['sin'] = []
step = 0
elif event.key == K_c:
current_played['c'].fadeout(350)
current_drawn = None
#sine - reset data
xPos = 0
posRecord['sin'] = []
step = 0
elif current_type == SQUARE_WAVE_TYPE:
if event.type == KEYDOWN:
#lower notes DOWN
if event.key == K_z:
print current_type, 130.81
current_played['z'] = MakeSquareWave(130.81)
current_played['z'].play()
current_drawn = DrawSquareWave
elif event.key == K_c:
print current_type, 180.81
current_played['c'] = MakeSquareWave(180.81)
current_played['c'].play()
current_drawn = DrawSquareWave
elif event.type == KEYUP:
#lower notes UP
if event.key == K_z:
current_played['z'].fadeout(350)
current_drawn = None
# square - reset data
xPos = 0
yPosSquare = AMPLITUDE
posRecord['square'] = []
step = 0
elif event.key == K_c:
current_played['c'].fadeout(350)
current_drawn = None
# square - reset data
xPos = 0
yPosSquare = AMPLITUDE
posRecord['square'] = []
step = 0
#-------------------
# draws
#-------------------
# fill the screen to draw from a blank state
screen.fill(BGCOLOR)
if current_drawn:
current_drawn()
pygame.display.update()
#-------------------
# moves
#-------------------
if current_drawn:
xPos += 1.0 #0.5
if xPos > WINDOWWIDTH:
#sine ### HERE
xPos = 0
posRecord['sin'] = []
step = 0
# square ### HERE
yPosSquare = AMPLITUDE
posRecord['square'] = []
else:
#sine ### HERE
step += 0.008
#step %= 2 * math.pi
# square ### HERE
# jump top and bottom every 100 pixels
if xPos % 100 == 0:
yPosSquare *= -1
# add vertical line
for x in range(-AMPLITUDE, AMPLITUDE):
posRecord['square'].append((int(xPos), int(x) + WIN_CENTERY))
#-------------------
# FPS
#-------------------
fps_clock.tick(FPS)
#-------------------
# end program
#-------------------
pygame.quit()
#----------------------------------------------------------------------
I read the other questions about pygame bogging down and all I could find was limit the FPS, I tried that but it's still bogging down, I've tried everything but in the end I think it's just because my code is inefficient or something, any help would be appreciated.
Here's a video of the performance since i'm sure none of you want to actually download this,
https://www.youtube.com/watch?v=vmZaxb0zQR0
when it first starts it's way slower than it should be, at 0:12 in the video is when it's running at the speed it should be, and at 0:50 it bogs down again.
I tried looking everywhere to see what it could be and i found nothing, frankly just posting this source is embarassing
main.py
import sys, pygame
import random
import time
from resources import _time_
from pygame.locals import *
debug = True
start = time.time()
pygame.mixer.pre_init(44100, -16, 2, 2048)
pygame.init()
size = width, height = 800, 600
speed = [1, 1]
ai_speed = [1, 1]
black = (0, 0, 0)
text_color = (255, 255, 255)
dad_count = 0
#values change to True if appropriate key is pressed
keys = [False, False, False, False]
#set title and screen width and height
pygame.display.set_caption("Dad Explorer v0.02")
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()
screen_rect = screen.get_rect()
#set boundary variables
b_height = height - 26
b_width = width - 25
newdad = False
#load our beautiful sounds
loop = pygame.mixer.music.load("sound/loop.wav")
intro = pygame.mixer.Sound("sound/intro.wav")
alarm1 = pygame.mixer.Sound("sound/klaxon_alert1.wav")
beeps = pygame.mixer.Sound("sound/beeps.wav")
beeps.set_volume(0.1)
defeated = pygame.mixer.Sound("sound/defeatednormal.wav")
defeated.set_volume(0.5)
yell = pygame.mixer.Sound("sound/yell.wav")
yell.set_volume(0.7)
#spawn objects at random coordinates
playerspawn = (random.randrange(b_width), random.randrange(b_height))
enemyspawn = (random.randrange(b_width), random.randrange(b_height))
#player
player = pygame.image.load("images/smug.gif")
player_rect = player.get_rect()
player_rect[:2] = playerspawn
#enemy
enemy = pygame.image.load("images/dad.png")
enemy_rect = enemy.get_rect()
enemy_rect[:2] = enemyspawn
#loop music
pygame.mixer.music.set_volume(0.2)
pygame.mixer.music.play(-1)
#pre set collision to false
collision = False
#fpsCounter = text_font.render("FPS: {}".format(pygame.time.Clock()))
while 1:
respawn = (random.randrange(b_width), random.randrange(b_height))
#display text on the screen
text_font = pygame.font.SysFont("monospace", 14)
player_label = text_font.render("-- Player --", 1, text_color)
player_velocity = text_font.render("Velocity: {}".format(speed), 1, text_color)
player_position = text_font.render("Position: {}".format((player_rect.x, player_rect.y)), 1, text_color)
text_debug = text_font.render("Debug: {}".format(debug), 1, text_color)
time_label = text_font.render("Time wasted: {}".format(_time_()), 1, text_color)
dad_kills = text_font.render("{} Dads defeated".format(dad_count), 1, text_color)
enemy_label = text_font.render("-- Enemy --", 1, text_color)
enemy_velocity = text_font.render("Velocity: {}".format(ai_speed), 1, text_color)
enemy_currentpos = text_font.render("Position: {}".format(enemy_rect[:2]), 1, text_color)
#move the enemy
enemy_rect = enemy_rect.move(ai_speed)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key==K_UP:
keys[0]=True
elif event.key==K_LEFT:
keys[1]=True
elif event.key==K_DOWN:
keys[2]=True
elif event.key==K_RIGHT:
keys[3]=True
if event.key == K_r:
#press r to reset player position
if debug == True:
player_rect[:2] = [100, 100]
speed = [1, 1]
else:
pass
if event.type == pygame.KEYUP:
if event.key==pygame.K_UP:
keys[0]=False
elif event.key==pygame.K_LEFT:
keys[1]=False
elif event.key==pygame.K_DOWN:
keys[2]=False
elif event.key==pygame.K_RIGHT:
keys[3]=False
#set enemy boundaries
if enemy_rect.left < 0 or enemy_rect.right > width:
ai_speed[0] = -ai_speed[0]
if enemy_rect.top < 0 or enemy_rect.bottom > height:
ai_speed[1] = -ai_speed[1]
#set player boundaries
if not screen_rect.contains(player_rect):
if player_rect.right > screen_rect.right:
player_rect.right = screen_rect.right
if player_rect.bottom > screen_rect.bottom:
player_rect.bottom = screen_rect.bottom
if player_rect.left < screen_rect.left:
player_rect.left = screen_rect.left
if player_rect.top < screen_rect.top:
player_rect.top = screen_rect.top
#check for collision
new_collision = enemy_rect.colliderect(player_rect)
if not collision and new_collision:
print "alarm"
dad_count += 1
defeated.play()
newdad = True
if dad_count == 1:
enemy = pygame.image.load("images/maddad.png")
yell.play()
elif collision and not new_collision:
print "done colliding"
beeps.play()
collision = new_collision
screen.fill(black) #display background
screen.blit(player, player_rect) #display player object and player movement
if newdad is True:
enemy_rect[:2] = respawn
ai_speed = [random.randrange(-5, 5), random.randrange(-5, 5)]
screen.blit(enemy, enemy_rect)
newdad = False
else:
screen.blit(enemy, enemy_rect)
#player data
screen.blit(player_label, (5, 10))
screen.blit(player_velocity, (5, 30))
screen.blit(player_position, (5, 50))
screen.blit(text_debug, (5, 70))
screen.blit(time_label, (5, b_height))
screen.blit(dad_kills, (5, b_height - 30))
#AI data
screen.blit(enemy_label, (5, 120))
screen.blit(enemy_velocity, (5, 140))
screen.blit(enemy_currentpos, (5, 160))
if keys[0]:
speed[1] = -2
elif keys[2]:
speed[1] = 2
else:
speed[1] = 0
if keys[1]:
speed[0] = -2
elif keys[3]:
speed[0] = 2
else:
speed[0] = 0
player_rect.move_ip(speed[0], speed[1])
pygame.display.flip()
clock.tick(150)
resources.py (keeps track of time played)
import random
import time, datetime
from random import choice
start_datetime = datetime.datetime.now()
def _time_():
delta = datetime.datetime.now() - start_datetime
days = delta.days
hours = delta.seconds / 3600
minutes = (delta.seconds / 60) % 60
seconds = delta.seconds % 60
runningtime = []
if days > 0:
runningtime.append("{} day{}".format(days, "" if days == 1 else "s"))
if hours > 0:
runningtime.append("{} hour{}".format(hours, "" if hours == 1 else "s"))
if minutes > 0:
runningtime.append("{} minute{}".format(minutes, "" if minutes == 1 else "s"))
if seconds > 0:
runningtime.append("{} second{}".format(seconds, "" if seconds == 1 else "s"))
if len(runningtime) < 2:
time = " and ".join(runningtime)
else:
time = ", ".join(runningtime[:-1]) + ", and " + runningtime[-1]
return time
I think your problem is the font rendering. Font rendering is a very expensive operation, and you should always cache and re-use those rendered surfaces.
Also, you're loading the font every iteration of the main loop. Just load it once.
The game is quite simple, nonetheless I recommend a framerate of 60 (this is a good value based on my experience, YMMV).
An example of a cache could look like:
...
text_font = pygame.font.SysFont("monospace", 14)
cache={}
def get_msg(msg):
if not msg in cache:
cache[msg] = text_font.render(msg, 1 , text_color)
return cache[msg]
while 1:
respawn = (random.randrange(b_width), random.randrange(b_height))
player_label = get_msg("-- Player --")
player_velocity = get_msg("Velocity: {}".format(speed))
player_position = get_msg("Position: {}".format((player_rect.x, player_rect.y)))
text_debug = get_msg("Debug: {}".format(debug))
time_label = get_msg("Time wasted: {}".format(_time_()))
dad_kills = get_msg("{} Dads defeated".format(dad_count))
...
That should boost your framerate and prevent the slowdown.
As a side note, instead of:
#set player boundaries
if not screen_rect.contains(player_rect):
if player_rect.right > screen_rect.right:
player_rect.right = screen_rect.right
if player_rect.bottom > screen_rect.bottom:
player_rect.bottom = screen_rect.bottom
if player_rect.left < screen_rect.left:
player_rect.left = screen_rect.left
if player_rect.top < screen_rect.top:
player_rect.top = screen_rect.top
simply use:
#set player boundaries
player_rect.clamp_ip(screen_rect)
There are some other small issues with your code, but that's out of this question/answer.