Making multiple 'game screens' using Pygame - python

How would you be able to create multiple screens using Pygame and events performed by the user?
For example, if I had a menu screen with 2 buttons ('Start' and 'Exit') and the user clicked on 'Start', a new screen would appear in the same window with whatever is next in the game. From that screen, a user could click on another button and move on to another screen/ return to the menu, etc.

Make classes for each one, where each class is a subclass of pygame.Surface, of the same size as the display. Then you could have 3 variable TITLESCREEN, PLAYING, HIGHSCORES and change them on key press. Then you would be able to blit the correct screen to the display.

It is same as with any program/language, you simply trigger current loop.
Consider this example:
FPS = 25
MainLoop = True
Loop1 = True # Start app in Loop1
Loop2 = False # Loop2 is idle
while MainLoop :
clock.tick(FPS)
pygame.event.pump()
keypress = pygame.key.get_pressed()
keypress_dn = tuple(x > y for x,y in zip(keypress, keypress_old))
keypress_old = keypress
if Loop1:
if keypress_dn [pygame.K_ESCAPE] :
Loop1 = False
MainLoop = False
if keypress_dn [pygame.K_2] : # goto Loop2
Loop1 = False
Loop2 = True
...
if Loop2:
if keypress_dn [pygame.K_ESCAPE] :
Loop2 = False
MainLoop = False
if keypress_dn [pygame.K_1] : # goto Loop1
Loop2 = False
Loop1 = True
...
pygame.display.flip( )
pygame.quit( )
So you add your rendering and check keypresses in corresponding loop. Here you press "2" to go to loop2 and press "1" to go to loop 1. "Escape" will quit both loops and main loop.

What I did for this was have a literal main loop that checks to see what "Screen" the player wants to go to. Like if they press the exit button, it returns where to go next. The main loop then runs a the new script for the new screen.

Related

How to get out of a while loop after calling a function?/Can I clear a pygame screen so it stops pygame.event.get()? [duplicate]

This question already has answers here:
Pygame mouse clicking detection
(4 answers)
Closed 6 months ago.
Okay so basically I have some code that checks to see if a "finish" button is clicked.
the way I have it set up is a while loop that has an event for loop to see if the button is clicked by checking the coordinates of the click. when the done button is clicked it should call my next function and the button should disappear (which it does, Its just the last while loop is still listening for my clicks or something). BUT for some reason when I go to the next screen where my results are printing and click in the coordinates of the button (even tho its not there) it will still click it and show up again.
Here is some pseudo-code of what its like:
def findSomething():
#main block of code more than what is here
#I am just showing how I set up the surface
surface = pygame.display.set_mode((400, 300))
background_colour = (18,18,19)
surface.fill(background_colour)
#button code
while running:
pygame.display.flip()
for event in pygame.event.QUIT:
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.MOUSEBUTTONDOWN:
x,y = pygame.mouse.get_pos()
if 125 <= x <= 275 and 190 <= y <= 240:
print("clicked done")
printOptions()
def printOptions():
surface = pygame.display.set_mode((400, 700))
background_colour = (18,18,19)
surface.fill(background_colour)
phraseGuess = buttonFont.render(("Remaining Words: "), True, white)
surface.blit(phraseGuess, (25, 50))
#code for printing stuff out
so Im not sure why its still checking for clicks after i call printOptions(). is there a way to exit the loop while simultaneously calling printOptions() and still having it work? Or is there something I can do at the top of the printOptions function to kind of reset the display and stop looking for clicks in that certain spot?
You could stop this while loop from continuing to execute after printOptions by setting running to False, or by using 'break' inside the while loop body.
With break:
if 125 <= x <=275 and 190 <= y <= 240:
print("clicked done")
printOptions()
break #ends execution of the loop after printOptions() runs
Or by setting running to False:
if 125 <= x <=275 and 190 <= y <= 240:
print("clicked done")
printOptions()
running = False #loop condition will fail next iteration, so it will stop

Python onkey not stopping loop

