A good way to switch to a menu using PyGame - python

I am using PyGame to create a game, and can't figure a clean way to switch between scenes and menus. I don't know if I need to create a scene class for each thing with its own update and render loop or just use while statements and functions (which I don't like that much as it is a little messy for me) It doesn't matter about my code, just the way to switch between a menu and the main game.

There are different strategies and it depends on what functionality you want. One simple way is to have each scene in a function that returns what the next scene should be. Then create a simple if-statement that switches between them in the main game loop.
import pygame
pygame.init()
SCENE_MENU = 0
SCENE_GAME = 1
SCENE_SHOP = 2
def menu(screen):
# Initialize game variables as the player, enemies and such.
fps_cap = 30
options = ['continue', 'save', 'quit']
clock = pygame.time.Clock()
# Game loop.
while True:
# Time management.
clock.tick(fps_cap)
# Handle events.
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_a: # Go to game if you press A.
return SCENE_GAME
elif event.key == pygame.K_b: # Go to shop if you press B.
return SCENE_SHOP
# Update the menu (like buttons, settings, ...).
print('Updating buttons:', *options)
# Draw the shop.
screen.fill((0, 0, 255)) # A green menu.
pygame.display.update()
def game(screen):
# Initialize game variables as the player, enemies and such.
fps_cap = 60
player = 'Ted'
clock = pygame.time.Clock()
# Game loop.
while True:
# Time management.
clock.tick(fps_cap)
# Handle events.
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_a: # Go to menu if you press A.
return SCENE_MENU
elif event.key == pygame.K_b: # Go to shop if you press B.
return SCENE_SHOP
# Update the game.
print(f'Player {player} is playing!')
# Draw your game.
screen.fill((0, 255, 0)) # A blue game.
pygame.display.update()
def shop(screen):
# Initialize game variables as the player, enemies and such.
fps_cap = 30
items = ['sword', 'armor', 'potion']
clock = pygame.time.Clock()
# Game loop.
while True:
# Time management.
clock.tick(fps_cap)
# Handle events.
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_a: # Go to game if you press A.
return SCENE_GAME
elif event.key == pygame.K_b: # Go to shop if you press B.
return SCENE_SHOP
# Update the shop (like buttons, money transfers, ...).
print('Looking at items:', *items)
# Draw the shop.
screen.fill((255, 0, 0)) # A red shop.
pygame.display.update()
def main():
screen = pygame.display.set_mode((100, 100))
scene = SCENE_MENU
while True:
if scene == SCENE_MENU:
scene = menu(screen)
elif scene == SCENE_SHOP:
scene = shop(screen)
elif scene == SCENE_GAME:
scene = game(screen)
main()
This is just a simple example, but should show the principle.

Related

Change transparacy of an image created thanks to a function

I'm displaying two random images in my pygame project that need to change when pressing one "z" or "x" in my keyboard. So I've created a function to do so and the images are correctly showing and changing. My problem is with the if statement in the run function: when the condition are met the sound start playing, but the images don't change their transparency. This is my code:
class Main():
def __init__(self):
pygame.init()
self.window = pygame.display.set_mode((0,0), pygame.FULLSCREEN)
self.clock = pygame.time.Clock()
self.sound_one = pygame.mixer.Sound(os.path.join("soundone.mp3"))
self.sound_two = pygame.mixer.Sound(os.path.join("soundtwo.mp3"))
self.image_list = [] # a list with all the images
def show_image(self, image_one, image_two):
rect_one = image_one.get_rect(midleft=(50, 240))
rect_two = image_two.get_rect(midright=(640- 50, 240))
image_one.set_alpha(0)
image_two.set_alpha(0)
self.window.blit(image_one, rect_one)
self.window.blit(image_two, rect_two)
pygame.display.update()
return image_one, image_two
def run(self):
while True:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
if event.key == pygame.K_z:
self.one, self.two = self.show_image(random.choice(self.image_list), random.choice(self.image_list))
if event.key == pygame.K_x:
self.one, self.two = self.show_image(random.choice(self.image_list), random.choice(self.image_list))
self.window.fill((0,0,0))
if self.value <= 42:
self.one.set_alpha(50)
self.two.set_alpha(0)
self.sound_two.play(loops=0)
elif self.value > 53:
self.sound_one.play(loops=0)
self.one.set_alpha(0)
self.two.set_alpha(50)
elif 42 > self.value > 53:
self.one.set_alpha(0)
self.two.set_alpha(0)
self.clock.tick(30)
if __name__ == "__main__":
main = Main()
main.run()
How can I access the image created with a function to edit it? My code is not giving me back any error. It just doesn't work
You have to redraw the scene in every frame You must draw the images in the application loop and update the display in every frame:
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
run = True
if event.key == pygame.K_z:
self.one, self.two = random.choice(self.image_list), random.choice(self.image_list)
if event.key == pygame.K_x:
self.one, self.two = random.choice(self.image_list), random.choice(self.image_list)
self.window.fill((0,0,0))
rect_one = image_one.get_rect(midleft=(50, 240))
rect_two = image_two.get_rect(midright=(640- 50, 240))
self.window.blit(self.one, rect_one)
self.window.blit(self.two, rect_two)
pygame.display.update()
# [...]
pygame.quit()
sys.exit()
The typical PyGame application loop has to:
limit the frames per second to limit CPU usage with pygame.time.Clock.tick
handle the events by calling either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by calling either pygame.display.update() or pygame.display.flip()

Two keypresses processed in each frame (pygame snake game)

I am making a snake game, and I want to make sure that if you want to turn twice (for example a 180 degree turn), if both keys are pressed in the same frame, the next key press will be processed in the next frame, so that the snake actually turns twice over two frames instead of changing direction twice in the same frame, which could cause it to turn into itself and die. So basically, one turn per frame.
import pygame, sys
from pygame.locals import *
from sys import exit
import keyboard
import random
import time
class body:
def __init__(self,properties=[0,0,20,20],colour=-1):
self.properties=properties
self.colour=colour
self.next=None
class fruit:
def __init__(self,centre=[0,0],size=10):
self.centre=centre
self.size=size
def drawSnake(window,snake):
pygame.draw.rect(window,(0,0,0),snake.properties)
snake.colour=snake.colour*-1
temp=snake.next
while temp:
# Alternate snake colour
if temp.colour==-1:
colour=(0,150,0)
else:
colour=(0,100,0)
temp.colour=temp.colour*-1
pygame.draw.rect(window,colour,temp.properties)
temp=temp.next
return snake
def drawApple(window,snake,size):
numApples=500/(size*2)
bound=numApples-1
apple=fruit([(random.randint(0,bound)*(500/numApples))+size,(random.randint(0,bound)*(500/numApples))+size],size)
#apple=fruit([290,250],10)
pygame.draw.circle(window,"red",apple.centre,apple.size)
return apple
def newGame():
# Draw initial snake and apple
window.fill((255, 255, 255))
snake=body([240,240,20,20],-1)
snake=drawSnake(window,snake)
apple=drawApple(window,snake,10)
return snake,apple
def die(snake):
pygame.draw.rect(window,(180,0,0),[snake.properties[0],snake.properties[1],snake.properties[2],snake.properties[3]])
pygame.display.update()
time.sleep(1)
def getDirection(key,direction):
print(key)
if key == pygame.K_w:
if direction!=[0,20]:
direction=[0,-20]
if key == pygame.K_a:
if direction!=[20,0]:
direction=[-20,0]
if key == pygame.K_s:
if direction!=[0,-20]:
direction=[0,20]
if key == pygame.K_d:
if direction!=[-20,0]:
direction=[20,0]
return direction
def move(snake,apple,direction,length):
# New body piece location
x=snake.properties[0]+direction[0]
y=snake.properties[1]+direction[1]
# If snake crashed, restart
if x<0 or y<0 or x>480 or y>480:
die(snake)
snake,apple=newGame()
return snake,apple,0,False
# Check if collision with body
temp=snake
# Create new body piece with other colour and add to front of list
newBody=body([x,y,20,20],snake.colour*-1)
newBody.next=snake
snake=newBody
# If apple is eaten
if [x,y]==[apple.centre[0]-10,apple.centre[1]-10]:
# Add 1 to length, spawn new apple, do not remove end body piece
length+=1
apple=drawApple(window,snake,10)
while temp:
# Check if apple spawned in body
if temp.properties[0]==apple.centre[0]-10 and temp.properties[1]==apple.centre[1]-10:
apple=drawApple(window,snake,10)
temp=snake.next
temp=temp.next
else:
# Remove end body piece
temp=snake
while temp.next:
# Check if collision with body
if temp.next.properties[0]==x and temp.next.properties[1]==y:
die(snake)
snake,apple=newGame()
return snake,apple,0,False
previous=temp
temp=temp.next
pygame.draw.rect(window,"white",temp.properties)
previous.next=None
return snake,apple,length,True
# Make window
pygame.init()
window=pygame.display.set_mode((500, 500))
snake,apple=newGame()
length=0
delay=0.1
clock=pygame.time.Clock()
pygame.display.update()
prevEvents=[]
while True:
# Wait until a key is pressed to start the game
pressed_keys=pygame.key.get_pressed()
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
# Key is pressed, get direction and start main game loop
direction=getDirection(event.key,[])
game=True
## MAIN GAME LOOP
while game:
# Set FPS
clock.tick(1)
# Get current event queue
events=pygame.event.get()
print("events1: ",events)
print()
# Add current event queue to previous events which were not processed as a key was pressed in the last frame
prevEvents.extend(events)
events=prevEvents
prevEvents=[]
print("events2: ",events)
print()
if events!=None:
i=1
for event in events:
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
# Key was pressed, get new direction of snake
direction=getDirection(event.key,direction)
# Save rest of event queue for next frame to process
prevEvents=events[i:len(events)+1]
print("prevEvents: ",prevEvents)
print()
# Make events nothing to exit this loop, move the snake and get to the next frame
events=[]
i+=1
# Move and draw snake
snake,apple,length,game=move(snake,apple,direction,length)
snake=drawSnake(window,snake)
pygame.display.update()
I am new to pygame, so any help is appreciated.
Store the keys pressed in a queue:
direction_queue = []
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_a:
direction_queue.append('L')
if event.key == pygame.K_d:
direction_queue.append('R')
if event.key == pygame.K_w:
direction_queue.append('U')
if event.key == pygame.K_s:
direction_queue.append('D')
Process the keys one after the other in the application loop:
if direction_queue:
direction = direction_queue[0]
direction_queue.pop(0)
Minimal example:
import pygame
pygame.init()
COLUMNS, ROWS, TIESIZE = 20, 20, 20
window = pygame.display.set_mode((COLUMNS*TIESIZE, ROWS*TIESIZE))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 50)
snake_x, snake_y = 9, 9
key_map = {pygame.K_a: 'L', pygame.K_d: 'R', pygame.K_w: 'U', pygame.K_s: 'D'}
direction_map = {'L': (-1, 0), 'R': (1, 0), 'U': (0, -1), 'D': (0, 1)}
direction_queue = []
direction = 'R'
run = True
while run:
clock.tick(5)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
if event.key in key_map:
direction_queue.append(key_map[event.key])
text_surf = font.render(str(direction_queue), True, 'black')
if direction_queue:
direction = direction_queue[0]
direction_queue.pop(0)
snake_x = (snake_x + direction_map[direction][0]) % COLUMNS
snake_y = (snake_y + direction_map[direction][1]) % ROWS
window.fill("white")
for c in range (1, COLUMNS):
pygame.draw.line(window, "gray", (c*TIESIZE, 0), (c*TIESIZE, window.get_height()))
for r in range (1, ROWS):
pygame.draw.line(window, "gray", (0, r*TIESIZE), (window.get_width(), r*TIESIZE))
rect = pygame.Rect(snake_x*TIESIZE, snake_y*TIESIZE, TIESIZE, TIESIZE)
pygame.draw.rect(window, "red", rect)
window.blit(text_surf, (10, 340))
pygame.display.flip()
pygame.quit()
exit()

