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.
Related
The following code generates random x,y coordinates, appending the coordinates to a list, then runs a for-loop through the list to blit star images to the screen. The same stars are constantly being redrawn while the code is running, but they're in the same location, so the screen looks static. Here is the code.
import time
import pygame
from random import randint
pygame.init()
display_width = 800
display_height = 600
gameDisplay = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption("My God, it's full of stars!")
med_star_img = pygame.image.load('images/medium_star.png')
tiny_star_img = pygame.image.load('images/tiny_star.png')
black = (0,0,0)
white = (255,255,255)
gray = (50,50,50)
tiny_stars_width = tiny_star_img.get_width()
tiny_stars_height = tiny_star_img.get_height()
med_stars_width = med_star_img.get_width()
med_stars_height = med_star_img.get_height()
tiny_stars_location = []
med_stars_location = []
clock = pygame.time.Clock()
running = True
gameDisplay.fill(gray)
# create random coordinates for stars
for i in range(25):
tiny_stars_location.append(pygame.Rect(randint(1,800),randint(1,600),tiny_stars_width,tiny_stars_height))
for i in range(10):
med_stars_location.append(pygame.Rect(randint(1,800),randint(1,600),med_stars_width,med_stars_height))
def make_med_star(x,y):
gameDisplay.blit(med_star_img, (x,y))
def make_tiny_star(x,y):
gameDisplay.blit(tiny_star_img, (x,y))
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
for star in med_stars_location:
make_med_star(star.x,star.y)
for star in tiny_stars_location:
make_tiny_star(star.x,star.y)
pygame.display.flip()
time.sleep(1)
clock.tick(60)
pygame.quit()
quit()
I'd like the stars to occasionally blink, which I think would be done by randomly removing one or two stars from the stars_location list during the main loop before adding them back when the loop comes back around. The loop probably executes very fast, but I think I can add a delay. Any help here would be greatly appreciated.
You can save a boolean for each star. If the value is True the star is visible.
Example for saving star data: (I wouldn't use the pygame.Rect and instead a simple list)
for i in range(25):
tiny_stars_location.append([randint(1,800),randint(1,600),tiny_stars_width,tiny_stars_height, True])
Also you should not use time.sleep in a pygame program. It does not delay a task. I delays the program. So you have to wait a whole second to close the program or interact with it in any way.
A lazy but working approach would be just to use randomness. You could just get a random number with random.randint(0, n), if it is 0 you set the boolean value of the star to True or False, depending on what state it currently is. You can set the variable n to some number like
n = maximum fps * average seconds before the star is hidden/shown
An other thing you probably just forgot is to clear the window in the loop with gameDisplay.fill(gray).
Your finished code could look like this:
import time
import pygame
from random import randint
pygame.init()
display_width = 800
display_height = 600
gameDisplay = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption("My God, it's full of stars!")
med_star_img = pygame.image.load('images/medium_star.png')
tiny_star_img = pygame.image.load('images/tiny_star.png')
black = (0,0,0)
white = (255,255,255)
gray = (50,50,50)
tiny_stars_width = tiny_star_img.get_width()
tiny_stars_height = tiny_star_img.get_height()
med_stars_width = med_star_img.get_width()
med_stars_height = med_star_img.get_height()
tiny_stars_location = []
med_stars_location = []
clock = pygame.time.Clock()
running = True
gameDisplay.fill(gray)
# create random coordinates for stars
for i in range(25):
tiny_stars_location.append([randint(1,800),randint(1,600),tiny_stars_width,tiny_stars_height, True])
for i in range(10):
med_stars_location.append([randint(1,800),randint(1,600),med_stars_width,med_stars_height, True])
def make_med_star(x,y):
gameDisplay.blit(med_star_img, (x,y))
def make_tiny_star(x,y):
gameDisplay.blit(tiny_star_img, (x,y))
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
for star in med_stars_location:
if star[2]:
make_med_star(star[0], star[1])
if not randint(0, 300): # 300 / 60(fps) = 5 -> every 5 seconds on average
star[2] = not star[2] # inverse
for star in tiny_stars_location:
if star[2]:
make_tiny_star(star[0], star[1])
if not randint(0, 300): # 300 / 60(fps) = 5 -> every 5 seconds on average
star[2] = not star[2] # inverse
pygame.display.flip()
#time.sleep(1) <- never do this in pygame
clock.tick(60)
gameDisplay.fill(gray) # reset display
pygame.quit()
import pygame
import time
# WINDOW SETUP
window = pygame.display.set_mode((900, 500))
pygame.display.set_caption("Pong")
time.sleep(5)
FPS = 60
# RGB VALUE VARIABLES
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
def background(window):
window.fill(WHITE)
pygame.display.update()
# FRAMERATE AND EVENT LOOP INITIALIZATION
def main():
run = True
clock = pygame.time.Clock()
while run:
clock.tick(FPS)
background(window)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if running == False:
pygame.quit()
Above, is my code. I'm trying to make a pong game with pygame. The text editor I am coding with is Visual Studios Code (VSCODE).
Firstly, you need to call the main. You should also make you're code nice and easy to read when possible. I imported pygame as pg which makes typing pygame functions a bit faster as you have less to type. Also, it's better to use global variables to hold variables that won't change through the program, like screen width, height, colours, etc. Then make sure you initialise the module.
As well as that, the only update you have is in background(). You should put the update at the bottom of the loop and remove it from background(). This way everything above will update each loop.
I apologise for not adding you're FPS counter in here as well but I think this should be enough to help you get you're window running with more readable code and a more efficient loop.
import pygame as pg
# Global Variables
screen_width = 900
screen_height = 500
screen = pg.display
window = screen.set_mode((screen_width, screen_height))
colour = 'red'
def main():
# Initialise module
pg.init()
pg.display.set_caption('PONG')
running = True
while running:
# This is a better way of writing your loop
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
# Call background function
background()
# Updates window
# place this inside the loop near the bottom
# so everything is updated at the end of each loop
screen.flip()
def background():
window.fill(colour)
# Remember to call your main function
# This if statement is good practise but not required
# You can just place main() here
if __name__ == '__main__':
main()
So i wrote this code:
# Pygame development 4
# Focus on making code object oriented
# Introduce classes and objects into our code
# Gain access to the pygame library
import pygame
# Size of the screen
SCREEN_TITLE = 'Crossy RPG'
SCREEN_WIDTH = 500
SCREEN_HEIGHT = 500
# Colors according to RGB codes
WHITE_COLOR = (255, 255, 255)
BLACK_COLOR = (0, 0 , 0)
# Clock used to update game events and frames
clock = pygame.time.Clock()
pygame.font.init()
font = pygame.font.SysFont('comicsans', 75)
class Game:
# Typical rate of 60, equivalent to fps
TICK_RATE = 60
# Initializer for the game class to set up the width, height, and title
def __init__(self, title, width, height):
self.title = title
self.width = width
self.height = height
# Create the window of specified size in white to display the game
self.game_screen = pygame.display.set_mode((width, height))
# Set the game window color to white
self.game_screen.fill(WHITE_COLOR)
pygame.display.set_caption(title)
def run_game_loop(self):
is_game_over = False
# Main game loop, used to update all gameplay suh as movement, check, and graphics
# Runs unit is_game_over = True
while not is_game_over:
# A loop to get a;l of the events occuring at any given time
# Events are most often mouse movement, mouse and button clicks, or eit events
for event in pygame.event.get():
# If we have a quite type event(exit out) then exit out of the game loop
if event.type == pygame.QUIT:
is_game_over = True
print(event)
# Update all game graphics
pygame.display.update()
# Tick the clock to update everything within the game
clock.tick(self.TICK_RATE)
pygame.init()
new_game = Game(SCREEN_TITLE, SCREEN_WIDTH, SCREEN_HEIGHT)
new_game.run_game_loop()
pygame.quit()
quit()
Right now I am learning to code with python so im following a course online and since I couldn't get help from the forums of that website I thought I might ask the question here! So I've looked at the code multiple times to check for spelling mistakes but I couldn't find any and anyway i think that it's' not about something missing but it has something to do with pygame.display.update ! Can somebody pls help me?
Without running your code or having a stack trace of where the problem happens, we need to debug the code for you first. So it would be beneficial to add a full stack trace to your questions. I'm pretty confident however that there's two issues that you should work out.
pygame.display.update() should be correctly indented to be in the while loop of your main game event loop. Secondly, the pygame.init() should be run before any other initialization (or at least so I've been taught over the years and every example points to)
Try this out, I think it solves your problem:
# Pygame development 4
# Focus on making code object oriented
# Introduce classes and objects into our code
# Gain access to the pygame library
import pygame
pygame.init()
# Size of the screen
SCREEN_TITLE = 'Crossy RPG'
SCREEN_WIDTH = 500
SCREEN_HEIGHT = 500
# Colors according to RGB codes
WHITE_COLOR = (255, 255, 255)
BLACK_COLOR = (0, 0 , 0)
# Clock used to update game events and frames
clock = pygame.time.Clock()
pygame.font.init()
font = pygame.font.SysFont('comicsans', 75)
class Game:
# Typical rate of 60, equivalent to fps
TICK_RATE = 60
# Initializer for the game class to set up the width, height, and title
def __init__(self, title, width, height):
self.title = title
self.width = width
self.height = height
# Create the window of specified size in white to display the game
self.game_screen = pygame.display.set_mode((width, height))
# Set the game window color to white
self.game_screen.fill(WHITE_COLOR)
pygame.display.set_caption(title)
def run_game_loop(self):
is_game_over = False
# Main game loop, used to update all gameplay suh as movement, check, and graphics
# Runs unit is_game_over = True
while not is_game_over:
# A loop to get a;l of the events occuring at any given time
# Events are most often mouse movement, mouse and button clicks, or eit events
for event in pygame.event.get():
# If we have a quite type event(exit out) then exit out of the game loop
if event.type == pygame.QUIT:
is_game_over = True
print(event)
# Update all game graphics
pygame.display.update()
# Tick the clock to update everything within the game
clock.tick(self.TICK_RATE)
new_game = Game(SCREEN_TITLE, SCREEN_WIDTH, SCREEN_HEIGHT)
new_game.run_game_loop()
pygame.quit()
This also seams to be a school assignment and not a online course (but I might be wrong here), never the less I'll leave this piece of advice if I'm right. I strongly suggest that if you bump into problems, ask your teacher for guidance. As there's always a reason for teachers giving you a challenge/problem to solve. It teaches you the latest techniques you've learned in class, and if you can't solve the problem with the tools that you've been given - you've most likely haven't learned the fundamentals that has been taught out - and you should really re-do some steps.
I can't get python to work when I am using my Raycast function to display images, how do I fix this?
I tried moving some variables and played around with the function, but I can't seem to get it to work.
import pygame
pygame.init()
Screen = "Title"
DB = 0
Width = 800
Height = 600
Frame = pygame.display.set_mode((Width,Height))
pygame.display.set_caption("GAME")
FPS = pygame.time.Clock()
def Raycast(RayXPos, RayYPos):
RaycastThis = pygame.image.load(TTR)
Frame.blit(RaycastThis, (RayXPos, RayYPos))
Loop = True
while Loop == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit()
pygame.display.update()
FPS.tick(60)
while Screen == "Title" and DB == 0:
TTR = 'TitleScreenSmall.png'
Raycast(0, 0)
I expected the frame to display the image (the same size as the window) and it instead crashed, and I can't run the program
Your problem is the infinite loop:
while Screen == "Title" and DB == 0:
TTR = 'TitleScreenSmall.png'
Raycast(0, 0)
Since the loop control variables Screen and DB never change, you have no way to exit the loop. You're stuck here, eternally repeating a function that does very little, and includes no changes to observe.
See this lovely debug blog for help.
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())