def startgame():
start.state = False
print start.state
def restart():
end.state = False
start.state = True
print game.state, end.state
s.listen()
s.onkey(startgame, "Return")
s.onkey(restart, 'r')
# This Loop stops when you hit Enter
while start.state:
start.enter()
s.reset()
# I tried repeating it here but it doesn't work
while end.state:
end.enter()
s.reset()
game.state = 'playing'
Both loops are nested in a main while loop but the second one is nested in another while loop (If that helps) so it would look something like this
while True:
while start.state:
start.flash()
s.reset()
while True:
# main game code here
while end.state:
end.flash()
s.reset()
game.state = 'playing'
break
I simply want it to show the end screen and have 'Press r to play again' flash on screen until the player hits r and then it should restart the game and go back to the start screen. The end.state variable wont update during the while loop, but start.state does during its while loop.
Your drawn out (in time) while loops have no place in an event-driven environment like turtle. In general, we need to control everything with events, specifically timer events. Let's rewrite your code as a state machine. (Since I don't have your object classes, entities like start.state become globals like start_state in my example code.)
from turtle import Screen, Turtle
start_state = True
end_state = False
def start_game():
global start_state
start_state = False
def end_game():
global end_state
if not start_state:
end_state = True
def restart_game():
global end_state, start_state
end_state = False
start_state = True
def flash(text):
turtle.clear()
turtle.write(text, align="center", font=('Arial', 24, 'bold'))
color = turtle.pencolor()
turtle.pencolor(turtle.fillcolor())
turtle.fillcolor(color)
def game_states():
if start_state:
flash("Start")
elif end_state:
flash("End")
else:
flash("Playing")
screen.ontimer(game_states, 500)
screen = Screen()
turtle = Turtle()
turtle.hideturtle()
turtle.fillcolor(screen.bgcolor())
screen.onkey(start_game, 'Return')
screen.onkey(restart_game, 'r')
screen.onkey(end_game, 'e')
screen.listen()
game_states()
screen.mainloop()
The state machine rules are:
Start via <Return> -> Playing
Playing via <e> -> End and via <r> -> Start
End via <r> -> Start

Slowing time, but not the program

I'm making a game, where when a player steps outside the screen, the level starts. I wish to show an image of "LEVEL 1" before the game starts, yet the program shows the image too quickly. My framerate is at 60.
I am wondering if there is a way to delay time for about 5 seconds during the screen blitting but after it resumes to it's normal pace. The problem for me with the pygame.time.delay() and wait stuff is that is slows the entire program down.
Is there a easier way?
EDIT______ CODE
#START OF LEVEL 1
if level1:
screen.blit(level1_image,background_position)
pygame.time.delay(500)
level1yay = True
if level1yay:
screen.blit(background,background_position)
#Flip the Display
pygame.display.flip()
clock.tick(time)
#Quit
pygame.quit()
The first image is not displayed and goes directly to the second image
You could set up a timer and keep track of the time. when the timer reaches the delay you want you can do something.
In the example below I set up the timer and I set the level1yay to true only once the timer has reached the delay value.
I added a condition canexit so the game doesn't terminate on the first loop. I assumed that the condition for "if level1: " has been set somewhere else.
mytimer = pygame.time.Clock() #creates timer
time_count = 0 #will be used to keep track of the time
mydelay = 5000 # will delay the main game by 5 seconds
canexit = False
#updates timer
mytimer.tick()
time_count += mytimer.get_time()
#check if mydelay has been reached
if time_count >= mydelay:
level1yay = True
canexit = True
#START OF LEVEL 1
if level1:
screen.blit(level1_image,background_position)
if level1yay:
screen.blit(background,background_position)
#Flip the Display
pygame.display.flip()
clock.tick(time)
#Quit
if canexit == True:
pygame.quit()
EDIT TO INCLUDE ACTIONS PRIOR TO ENTERING LEVEL1:
mytimer = pygame.time.Clock() #creates timer
time_count = 0 #will be used to keep track of the time
mydelay = 5000 # will delay the main game by 5 seconds
canexit = False
start_menu = True # to start with the start menu rather than level1
#Do something before level 1
if start_menu == True:
... do stuff
if start_menu end:
level1 = True
time_count = 0
#START OF LEVEL 1
if level1 == True:
#updates timer
mytimer.tick() # start's ticking the timer
time_count += mytimer.get_time() # add's the time since the last tick to time_count
#check if mydelay has been reached
if time_count >= mydelay:
level1 = False # so that you do not enter level1 again (even though this is redundant here since you will exit the game at the end of the loop... see canexit comment)
level1yay = True # so that you can enter level1yay
canexit = True # so that the game terminates at the end of the game loop
screen.blit(level1_image,background_position)
if level1yay:
screen.blit(background,background_position)
#Flip the Display
pygame.display.flip()
clock.tick(time)
#Quit
if canexit == True:
pygame.quit()
I don't know if this will really work, but you could try displaying the same image of "LEVEL 1" multiple times in a row, so it appears to stay around longer, but the program itself is never actually delayed.
pygame.time.wait(5000) is a delay of 5 seconds, 500 is half a second. You did set the level1 variable to True just before this code fragment, right?