Pygame project, screen doesn't update in one function, but it works within two other functions

My pygame project consists of several parts, including global map and towns. I use one Game class to contain necessary objects. All game is shown on one screen and works properly with global map and town (in_city function), it changes the screen and shows necessary information, but when I call another function (buy), it doesn't update screen.
def buy(self):
while self.running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
if event.type == pygame.KEYDOWN:
return
self.screen.fill('BLACK')
pygame.display.update()
pygame.display.flip()
def in_city(self):
while self.running:
stop = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
if event.type == pygame.MOUSEMOTION:
all_sprites.update(pygame.mouse.get_pos())
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
button_right.click(mouse_pos)
button_left.click(mouse_pos)
button_next.click(mouse_pos)
button_prev.click(mouse_pos)
if button_close.on_button(mouse_pos):
stop = True
if event.type == pygame.KEYDOWN:
self.buy()
if stop:
self.player.route = [(0, 0)]
self.player.next_move(self.maps)
self.camera.update(self.player)
return
screen.fill('WHITE')
city.render(screen)
current_text = city.enemies[city.current_enemy]
window.show(screen, current_text.get_text())
print(current_text.goods)
all_sprites.draw(screen)
pygame.display.update()
pygame.display.flip()`
Never run game loops recursively. Use the one application loop to draw the scene depending on states. You should never jump to another application loop in an event. The event should only change a state that is used to draw the scene. e.g.:
game_state = 'A'
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
game_state = 'B'
screen.fill('WHITE')
if game_state == 'A':
# draw scene A
elif game_state == 'B':
# draw scene B
pygame.display.flip()

How to fix pygame menu (space invaders)?

This is my first game so excuse the messy code. I am making a space invaders game and everything i implemented is working fine (sprites, function of the game, music, pause screen, etc). I wanted to implement a really simple menu screen where, if you press C, the game starts. However, the problem with this is that no matter where i call the menu function, there is always a problem, here is the code (im just going to post the menu function and main loop since everything else i believe is not needed).
import pygame
import random
import math
from pygame import mixer
# Start pygame
pygame.init()
# Create Screen
screen = pygame.display.set_mode((1000, 710))
# Background Image
background = pygame.image.load('background.png').convert_alpha()
# Menu Variables
menu_font = pygame.font.Font('freesansbold.ttf', 65)
menuX = 380
menuY = 250
# Menu Function
def game_intro(x, y):
menu = True
while menu:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_c:
menu = False
if event.key == pygame.K_q:
pygame.quit()
quit()
# Menu Text
menu_text = menu_font.render("Space Invaders", True, (255, 255, 255))
screen.blit(menu_text, (x, y))
pygame.display.update()
# Game Loop
running = True
while running:
# RGB - Red, Green, Blue
screen.fill((0, 0, 0))
# Background Image
screen.blit(background, (0, 0))
----game_intro(menuX,menuY)---IF I PUT IT HERE, THE ACTUAL GAME APPEARS FOR ONE SECOND AND IT GOES BACK TO MAIN MENU-----------
# Making the screen stay still
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
--------game_intro(menuX,menuY)--- IF I PUT IT HERE, THE GAME APPEARS ONLY WHEN 'c' IS BEING HELD DOWN-----------------
*more code*
# Updating
pygame.display.update()
if i put it above pygame.display.update(), then the same thing happens: the game appears for one second and then it goes back to the menu screen. I have tried to search everywhere but the videos either are from 2014, and the websites with some similar problem don't explain how to fix it. Please help.
First of all you should throw the while loop out of your function.
def game_intro(x, y):
# Menu Text
menu_text = menu_font.render("Space Invaders", True, (255, 255, 255))
screen.blit(menu_text, (x, y))
the missing code gets put in the mainloop like this
...
# Making the screen stay still
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_c:
menu = False
if event.key == pygame.K_q:
pygame.quit()
...
now in your mainloop you need to decide whether to draw the menu or the game
if menu:
game_intro(x, y)
else:
#CODE THAT DRAWS THE GAME
all together:
import pygame
import random
import math
from pygame import mixer
# Start pygame
pygame.init()
# Create Screen
screen = pygame.display.set_mode((1000, 710))
# Background Image
background = pygame.image.load('background.png').convert_alpha()
# Menu Variables
menu_font = pygame.font.Font('freesansbold.ttf', 65)
menuX = 380
menuY = 250
# Menu Function
def game_intro(x, y):
# Menu Text
menu_text = menu_font.render("Space Invaders", True, (255, 255, 255))
screen.blit(menu_text, (x, y))
# Game Loop
running = True
while running:
# RGB - Red, Green, Blue
screen.fill((0, 0, 0))
# Background Image
screen.blit(background, (0, 0))
# Making the screen stay still
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_c:
menu = False
if event.key == pygame.K_q:
pygame.quit()
if menu:
game_intro(x, y)
else:
# CODE THAT DRAWS THE GAME
# Updating
pygame.display.update()
this should work
note that you need to set menu to True somewhere to get into the menu

multiple clicks registering in pygame

I'm trying to make a board that changes color upon a left mouse click. But when I click it cycles through is_square_clicked() 3 times. That's a problem, I only want it to do it once. As you can probably guess this causes an issue for my program. So how do I limit it to 1 pass through per click? Thanks!
def is_square_clicked(mousepos):
x, y = mousepos
for i in xrange(ROWS):
for j in xrange(COLS):
for k in xrange(3):
if x >= grid[i][j][1] and x <= grid[i][j][1] + BLOCK:
if y >= grid[i][j][2] and y <= grid[i][j][2] + BLOCK:
if grid[i][j][0] == 0:
grid[i][j][0] = 1
elif grid[i][j][0] == 1:
grid[i][j][0] = 0
while __name__ == '__main__':
tickFPS = Clock.tick(fps)
pygame.display.set_caption("Press Esc to quit. FPS: %.2f" % (Clock.get_fps()))
draw_grid()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONUP and event.button == 1:
mousepos = pygame.mouse.get_pos()
is_square_clicked(mousepos)
pygame.display.update()
The reason that it cycles through is because you hold down the mouse long enough for it to check three times. I think if you have it wait between clicks, or you have it check not every time a cycle, it should be fixed.
im going to guess that since the game loops more than once each click it changes more then once
even though a click is very fast the loop is looping faster (depending on the FPS)
here is an example that will change the color of the screen on each click:
"""Very basic. Change the screen color with a mouse click."""
import os,sys #used for sys.exit and os.environ
import pygame #import the pygame module
from random import randint
class Control:
def __init__(self):
self.color = 0
def update(self,Surf):
self.event_loop() #Run the event loop every frame
Surf.fill(self.color) #Make updates to screen every frame
def event_loop(self):
for event in pygame.event.get(): #Check the events on the event queue
if event.type == pygame.MOUSEBUTTONDOWN:
#If the user clicks the screen, change the color.
self.color = [randint(0,255) for i in range(3)]
elif event.type == pygame.QUIT:
pygame.quit();sys.exit()
if __name__ == "__main__":
os.environ['SDL_VIDEO_CENTERED'] = '1' #Center the screen.
pygame.init() #Initialize Pygame
Screen = pygame.display.set_mode((500,500)) #Set the mode of the screen
MyClock = pygame.time.Clock() #Create a clock to restrict framerate
RunIt = Control()
while 1:
RunIt.update(Screen)
pygame.display.update() #Update the screen
MyClock.tick(60) #Restrict framerate
this code will blit a random color background each time you click so you can probably figure out the proper way to do it from the above code
Good Luck!

Categories