I am trying to develop an app using pygame and I want to create a popup window.
I am trying to keep it all in one file, so I am using multiprocessing for this task.
I have two classes, the app and the popup window. Each one is inside a function that creates the windows and start both of their loops.
This is some simplified code:
main.py
from multiprocessing import Process
def popup():
import pygame as pg
pg.init()
class PopUp:
def __init__(self):
self.screen = pg.display.set_mode((300,300))
def update(self):
pg.display.flip()
p = PopUp()
while True:
p.update()
def app():
import pygame as pg
pg.init()
class App:
def __init__(self):
self.screen = pg.display.set_mode((800,600))
self.create_popup()
def create_popup(self):
p = Process(target=popup)
p.start()
p.join()
def update(self):
pg.display.flip()
a = App()
while True:
a.update()
if __name__ == '__main__':
a = Process(target=app)
a.start()
a.join()
However, when I execute this code, only one window appears, with the size of the App, and then is resized to the size of the PopUp, even though it is a different process.
If I do it this other way, then two separate windows will appear with no problem at all.
Why is this happening and how can I get to create the popup window from the app class?
You're duplicating the process after pygame.init() is called. So probably they're sharing window handles, etc.
If the process is copied before the init(), it works OK.
import multiprocessing
import pygame
import os
def handleUpdates( window, colour ):
""" Simple pygame message loop.
Paints the window in a single colour,
handles quit event """
clock = pygame.time.Clock()
exiting = False
while not exiting:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
exiting = True
# draw the window
window.fill( colour )
pygame.display.flip()
# save CPU
clock.tick( 30 )
pygame.quit()
def pyGameWindow( name, window_pos, colour ):
""" Initialise PyGame for a new window """
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % window_pos
pygame.init()
window = pygame.display.set_mode( ( 300, 300 ) )
pygame.display.set_caption( name )
handleUpdates( window, colour )
if __name__ == '__main__':
p1 = multiprocessing.Process(target=pyGameWindow, args=('Window One', ( 100, 100 ), (255, 0, 0 )))
p1.start()
p2 = multiprocessing.Process(target=pyGameWindow, args=('Window Two', ( 500, 100 ), (255, 255, 0 )))
p2.start()
p1.join()
p2.join()
Related
I’m trying to use pyttsx3 in a game where speech is said in response to events within the game, however I can’t figure out how to execute speech commands without pausing the game loop until the audio finishes. Using runAndWait() is no good for this reason, and I can’t get the example in the docs using iterate() to work as I need it to either. See the code below:
import pyttsx3
import pygame as pg
def onStart(name):
print('start')
def onWord(name, location, length):
print('word', name, location, length)
def onEnd(name, completed):
print('end')
def speak(*args, **kwargs):
engine.connect('started-utterance', onStart)
engine.connect('started-word', onWord)
engine.connect('finished-utterance', onEnd)
engine.say(*args, **kwargs)
def main():
engine.startLoop(False)
n = 0
while True:
n += 1
print(n)
if n == 3:
speak('The first utterance.')
elif n == 6:
speak('Another utterance.')
engine.iterate()
clock.tick(1)
if __name__ == '__main__':
pg.init()
engine = pyttsx3.init()
clock = pg.time.Clock()
main()
pg.quit()
sys.exit()
In this example, the first statement is triggered at the right time, but it seems like pyttsx3 stops processing at that point - no further say commands produce any sound, and only the first started-utterance event fires - the started-word and finished-utterance commands never fire. I’ve tried this every way I can think of and still can’t get it to work. Any ideas?
I'm not familiar with pyttx3, but I've created an example that incorporates a pygame event loop. The colour changes with every mouse click event and the name of the colour is spoken.
import random
import pygame
import pyttsx3
WIDTH = 640
HEIGHT = 480
FPS = 120
simple_colors = [
"red",
"orange",
"yellow",
"green",
"blue",
"violet",
"purple",
"black",
"white",
"brown",
]
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
# TTS Setup
engine = pyttsx3.init()
engine.startLoop(False) # have to call iterate in the main loop
# set a random color
current_color = random.choice(simple_colors)
# create a centered rectangle to fill with color
color_rect = pygame.Rect(10, 10, WIDTH - 20, HEIGHT - 20)
frames = 0
paused = False
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONUP:
current_color = random.choice(simple_colors)
engine.stop() # interrupt current speech?
engine.say(f"The next color is {current_color}")
# update game elements
pygame.display.set_caption(
f"Color: {current_color:10} Frame: {frames:10} FPS: {clock.get_fps():.1f}"
)
# draw surface - fill background
window.fill(pygame.color.Color("grey"))
## draw image
window.fill(pygame.color.Color(current_color), color_rect)
# show surface
pygame.display.update()
# call TTS Engine
engine.iterate()
# limit frames
clock.tick(FPS)
frames += 1
pygame.quit()
engine.endLoop()
If you run this you'll see a repetition of the problem you described, the game loop pausing, indicated by the frame count in the title bar. I used a longer sentence rather than just the colour name to exacerbate the issue. Perhaps someone with a better understanding of pyttx3 will understand where we've both gone wrong. In any case we can get around this by running the Text to Speech engine in a different thread.
import random
import threading
import queue
import pygame
import pyttsx3
# Thread for Text to Speech Engine
class TTSThread(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
self.daemon = True
self.start()
def run(self):
tts_engine = pyttsx3.init()
tts_engine.startLoop(False)
t_running = True
while t_running:
if self.queue.empty():
tts_engine.iterate()
else:
data = self.queue.get()
if data == "exit":
t_running = False
else:
tts_engine.say(data)
tts_engine.endLoop()
WIDTH = 640
HEIGHT = 480
FPS = 120
simple_colors = [
"red",
"orange",
"yellow",
"green",
"blue",
"violet",
"purple",
"black",
"white",
"brown",
]
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
# create a queue to send commands from the main thread
q = queue.Queue()
tts_thread = TTSThread(q) # note: thread is auto-starting
# set a random color
current_color = random.choice(simple_colors)
# Initial voice message
q.put(f"The first color is {current_color}")
# create a centered rectangle to fill with color
color_rect = pygame.Rect(10, 10, WIDTH - 20, HEIGHT - 20)
frames = 0
paused = False
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
q.put("exit")
elif event.type == pygame.MOUSEBUTTONUP:
current_color = random.choice(simple_colors)
q.put(f"The next color is {current_color}")
# update game elements
pygame.display.set_caption(
f"Color: {current_color:10} Frame: {frames:10} FPS: {clock.get_fps():.1f}"
)
# draw surface - fill background
window.fill(pygame.color.Color("grey"))
## draw image
window.fill(pygame.color.Color(current_color), color_rect)
# show surface
pygame.display.update()
# limit frames
clock.tick(FPS)
frames += 1
pygame.quit()
In this case, we're using a queue to send the text to the thread which will perform the text-to-speech, so the frame counter does not pause. Unfortunately we can't interrupt the speech, but that might not be a problem for your application.
Let me know if you need any further explanation of what's going on, I've tried to keep it simple.
EDIT: Looking at this issue recorded on github it seems that speech interruption on Windows is not working, the work-around in that discussion is to run the text-to-speech engine in a separate process which is terminated. I think that might incur an unacceptable start-up cost for process and engine re-initialisation every time an interruption is desired. It may work for your usage.
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()
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()
Now below what i did is simply created a sprite as shown..
futher to do something interesting,thought of threading i added a thread which will check the cursor position, hence update global x & y resulting in change of sprite position that is trigered by display.update
I am newbie so i might me wrong in many ways ...so please bear me....
a lot thanks in advance
from pygame import *
from main import *
import threading
import sys
#lets add our sprites
(x,y) = (0, 0)
class threading1(threading.Thread):
def run(self):
global x,y
clockobj1 = pygame.time.Clock()
while (True):
clockobj1.tick(6)
(x,y)=pygame.mouse.get_pos()
display.update()
class sprites(pygame.sprite.Sprite ):
def __init__(self,color= redd, width=120, height=120):
super(sprites,self).__init__()
self.image = pygame.Surface((width,height))
self.image.fill(color)
self.rect=self.image.get_rect()
self.rect.move_ip(x,y)
display.update()
if __name__ == '__main__':
clockobj = pygame.time.Clock()
init()
mainwin = pygame.display.set_mode((720,640))
sprite1 = sprites()
spritegrp = pygame.sprite.Group()
spritegrp.add(sprite1)
spritegrp.update()
mainwin.fill(blue)
spritegrp.draw(mainwin)
threadingobj = threading1()
threadingobj.start()
x = True
while(x):
display.update()
for evt in event.get() :
if (evt.type == QUIT) :
quit()
x=False
clockobj.tick(40)
***BELOW IS MY LATEST CODE----------UPDATED AS PER ANSWERS***PLEASE CHECK
import pygame
from main import *
import threading
import sys
# lets add our sprites
class Sprites(pygame.sprite.Sprite ):
def __init__(self, color=redd, width=120, height=120):
super().__init__()
self.image = pygame.Surface((width, height))
self.image.fill(color)
self.rect = self.image.get_rect()
def updaterect(self):
print("i m in updatereact")
print(pygame.mouse.get_pos())
self.rect.center= pygame.mouse.get_pos()
pygame.display.update()
if __name__ == '__main__':
clockobj = pygame.time.Clock()
pygame.init()
mainwin = pygame.display.set_mode((720,640))
sprite1 = Sprites()
sprite1.updaterect()
spritegrp = pygame.sprite.Group()
spritegrp.add(sprite1)
spritegrp.update()
mainwin.fill(blue)
spritegrp.draw(mainwin)
x = True
while x:
sprite1.updaterect()
pygame.display.update()
for evt in pygame.event.get() :
if evt.type == pygame.QUIT :
quit()
x=False
Threading will only just complicate things. Get rid of it, and take self.rect.move_ip(x,y) and display.update() out of the __init__ for the class. Make a function in the class called update(). This function will move the rect just by saying. self.rect.center = pygame.mouse.get_pos(). Then in the main game loop, put sprite1.update() in there or update it by using the group name instead (as you did with spritegrp.update() ) and then call display.update() there too instead of in the function.
Other things:
super().__init__() doesn't need any args
if and while loops don't need parentheses
you don't need to import main (I may be wrong about this but I don't think so)
from module import * is a bad practice in general, but if you are going to do it (I don't recommend it), you can't put module.method anywhere. You did from pygame import *, but still put pygame.mouse and others like that. Maybe you meant from pygame.locals import * ?
colons don't have a space in between them and the word, i.e. for evt in event.get() : is bad
Indentation should also be the length of a tab, or four spaces. (What IDE are you using? Most do it automatically.)
variables assignments should have spaces: x = False
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())