Launch pygames from a pygame menu

Good day, Stack Overflow
I've been working on a project that is a bunch of different minigames, and I have the menu and games all written up, the problem is, how would I go about writing code to launch these games if the right button is pressed, and then make the menu re-launch when the game is finished?
It is actually very simple. Your main loop should have sub loops. Each sub loop is a single game. If you hit button one you run loop 1. If you hit button 2 you run loop 2 etc. something like this..
while mainloop:
select=True
button1 = False
button2 = False
while select:
if button1 == True:
button1 = False
select = False
game1 = True
elif button2 == True:
button2 = False
select = False
game2 = 2
while game1:
# the game loop
if player_die is True:
game1 = False
select = True
while game2:
# the game loop
if player_die is True:
game2 = False
select = True
something like the above should work. You work out the rest. Remember!! Try before asking..

pygame.key.set_repeat for Joystick

I'm building a menu using pygame and I want to make it navigable using a specific gamepad. Ideally I want to be able to press and hold *down" on the D-pad repeatedly, or get something like on a keyboard where the first button press has a delay before repeatedly entering the same character (seemingly).
I'm trying to emulate the pygame.key.set_repeat(...) function for a Joystick. my approach so far has been
pygame.time.set_timer(pygame.USEREVENT, 10)
DELAY_TIME = 0.250 #ms
y_delay = True
while not done:
for event in pygame.event.get():
y_axis = gamepad.get_axis(1)
if y_axis > 0.5: # pushing down
main_menu.move_down()
redraw() #redraw everything on the surface before sleeping
if y_delay:
time.sleep(DELAY_TIME)
y_delay = False #don't delay the next time the y axis is used
elif y_axis < -0.5: #pushing up
# repetitive I know, but I'm still working on it
main_menu.move_up()
redraw()
if y_delay:
time.sleep(DELAY_TIME)
y_delay = False
else:
y_delay = True # delay the next time
my issue is if someone taps up or down faster than DELAY_TIME they are limited to the DELAY_TIME before they can move again. Also if someone releases and depresses the up/down button within the time.sleep interval, python never sees that it was released at all and doesn't allow for a delay.
Maybe there's a way to do this using events or mapping the joystick to keys somehow? qjoypad doesn't cut it for me, and joy2keys is trash. I would need to do the mapping within the python program.
Sleep causes the program to halt execution, so it's not a viable option. You can also do this without using set_timer and events. I did it using a couple of flags and pygame.time's get_ticks.
import pygame
from pygame.locals import *
def main():
pygame.init()
pygame.display.set_mode((480, 360))
gamepad = pygame.joystick.Joystick(0)
gamepad.init()
delay = 1000
neutral = True
pressed = 0
last_update = pygame.time.get_ticks()
while True:
for event in pygame.event.get():
if event.type == QUIT:
return
move = False
if gamepad.get_axis(1) == 0:
neutral = True
pressed = 0
else:
if neutral:
move = True
neutral = False
else:
pressed += pygame.time.get_ticks() - last_update
if pressed > delay:
move = True
pressed -= delay
if move:
print "move"
last_update = pygame.time.get_ticks()
if __name__ == "__main__":
main()
pygame.quit()
When get_axis indicates no motion, the neutral flag is set, and the pressed timer is reset, causing the move flag to remain unset. When the neutral flag is unset, if it's newly set, the move flag is set. If it's not newly set, the pressed timer increases, and move is set only if the pressed timer is greater than delay.

Categories