Code is running correctly and as I expected but after 5 seconds the display for graphics freezes forever. It shows no error, nothing, just stops responding.
This is a program to simulate a movement of a large group of objects. They have to move randomly while aimless like Brownian motion. To make this I used Pygame to draw any object as a rectangle of random location, to move them I remove everything and draw them again with their location randomly changed by 1.
I am using pygame to show graphics.
Could you please also suggest a better solution for this problem?
import pygame, random, sys, time, numpy
from pygame.locals import *
black = (0,0,0)
white = (255,255,255)
clock = pygame.time.Clock()
class people():
def __init__(self):
screen = pygame.display.get_surface()
self.x = random.randint(0, 800)
self.y = random.randint(0, 600)
def move(self):
self.x += random.randint(-1, 1)
self.y += random.randint(-1, 1)
if self.x < 0:
self.x = 0
if self.x > 800:
self.x = 800
if self.y < 0:
self.y = 0
if self.y > 600:
self.y = 600
def place(x, y):
screen = pygame.display.get_surface()
pygame.draw.rect(screen, black, [x, y, 10, 10])
def main():
# Initialise screen
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption('Test')
peoples = []
chosepopul = 1
while chosepopul == 1:
try:
population = abs(int(input("How many people would you like to have")))
chosepopul = 0
except:
print("Number, please")
for i in range(population):
peoples.append(people())
while True:
screen.fill(white)
for obj in peoples:
people.place(obj.x, obj.y)
people.move(obj)
pygame.display.update()
clock.tick(60)
if __name__ == '__main__':
main()
pygame.quit()
quit()
Everything is working as I expected but freezing inevitably.
UPDATE: If I change input script to constant number, everything is working correctly. So the problem is somehow linked with user interface interactions.
The program stops because the input() blocks the program flow. No further PyGame updates or events are sent & processed. Basically everything comes to a halt, waiting for the user to type.
The best way around this is to write code such that the user does some PyGame on-screen input, rather than in the console. Maybe make a slider or spinner-control to select the number, or plus/minus buttons, whatever.
Alternatively, the program can still use console input in a thread which uses the post() function to send the result to the main PyGame event-loop thread.
I must admit, this answer is of academic interest only, since using the console to input along with a PyGame window is pretty ugly!
Anyway, here is some code. The main python window simply changes colour every 0.5 seconds, while the user can input text in the console with the standard python input() function. The code uses it's own Event enumerated type for posting messages, but these could also just be plain numbers.
This works, as per the OP, because the input() function is called inside a sub-thread of execution. This leaves the main thread free to continually process the PyGame event queue, and paint updates to the window. It's important to only have a single event queue/loop (for reasons beyond the scope of this answer), so the sub-thread "posts" events back to the main thread, rather than acting on the window/events itself.
import threading
import pygame
import enum
# Window size
WINDOW_WIDTH = 200
WINDOW_HEIGHT = 200
DARK = ( 50, 50, 50 )
WHITE = ( 255,255,255 )
RED = ( 255, 55, 55 )
GREEN = ( 5,255, 55 )
BLUE = ( 5, 55,255 )
colour_cycle = [ DARK, WHITE, RED, GREEN, BLUE ]
class UserEvents( enum.IntEnum ):
CLIENT_NUMBER = pygame.USEREVENT + 1
CLIENT_QUIT = pygame.USEREVENT + 2
# ...
class ConsoleInputThread( threading.Thread ):
""" A thread that handles user input on the console.
Waits for user input, then posts messages
to the main PyGame thread for processing """
def __init__( self, prompt ):
threading.Thread.__init__(self)
self.daemon = True # exit with parent
self.done = False
self.prompt = prompt
def stop( self ):
self.done = True
def run( self ):
""" Loops until the user hangs-up """
while ( not self.done ):
# Get some input from the user
user_input = input( self.prompt ).strip()
new_event = None
if ( user_input == 'quit' ):
new_event = pygame.event.Event( UserEvents.CLIENT_QUIT, { } )
else:
try:
user_input = int( user_input )
new_event = pygame.event.Event( UserEvents.CLIENT_NUMBER, { "value":user_input } )
except:
print( "Syntax Error" )
# If we received valid input post it to the main thread
if ( new_event ):
pygame.event.post( new_event )
###
### MAIN
###
# Create the window
pygame.init()
pygame.display.set_caption("Socket Messages")
SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
WINDOW = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), SURFACE )
# Start the console-input thread
input_thread = ConsoleInputThread( "How many people would you like to have: " )
input_thread.start()
# Main paint / update / event loop
done = False
clock = pygame.time.Clock()
colour_index = 0
while ( not done ):
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == UserEvents.CLIENT_QUIT ):
print("\nCLIENT ASKED TO QUIT " )
done = True
elif ( event.type == UserEvents.CLIENT_NUMBER ):
print( "\nVALUE WAS INPUT: %d " % ( event.value, ) )
WINDOW.fill( colour_cycle[colour_index] )
# rotate the colours, just so the screen changes
colour_index += 1
if ( colour_index >= len( colour_cycle ) ):
colour_index = 0
pygame.display.flip()
clock.tick_busy_loop(2) # NOTE: 2 frames per second, no flashy-flashy
input_thread.stop()
pygame.quit()
Related
I was trying to create a function that will load an image and move the image downwards while loading another image and still moving with the same speed as the first. But I don't know how to do it, all I can do for now is to load the next image when the y coordinate of the first is at 500
def aliens():
s = 0
i = "345678"
c = 1
b = int("".join(random.sample(i, c)))
h = 1
while h < 2:
while s <= b:
alien = pygame.image.load("ali.jpg")
alien = pygame.transform.scale(alien, (100, 100))
pos = "0123456789"
l = 3
x = int("".join(random.sample(pos, l)))
y = 0
if x > 500:
h = 3
crash = False
s +=1
while not crash :
scn.blit(alien, (x, y))
y +=2
pygame.display.flip()
if y > 500:
crash = True
The core of your problem is the fact that you are trying to do too many things in the same function. Try to follow the SRP (Single Responsability Principle).
Personnally I would use OOP (Object Oriented Programming) here but I don't know if you are familar with the concept, so first let's stick to the basics.
Here is how I would separate things up, if I wasn't allowed to use OOP:
First I would define two constants:
ALIEN_SIZE = 100
ALIEN_IMG = pg.image.load("green_square.png").convert()
ALIEN_IMG = pg.transform.scale(ALIEN_IMG, (ALIEN_SIZE, ALIEN_SIZE))
Since you are always using the same values, there is no need to load your image all the time. Loading your image just once will have a very positive impact on game's performances.
(Note that I used a diffrent image since you did not provide the image you use but feel free to put the link to your image back)
Then I would define an alien list:
aliens = []
I don't know how you plan to spawn aliens in your project ? For this example I used a custom event with a timer:
# Custom event
SPAWN_ALIEN = pg.USEREVENT
pg.time.set_timer(SPAWN_ALIEN, 2000)
# This function handles all pygame events
def handle_evt():
for evt in pg.event.get():
if evt.type == pg.QUIT:
exit()
if evt.type == SPAWN_ALIEN:
spawn_alien()
# This function spawns an alien
def spawn_alien():
pos = [rd.randint(0, screen.get_width() - ALIEN_SIZE), 0]
aliens.append(pos)
To make aliens move, I use a update() function:
# update aliens position and check if they leave the screen
def update():
for alien in aliens:
move_alien(alien)
if out_of_screen(alien):
kill_alien(alien)
# This function update alien's y position
def move_alien(alien):
alien[1]+=1
# This function remove the given alien from the aliens list
def kill_alien(alien):
aliens.remove(alien)
# This function returns True if the given position is outside screen's boundries
def out_of_screen(pos):
x, y = pos
if x < 0 or x > screen.get_width():
return True
elif y < 0 or y > screen.get_height():
return True
return False
Finally to render things :
def draw():
# Clear screen
screen.fill((0,0,0))
# Draw aliens
for alien in aliens:
screen.blit(ALIEN_IMG, alien)
Here is the full code, so you can see how everything is interacting :
# General imports
import pygame as pg
import random as rd
import sys
# Init
pg.init()
# Vars & consts
screen = pg.display.set_mode((500, 500))
pg.display.set_caption("Example")
FPS = 60
clock = pg.time.Clock()
ALIEN_SIZE = 100
ALIEN_IMG = pg.image.load("green_square.png").convert()
ALIEN_IMG = pg.transform.scale(ALIEN_IMG, (ALIEN_SIZE, ALIEN_SIZE))
# Custom event
SPAWN_ALIEN = pg.USEREVENT
pg.time.set_timer(SPAWN_ALIEN, 2000)
# Main functions
def update():
for alien in aliens:
move_alien(alien)
if out_of_screen(alien):
kill_alien(alien)
def draw():
# Clear screen
screen.fill((0,0,0))
# Draw aliens
for alien in aliens:
screen.blit(ALIEN_IMG, alien)
def handle_evt():
for evt in pg.event.get():
if evt.type == pg.QUIT:
exit()
if evt.type == SPAWN_ALIEN:
spawn_alien()
def exit():
pg.quit()
sys.exit()
# Other functions
def spawn_alien():
pos = [rd.randint(0, screen.get_width() - ALIEN_SIZE), 0]
aliens.append(pos)
def move_alien(alien):
alien[1]+=1
def kill_alien(alien):
aliens.remove(alien)
def out_of_screen(pos):
x, y = pos
if x < 0 or x > screen.get_width():
return True
elif y < 0 or y > screen.get_height():
return True
return False
# Main loop
if __name__ == '__main__':
aliens = []
spawn_alien()
while True:
handle_evt()
update()
draw()
clock.tick(FPS)
pg.display.flip()
And just in case, here is an OOP approach :
# General imports
import pygame as pg
import random as rd
import sys
# Init
pg.init()
# Vars
screen = pg.display.set_mode((500, 500))
pg.display.set_caption("Example")
FPS = 60
clock = pg.time.Clock()
# Class
class Alien():
LIST = []
SIZE = 100
IMG = pg.image.load("green_square.png").convert()
IMG = pg.transform.scale(IMG, (SIZE, SIZE))
def __init__(self, screen_width):
self.img = Alien.IMG
self.pos = (rd.randint(0, screen_width - Alien.SIZE), 0)
# Add alien to the list
Alien.LIST.append(self)
def move(self):
x, y = self.pos
self.pos = (x, y+1)
def has_crashed(self, boundries):
x, y = self.pos
if x < 0 or x > boundries[0]:
return True
elif y < 0 or y > boundries[1]:
return True
return False
def kill(self):
Alien.LIST.remove(self)
# Custom event
SPAWN_ALIEN = pg.USEREVENT
pg.time.set_timer(SPAWN_ALIEN, 2000)
# Main functions
def update():
for alien in Alien.LIST:
alien.move()
if alien.has_crashed(screen.get_size()):
alien.kill()
def draw():
# Clear screen
screen.fill((0,0,0))
# Draw aliens
for alien in Alien.LIST:
screen.blit(alien.img, alien.pos)
def handle_evt():
for evt in pg.event.get():
if evt.type == pg.QUIT:
exit()
if evt.type == SPAWN_ALIEN:
spawn_alien()
def exit():
pg.quit()
sys.exit()
# Other functions
def spawn_alien():
Alien(screen.get_width())
# Main loop
if __name__ == '__main__':
while True:
handle_evt()
update()
draw()
clock.tick(FPS)
pg.display.flip()
I hope all those informations can help you solve your problem. It's hard to know if this, really is the behavior you wanted for your aliens. That's because your variables names are not exactly explicit. One letter variable names are rarely a good idea. But anyway even if you need them to move a little diffrently, you can take inspiration of this code organization.
Have fun !
This question already has answers here:
How to load and play a video in pygame
(3 answers)
Closed 1 year ago.
I am looking to add videos in my game (such as cut scenes or an animated menu screen).
I looked around for a bit and it seems pygame doesn't support video playing anymore, so I was wondering if there was another way to get videos playing while being well integrated in my game, such as having the video playing in the background and having pygame elements (start button etc.) in the foreground.
There's an example somewhere (I was unable to find an original link) of using FFMPEG and another python module to decode the frames via a pipe and read these through into PyGame for displaying. I copied a code snippet (I thought from SO), and forgot about it.
I have now adapted that technique to make a VideoSprite. It uses FFMPEG to decode (and rescale) the video stream, where it's read during the sprites update() to get the next frame.
This is a very rough implementation, but I hope it gives you an idea of what's possible. While it would be nice if PyGame would just play the videos on its own, at least this method hands the video decoding and rescaling off to a subprocess where it hopefully runs on another CPU.
(EDIT: added handler for video ending, and proper FPS control)
import pygame
import subprocess
# Window size
WINDOW_WIDTH = 600
WINDOW_HEIGHT = 400
WINDOW_SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
DARK_BLUE = ( 3, 5, 54)
### initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption("Video Sprite")
class VideoSprite( pygame.sprite.Sprite ):
FFMPEG_BIN = "/usr/bin/ffmpeg" # Full path to ffmpeg executable
def __init__(self, rect, filename, FPS=25 ):
pygame.sprite.Sprite.__init__(self)
command = [ self.FFMPEG_BIN,
'-loglevel', 'quiet',
'-i', filename,
'-f', 'image2pipe',
'-s', '%dx%d' % (rect.width, rect.height),
'-pix_fmt', 'rgb24',
'-vcodec', 'rawvideo', '-' ]
self.bytes_per_frame = rect.width * rect.height * 3
self.proc = subprocess.Popen( command, stdout=subprocess.PIPE, bufsize=self.bytes_per_frame*3 )
self.image = pygame.Surface( ( rect.width, rect.height ), pygame.HWSURFACE )
self.rect = self.image.get_rect()
self.rect.x = rect.x
self.rect.y = rect.y
# Used to maintain frame-rate
self.last_at = 0 # time frame starts to show
self.frame_delay = 1000 / FPS # milliseconds duration to show frame
self.video_stop = False
def update( self ):
if ( not self.video_stop ):
time_now = pygame.time.get_ticks()
if ( time_now > self.last_at + self.frame_delay ): # has the frame shown for long enough
self.last_at = time_now
try:
raw_image = self.proc.stdout.read( self.bytes_per_frame )
self.image = pygame.image.frombuffer(raw_image, (self.rect.width, self.rect.height), 'RGB')
#self.proc.stdout.flush() - doesn't seem to be necessary
except:
# error getting data, end of file? Black Screen it
self.image = pygame.Surface( ( self.rect.width, self.rect.height ), pygame.HWSURFACE )
self.image.fill( ( 0,0,0 ) )
self.video_stop = True
### Create Video Area
video_sprite1 = VideoSprite( pygame.Rect( 100, 100, 320, 240 ), '1975_test_pattern.mp4' )
video_sprite2 = VideoSprite( pygame.Rect( 100, 100, 160, 90 ), '/home/kingsley/Videos/rocket.avi' ) # 640x360
#sprite_group = pygame.sprite.GroupSingle()
sprite_group = pygame.sprite.Group()
sprite_group.add( video_sprite1 )
sprite_group.add( video_sprite2 )
### Main Loop
clock = pygame.time.Clock()
done = False
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == pygame.MOUSEBUTTONUP ):
# On mouse-click
pass
# Movement keys
keys = pygame.key.get_pressed()
if ( keys[pygame.K_UP] ):
video_sprite2.rect.y -= 10
if ( keys[pygame.K_DOWN] ):
video_sprite2.rect.y += 10
if ( keys[pygame.K_LEFT] ):
video_sprite2.rect.x -= 10
if ( keys[pygame.K_RIGHT] ):
video_sprite2.rect.x += 10
# Update the window, but not more than 60fps
sprite_group.update()
window.fill( DARK_BLUE )
sprite_group.draw( window )
pygame.display.flip()
# Clamp FPS
clock.tick_busy_loop(25) # matching my video file
pygame.quit()
Obviously, since it's just the video stream, there's no sound. But for all intents and purposes it's just another sprite. When the video runs out we catch the error and go black.
NOTE: Sometimes when I run this, it breaks the terminal echoing in Linux. I suspect it's something to do with the subprocess and/or pipe. Running reset fixes this. It seems a common problem with subprocesses.
I wrote a code which seems to work very well with moviepy, I was inspired by that of Kingsley.
import moviepy.editor
import moviepy.video.fx.all
import pygame
class VideoSprite(pygame.sprite.Sprite):
def __init__(self, rect, filename):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((rect.width, rect.height), pygame.HWSURFACE)
self.rect = self.image.get_rect()
self.rect.x = rect.x
self.rect.y = rect.y
self.video = moviepy.editor.VideoFileClip(filename).resize((self.rect.width, self.rect.height))
self.video_stop = False
def update(self, time=pygame.time.get_ticks()):
if not self.video_stop:
try:
raw_image = self.video.get_frame(time / 1000) # /1000 for time in s
self.image = pygame.image.frombuffer(raw_image, (self.rect.width, self.rect.height), 'RGB')
except:
self.video_stop = True
The advantage here is that the video is at the right speed
so I'm making a game with pygame and I have some decently large tmx files for the map data. I was wondering if there is A, a way to speed it up or B, if I could simultaneously
run both the mainloop and load the map in. That way I could have a loading screen. I tried using multiprocessing but it said it cannot pickle pygame objects. Thanks!
Your code could load the TMX Map in a thread. This will allow the main loop to wait for a "finished loading" event, and do something else while waiting.
The example code below uses the pytmx library. Typically more code is needed to render the map into an image, but I left this out for the sake of making the answer shorter. Maybe it's useful to also post back more status information events during the load. This would allow the main-loop to display a progress indicator or suchlike.
Obviously this example does not do anything with the window, except fill it blue.
import pygame
import threading
import pytmx
WINDOW_WIDTH = 400
WINDOW_HEIGHT = 400
WINDOW_SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
DARK_BLUE = ( 3, 5, 54 )
CREAM = ( 255, 254, 224 )
MAP_LOAD_DONE = pygame.USEREVENT + 1
MAP_LOAD_ERROR = pygame.USEREVENT + 2
# global to hold map
global_tmx_map = None
# Thread function to load map in the background
class MapLoadThread( threading.Thread ):
def __init__( self, map_name ):
threading.Thread.__init__(self)
self.daemon = True # exit with parent
self.map_name = map_name
def run( self ):
global global_tmx_map
new_event_args = {}
try:
global_tmx_map = pytmx.load_pygame( self.map_name, pixelalpha=True )
# TODO: do more TMX operations in the thread here.
new_event = pygame.event.Event( MAP_LOAD_DONE, new_event_args )
except Exception as e:
new_event_args[ 'exception' ] = e
new_event = pygame.event.Event( MAP_LOAD_ERROR, new_event_args )
# send the load/fail event to the main loop
pygame.event.post( new_event )
# ...
### Window initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption( "TMX Loader Thread Example" )
# Start the Map Loading thread
thread1 = MapLoadThread( "my_map.tmx" )
thread1.start()
### Main Loop
clock = pygame.time.Clock()
done = False
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == MAP_LOAD_DONE ):
print( "MAP LOADED OK" );
elif ( event.type == MAP_LOAD_ERROR ):
print( "MAP LOAD FAILED" );
print( "EXCEPTION WAS " + str( event.exception ) )
# Update the window, but not more than 60 FPS
window.fill( DARK_BLUE )
pygame.display.flip()
# Clamp FPS
clock.tick_busy_loop(60)
pygame.quit()
In a game I am making, the user can take part in arena battles. Upon winning an arena they are meant to be congratulated via text on-screen and, depending on if they've cleared the arena before or not, get given a trophy or get told how many times they've won.
The code outlining what happens if the players' won before and if the player has only won for the first time is virtually similar, but the game freezes when it reaches the part of my if/elif statement outlining the conditions for if the player's already beaten the arena once.
Code for context [text is a separate file used for writing text to my screen, using booleans to determine if it's bold, it's position, etc]:
import pygame, time, text
if battleWon == True and x == len(enemyList)-1: #if the user won the fight and they were on the last enemy
screen.fill(bg_colour) #code for clearing screen
screen.blit(prevwindow,(150,100)) #drawing menus
text.textDisplay("arena champion!",300,140,True,14,"center",screen) #informing they've won
timesCompleted += 1 #incrementing their win count
pygame.display.update() #updating diaplay
time.sleep(2) #keeping message on screen
if arenaComplete == False: #if they havent beaten the arena before
arenaComplete = True #set to true
screen.fill(bg_colour)
screen.blit(prevwindow,(150,100))
text.textDisplay("you got a trophy!",300,140,False,14,"center",screen)# tell them they got a trophy
screen.blit(trophy, (250,160)) #show image of trophy
pygame.display.update()
time.sleep(2)
elif arenaComplete == True: #if already beaten
screen.fill(bg_colour)
screen.blit(prevwindow,(150,100))
text.textDisplay("you've now won "+str(timesCompleted)+" times!",300,140,False,14,"center",screen) #show how many times they've beaten arena
screen.blit(trophy, (250,160)) #trophy...
text.textDisplay("x" + str(timesCompleted),375,160,False,14,"topleft",screen) # ... x(no. of times won)
pygame.display.update()
time.sleep(2)
elif battleWon == False: #if player loses
print(":(") #placeholder
I've read over the code many times and I can't see why this is happening. There doesn't seem to be any errors in the syntax as it would've shown up in the shell. If the code was for some reason skipping out the elif arenaComplete == True part it would simply jump back to my game's main loop without freezing.
EDIT: due to some confusion in the comments I'll clarify that the time.sleep code, as far as I'm aware, does not cause the problem I'm describing. I want the message to be displayed on the screen for the given amount of time, the problem being that the code under the "elif arenaComplete == True" statement isn't making anything display at all. Here's what I said in a comment if it explains better:
What I mean is that the code under the "elif arenaComplete == True"
screen doesn't actually blit anything to the screen, despite the
"pygame.display.update" which comes BEFORE the time.sleep code,
meaning something must be happening beforehand but I am not sure what.
The code in the previous "if arenaComplete == False" section is almost
identical and for this reason I cannot think of any reason as to why
this is happening.
As commentators have pointed out, the game is locking up during the time.sleep(2) calls.
Really in any sort of event-driven program, it's not ideal to lock the whole process with a sleep-cycle. Especially in the loop that handles the events, as this can have other screen-paint consequences.
So how do you code around this? With a State Machine.
The game outlined above seems to have three states: "battle", "results" and "gameover" (presumably). Depending on what state the game is in, the display is different, and the user-input is handled differently.
First, lets define some states for the game, using a python Enumerated Type.
import enum
class GameState( enum.Enum ):
BATTLE = 1
WONGAME = 2
GAMEOVER = 3
STARTMENU = 4
This allows us to reference the current state with a human-readable name:
game_state = GameState.BATTLE
...
if ( game_state == GameState.GAMEOVER ):
...
It's not strictly necessary to use an enumerated type for this, just a string-description would suffice. But using an enumerated type is generally how this sort of thing is handled in a state machine (and is normal for software generally). As a bonus it catches typos with compile errors.
Anyway, so during the window drawing and event processing, the code checks the current game state to see how to paint the screen:
### Main Loop
# Re-draw the screen
if ( game_state == GameState.STARTMENU ):
WINDOW.fill( NAVY_BLUE )
drawMenu( start_menu )
elif ( game_state == GameState.BATTLE ):
SPRITES.update()
WINDOW.fill( INKY_BLACK )
SPRITES.draw( WINDOW )
...
elif ( game_state == GameState.GAMEOVER ):
WINDOW.fill( INKY_BLACK )
# tell user they failed
...
elif ( game_state == GameState.WONGAME ):
WINDOW.fill( bg_colour ) #code for clearing screen
WINDOW.blit( prevwindow, ( 150,100 ) ) #drawing menus
#informing they've won
text.textDisplay( "arena champion!", 300, 140, True, 14, "center", WINDOW )
if ( arenaComplete == False ):
...
# Update the window, but not more than 60fps
pygame.display.flip()
clock.tick_busy_loop( 60 )
Similarly in the event handling, for say a keypress, sometimes a different thing needs to happen depending on the state. For example if we're in GameState.STARTMENU pressing arrow keys might change the menu highlight-selection, but in GateState.BATTLE they move the player. It gets a bit involved, but it's not difficult. It also has the side-effect of enforcing a rigid simplicity on the handling, making for tidier source-code.
# Handle user input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif (event.type == pygame.KEYDOWN):
keys = pygame.key.get_pressed()
if ( game_state == GameState.SOMEMENU ):
# Up/Down changes selecton
if ( keys[pygame.K_UP] ):
my_menu.selectPrevious()
elif ( keys[pygame.K_DOWN] ):
my_menu.selectNext()
elif ( keys[pygame.K_ENTER] ):
# Do item from menu
...
elif ( game_state == GameState.GAMEOVER ):
# any key-press go back to main menu
game_state = GameState.STARTMENU
elif ( game_state == GameState.BATTLE ):
if ( keys[pygame.K_UP] ):
player_sprite.moveUp()
elif ( keys[pygame.K_DOWN] ):
player_sprite.moveDown()
...
This state machine method allows for the program screen-context to switch easily. It means there's no need for time.sleep() delays to show something to the user. It can stay at the "won" screen until a key is pressed.
Here I present a simple example, where pressing any key cycles through the three states. Each state shows a different screen. Alas, I do not currently have time for implementing a state-driven key-press handler too.
import pygame
import random
import time
import enum
# Window size
WINDOW_WIDTH = 400
WINDOW_HEIGHT = 400
# background colours
NAVY_BLUE = ( 28, 20, 186)
INKY_BLACK = ( 0, 0, 0)
CRIMSON = (195, 11, 41)
BAD_SNOW = (255, 252, 216)
class GameState( enum.Enum ):
MOVING = 1
MENU = 2
GAMEOVER = 3
class MovingSprite( pygame.sprite.Sprite ):
""" A bouncing Eyeball, just because """
def __init__( self ):
pygame.sprite.Sprite.__init__( self )
self.image = pygame.image.load("eyeball_32.png").convert_alpha()
self.rect = self.image.get_rect()
self.newPosition()
def newPosition( self ):
# Position to somewhere random
self.rect.center = ( random.randrange( 0, WINDOW_WIDTH ), random.randrange( 0, WINDOW_HEIGHT ) )
def update( self ):
self.newPosition()
### MAIN
pygame.init()
pygame.font.init()
WINDOW = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE )
pygame.display.set_caption("State Machine Example")
# Add some sprites
MOVERS = pygame.sprite.Group() # a group, for a single sprite
for i in range(3):
MOVERS.add( MovingSprite() )
# Font for menus (and whatever)
text_font = pygame.font.Font( None, 60 ) # just a default font
clock = pygame.time.Clock()
done = False
game_state = GameState.MOVING
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == pygame.VIDEORESIZE ):
WINDOW_WIDTH = event.w
WINDOW_HEIGHT = event.h
WINDOW = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE )
elif ( event.type == pygame.KEYDOWN ):
# Move to next state on any keypress, with loop
if ( game_state == GameState.MOVING ):
game_state = GameState.MENU
elif ( game_state == GameState.MENU ):
game_state = GameState.GAMEOVER
elif ( game_state == GameState.GAMEOVER ):
game_state = GameState.MOVING
# Repaint the screen, depending on the state
if ( game_state == GameState.MOVING ):
MOVERS.update() # re-position the flower-pot
WINDOW.fill( INKY_BLACK )
MOVERS.draw( WINDOW ) # draw the flower-pot
elif ( game_state == GameState.MENU ):
WINDOW.fill( NAVY_BLUE )
menu_items = [ "1. Something", "2. Something Else", "3. Third Choice" ]
total_height = 0 # use to tally menu height (for centering)
max_width = 0
# Make the menu images, work out the size it needs
for i in range( len( menu_items ) ):
# convert text into image, in-place
menu_items[i] = text_font.render( menu_items[i], True, BAD_SNOW )
max_width = max( max_width, menu_items[i].get_width() )
total_height = total_height + menu_items[i].get_height()
# Now draw to screen
cursor_x = ( WINDOW_WIDTH - max_width ) // 2 # centred on largest menu item
cursor_y = ( WINDOW_HEIGHT - total_height ) // 2
for i in range( len( menu_items ) ):
WINDOW.blit( menu_items[i], ( cursor_x, cursor_y ) )
cursor_y += 5 + menu_items[i].get_height() # move down height, plus a bit
elif ( game_state == GameState.GAMEOVER ):
WINDOW.fill( CRIMSON )
# Write "Game Over" text to middle of screen (image could be pre-generated)
game_over_text = text_font.render( "* Game Over *", True, BAD_SNOW )
centred_x = ( WINDOW_WIDTH - game_over_text.get_width() ) // 2
centred_y = ( WINDOW_HEIGHT - game_over_text.get_height() ) // 2
WINDOW.blit( game_over_text, ( centred_x, centred_y ) )
pygame.display.flip()
# Update the window, but not more than 60fps
clock.tick_busy_loop( 60 )
pygame.quit()
eyeball_32.png
couple of days ago, I made a code that plays a video in pygame window.
Code works just fine, just as I originally intended. However, when I print debug statement to see its fps, it's somewhere around 30fps. If I were to increase fps, what should I do?
Here is the code I used.
import sys
from color import *
import pyglet
pygame.init()
running = True
gameDisplay= pygame.display.set_mode((800,600))
window = pyglet.window.Window(visible=False)
background_vid = pyglet.media.Player()
background_vid.queue(pyglet.media.load(".\\music_folder\\music_vid/servant_of_evil_converted.mp4"))
background_vid.play()
def hellow():
print "hellow bloody world"
def on_draw():
#We have to convert the Pyglet media player's image to a Pygame surface
rawimage = background_vid.get_texture().get_image_data()
print "rawimage "+str(rawimage)
pixels = rawimage.get_data('RGBA', rawimage.width *8)
video = pygame.image.frombuffer(pixels, (rawimage.width*2,rawimage.height), 'RGBA')
#Blit the image to the screen
gameDisplay.blit(video, (0, 0))
circle_x=300
while True:
pyglet.clock.tick()
on_draw()
print "fps: "+str(pyglet.clock.get_fps())
for event in pygame.event.get():
if(event.type == pygame.QUIT):
sys.exit()
pygame.quit()
pygame.draw.rect(gameDisplay, red, (circle_x, 300, 300, 300), 5)
circle_x+=1
pygame.display.update()
So what #pydude said is not completely wrong.
However, in order to actually messure FPS I'd put a custom counter in the on_draw function, that will give better accuracy.
Further more, the only real problem with your code is that you don't insert vsync=False into your Window() decorator.
I've reworked your code to make it a little bit more modular, I've also removed potential bottle-necks and added my own custom FPS counter (via GL and not console), here - have a go and see if it works better for you.
(Note: Pressing Escape will exit the application)
import sys
from color import *
import pyglet
from pyglet.gl import *
from time import time # Used for FPS calc
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self):
super(main, self).__init__(800, 800, fullscreen = False, vsync = True)
self.running = True
self.background_vid = pyglet.media.Player()
self.background_vid.queue(pyglet.media.load(".\\music_folder\\music_vid/servant_of_evil_converted.mp4"))
self.background_vid.play()
self.fps_counter = 0
self.last_fps = time()
self.fps_text = pyglet.text.Label(str(self.fps_counter), font_size=12, x=10, y=10)
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.running = False
def on_draw(self):
self.render()
#We have to convert the Pyglet media player's image to a Pygame surface
def render(self):
self.clear()
rawimage = background_vid.get_texture().get_image_data()
pixels = rawimage.get_data('RGBA', rawimage.width *8)
video = pygame.image.frombuffer(pixels, (rawimage.width*2,rawimage.height), 'RGBA')
#Blit the image to the screen
self.blit(video, (0, 0))
# And flip the GL buffer
self.fps_counter += 1
if time() - self.last_fps > 1:
self.fps_text.text = str(self.fps_counter)
self.fps_counter = 0
self.last_fps = time()
self.fps_text.draw()
self.flip()
def run(self):
while self.running is True:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if event and event.type == pygame.QUIT:
self.running = False
x = main()
x.run()
Try toggling vsync = True to vsync = False and watch the difference in the FPS counter.
With python, printing is very slow. Try just printing every once and a while.
Example:(requires import random):
if random.random()>0.09:print "fps: "+str(pyglet.clock.get_fps())