Usually I would make a function or an if statement, like this:
def home_screen():
# code
if condition:
game()
def game():
# code
home_screen()
or something like:
game = 1
if game == 1:
# code for home screen
if condition:
game = 2
if game == 2:
# code for game
if game == 3:
# so on
The latter needs a global variable with a class, which is fine for me. However in Ursina, none of these work, either the update function stops on the former, or the color.red, color.blue, etc. stops working out of nowhere, or the second if statement just doesn't run. Does anyone have an alternative? I'm thinking of just making a home_screen.py file entirely but that won't do much good, and I'm not sure how that can be implemented anyway.
Edit: while loops also don't seem to work
Making a functional game menu is actually not that simple.
You could make a function that loads all the game models from a level, a function that shows a menu, and a final one that shows a loading screen.
Load a level :
def loadLevel():
global loading_screen
ground = Entity(model='quad', scale=10, y=-2, collider='box') # dummy entities
player = FirstPersonController()
player_model = Entity(model='player', parent=player)
building = Entity(model='building', collider='box')
destroy(loading_screen) # delete the loading screen when finished
Show the menu :
def showMenu():
play = Button('Play', on_click=showLoadingScreen) # A play button that show the loading menu when clicked
Show the loading screen :
from direct.stdpy import thread # we need threading to load entities in the background (this is specific to ursina, standard threading wouldn't work)
def showLoadingScreen():
global screen
screen = Entity(model='quad', texture='loading_image')
thread.start_new_thread(function=loadLevel, args='') # load entities in the background
Rest of the file :
from ursina import *
if __name__ == '__main__':
app = Ursina()
screen = None # for global statement
showMenu()
app.run()
Edit : a basic example is available here.
Related
I want to load a gif in ursina engine using the Animation class, it works flawlessly but when I want to reassign the Animation to another gif the memory stacks up like if I opened another gif. I tried to delete the instance and then recreate the gif but the memory keeps stacking up, also I tried to force a garbage collection but it doesn't work either.
What can I do?
The code is something like this:
from ursina import *
from ursina.prefabs.animation import Animation
import gc
app = Ursina()
FC={"FC":80}
monitor = Animation("monitor\gifs\monitor-%d.gif" % (FC["FC"]),loop=False)
monitor.enable()
def recreate_monitor():
global monitor
monitor = Animation("monitor\gifs\monitor-%d.gif" % (FC["FC"]),loop=False)
monitor.enabled = True
def refresh_monitor():
global monitor
monitor.sequence.start()
def update():
global monitor
if not monitor:
create_monitor()
else:
if monitor.sequence.finished:
if FC["FC"] != 80:
monitor.remove_node()
del monitor
print("DATA RECOLLECTED: ", gc.collect())
create_monitor()
else:
refresh_monitor()
def input(key):
if key == "space":
FC["FC"] = 100
app.run()
The context
I am creating my own version of the board game Battleship using PyQt. A PyQt main window contains both own and enemy boards. Boards are made up of clickable tiles that players 'fire at'. The implementation supports HUMANvsAI and AIvsAI games. I would like to test the strength of my AI algorithms through simulation. For example, I would like to run in a loop 1,000 AIvsAI games and get stats on % victory, average accuracy, etc.
The main issue
I am struggling to run the PyQt app multiple times to gather game data, e.g. in a for loop. Specifically, I cannot find a way to run the application, exit it, and re-run it again. Conceptually speaking I am looking for something like this:
# conceptual snippet
for i in range(n):
app = QApplication([])
window = MainWindow(b_size, boat_dict, players)
app.exec_()
With the call to exit the app somewhere else and called each time the game is over:
# conceptual snippet
if is_game_over():
sys.exit(app.exec_())
But this simple solution breaks the for loop. Any feedback would be welcome on how to run and exit a PyQt application multiple times, either sequentially (e.g. for loop approach) or in parallel threads.
You should not use sys.exit() since that instruction serves to terminate the execution of the program, if you want to terminate the Qt application you must use QCoreApplication::quit() (or QCoreApplication::exit(0)). In addition, another improvement would be to use multiprocessing:
import random
from multiprocessing import Pool
from PyQt5 import QtCore, QtWidgets
def create_app(i):
app = QtWidgets.QApplication([])
w = QtWidgets.QMainWindow()
w.setWindowTitle("Game-{}".format(i))
w.show()
# emulate end-game
def end_game():
QtCore.QCoreApplication.quit()
timeout = random.randint(1000, 2000) # 1000-2000 milliseconds
QtCore.QTimer.singleShot(timeout, end_game)
app.exec_()
# emulate results
o = {"victory": random.randint(0, 101), "average": random.randint(0, 101)}
return o
def main():
results = []
pool = Pool(processes=8)
for i in range(1000):
r = pool.apply_async(create_app, args=(i,))
results.append(r)
pool.close()
pool.join()
print([result.get() for result in results])
if __name__ == "__main__":
main()
I've to tell you, your question is dangerously near the level of a "too-broad" flag. The real problem is: how do you want to keep track of the gathered data, and what do you want to do with that data? This question may have a lot of answers.
As you already found out, you can't use sys.exit, but you could gather your data in different ways.
If you're going to run your application within a controlled environment, a possible solution is to serialize your gathered data while controlling the whole "gathering" process from the application
Semi-pseudo-code:
from PyQt5 import QtCore, QtWidgets
import json
class BattleShipWindow(QtWidgets.QMainWindow):
restart = QtCore.pyqtSignal()
# ...
def storeData(self, data):
# get the app data location
appDir = QtWidgets.QStandardPath.standardLocations(
QtCore.QStandardaPaths.AppDataLocation)[0]
# if it doesn't exists, create it
if not QtCore.QFile.exists(appDir):
QtCore.QDir().mkpath(appDir)
now = QtCore.QDateTime.currentDateTime()
fileName = str(now.toMSecsSinceEpoch() // 1000)
# write down the data
with open(os.path.join(appDir, fileName), 'w') as df:
df.write(json.dumps(data))
def getGameData(self):
# gather your own data here, this is just an example
return {'gameData': [1, 2, 3]}
def closeEvent(self, event):
if QtWidgets.QMessageBox.question(
self, 'Play again?',
'Play another game?',
QtWidgets.QMessageBox.Yes|QtWidgets.QMessageBox.No
) == QtWidgets.QMessageBox.Yes:
self.storeData(self.getGameData())
self.restart.emit()
class BattleShipApp(QtWidgets.QApplication):
def restart(self):
self.currentGame = BattleShipWindow()
self.currentGame.restart.connect(self.restart)
self.currentGame.show()
def exec_(self):
self.currentGame = BattleShipWindow()
self.currentGame.restart.connect(self.restart)
self.currentGame.show()
super(BattleShipApp, self).exec_()
if __name__ == '__main__':
import sys
app = BattleShipApp(sys.argv)
sys.exit(app.exec_())
Note: this is a [semi]pseudo code, I obviously didn't test it. Its purpose is to show how it could behave, so don't expect it to work as it is.
Version of pyglet - 1.4.2. Python - 3.6.6Ubuntu - 18.04
Code example:
import pyglet
import time
pyglet.options['audio'] = ('openal', 'pulse', 'directsound', 'silent')
source = pyglet.media.StaticSource(pyglet.media.load('explosion.wav'))
def my_playlist():
while True:
print(time.time())
print(1)
yield source
player = pyglet.media.Player()
player.queue(my_playlist())
player.play()
pyglet.app.run()
Code was writed based on documentation:
Logs in console:
1566296930.8165386 # played once
1
1566296931.529639 # won't play
1
1566296931.5301056 # won't play and etc.
1
1566296931.5304687
1
1566296931.5309348
1
Expected result:
Audio should play in loop with sounds which is returned from generator.
Current result:
Audio is played once.
Question:
What I did wrong here and how to achive expected result?
Not sure if you're trying to accomplish something more, but if all you need from your loop is to loop sound, you shouldn't actually use a loop of any kind. Instead, use the designated EOS_LOOP flag/trigger.
import pyglet
import time
pyglet.options['audio'] = ('openal', 'pulse', 'directsound', 'silent')
source = pyglet.media.StaticSource(pyglet.media.load('explosion.wav'))
player = pyglet.media.Player()
player.queue(source)
player.EOS_LOOP = 'loop'
player.play()
pyglet.app.run()
And since it's deprecated, you should move away to using the SourceGroup with the loop flag set.
I'm trying to draw an image with PyGame, and I copied this code from Adafruit. It's strange, seems nothing displays on the screen until I Ctrl-C, at which time it shows the image. Then the image stays on the screen until I press Ctrl-C again. I then get the following message:
Traceback (most recent call last):
File "RaspiDisplay.py", line 60, in
time.sleep(10)
KeyboardInterrupt
What's going on? By the way, I'm running this via ssh on a raspberry pi, with Display set to 0 (my TV) If I put a print statement in init, that also doesn't print until I press Ctrl-C.
import os
import pygame
import time
import random
class pyscope :
screen = None;
def __init__(self):
"Ininitializes a new pygame screen using the framebuffer"
# Based on "Python GUI in Linux frame buffer"
# http://www.karoltomala.com/blog/?p=679
disp_no = os.getenv("DISPLAY")
if disp_no:
print "I'm running under X display = {0}".format(disp_no)
# Check which frame buffer drivers are available
# Start with fbcon since directfb hangs with composite output
drivers = ['fbcon', 'directfb', 'svgalib']
found = False
for driver in drivers:
# Make sure that SDL_VIDEODRIVER is set
if not os.getenv('SDL_VIDEODRIVER'):
os.putenv('SDL_VIDEODRIVER', driver)
try:
pygame.display.init()
except pygame.error:
print 'Driver: {0} failed.'.format(driver)
continue
found = True
break
if not found:
raise Exception('No suitable video driver found!')
size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
print "Framebuffer size: %d x %d" % (size[0], size[1])
self.screen = pygame.display.set_mode(size, pygame.FULLSCREEN)
# Clear the screen to start
self.screen.fill((0, 0, 0))
# Initialise font support
pygame.font.init()
# Render the screen
pygame.display.update()
def __del__(self):
"Destructor to make sure pygame shuts down, etc."
def test(self):
# Fill the screen with red (255, 0, 0)
red = (255, 0, 0)
self.screen.fill(red)
# Update the display
pygame.display.update()
# Create an instance of the PyScope class
scope = pyscope()
scope.test()
time.sleep(10)
You're not running an event loop anywhere. Instead, you're just initializing everything, and then going to sleep for 10 seconds. During that 10 seconds, your code is doing nothing, because that's what you told it to do. That means no updating the screen, responding to mouse clicks, or anything else.
There are a few different ways to drive pygame, but the simplest is something like this:
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
# any other event handling you need
# all the idle-time stuff you want to do each frame
# usually ending with pygame.display.update() or .flip()
See the tutorial for more information.
As a side note, your initialization code has a bunch of problems. You iterate through three drivers, but you only set SDL_VIDEODRIVER once, so you're just trying 'fbcon' three times in a row. Also, you've got code to detect the X display, but you don't allow pygame/SDL to use X, so… whatever you were trying to do there, you're not doing it. Finally, you don't need a found flag in Python for loops; just use an else clause.
I have 4 video files (different scenes of a movie).
There's a starting scene that will be played when I run the player.
And before that scene ends, let's say the video player reads an int value (0-100) from external file (all happens at runtime), and depending on that int value, it has to determine which scene to play next.
pseudo example:
if (x > 0 && x < 30)
videoSource = scene2
else if (x >= 30 && x < 60)
videoSource = scene3
else if (x >= 60 && x <= 100)
videoSource = scene 4
How can I make it change video sources at runtime, depending on that variable?
I don't care about the format of the video file, (Avi, mp4...) whatever works will be fine.
I don't know how to approach this problem. I've searched for something that has the potential to accomplish this, like pyglet or GStreamer, but I didn't find a clear solution.
EDIT: I have the basic player and video player with pyglet, and I was able to play the video without depending on a variable using this code:
import pyglet
vidPath="sample.mpg"
window = pyglet.window.Window()
player = pyglet.media.Player()
source = pyglet.media.StreamingSource()
MediaLoad = pyglet.media.load(vidPath)
player.queue(MediaLoad)
player.play()
#window.event
def on_draw():
window.clear()
if player.source and player.source.video_format:
player.get_texture().blit(0,0)
pyglet.app.run()
How would I go about this? Guidance in the right direction and/or some sample code would be highly appreciated.
Thanks in advance.
Answer revised based on comments
If your goal is to constantly read a file that is receiving writes from the output of another process, you have a couple aspects that need to be solved...
You either need to read a file periodically that is constantly being overwritten, or you need to tail the output of a file that is being appended to with new values.
Your script currently blocks when you start the pyglet event loop, so this file check will have to be in a different thread, and then you would have to communicate the update event.
I can't fully comment on step 2 because I have never used pyglet and I am not familiar with how it uses events or signals. But I can at least suggest half of it with a thread.
Here is a super basic example of using a thread to read a file and report when a line is found:
import time
from threading import Thread
class Monitor(object):
def __init__(self):
self._stop = False
def run(self, inputFile, secs=3):
self._stop = False
with open(inputFile) as monitor:
while True:
line = monitor.readline().strip()
if line.isdigit():
# this is where you would notify somehow
print int(line)
time.sleep(secs)
if self._stop:
return
def stop(self):
self._stop = True
if __name__ == "__main__":
inputFile = "write.txt"
monitor = Monitor()
monitorThread = Thread(target=monitor.run, args=(inputFile, 1))
monitorThread.start()
try:
while True:
time.sleep(.25)
except:
monitor.stop()
The sleep loop at the end of the code is just a way to emulate your event loop and block.
Here is a test to show how it would work. First I open a python shell and open a new file:
>>> f = open("write.txt", 'w+', 10)
Then you can start this script. And back in the shell you can start writing lines:
>>> f.write('50\n'); f.flush()
In your script terminal you will see it read and print the lines.
The other way would be if your process that is writing to this file is constantly overwriting it, you would instead just reread the file by setting monitor.seek(0) and calling readline().
Again this is a really simple example to get you started. There are more advanced ways of solving this I am sure. The next step would be to figure out how you can signal the pyglet event loop to call a method that will change your video source.
Update
You should review this section of the pyglet docs on how to create your own event dispatcher: http://pyglet.org/doc/programming_guide/creating_your_own_event_dispatcher.html
Again, without much knowledge of pyglet, here is what it might look like:
class VideoNotifier(pyglet.event.EventDispatcher):
def updateIndex(self, value):
self.dispatch_events('on_update_index', value)
VideoNotifier.register_event('on_update_index')
videoNotifier = VideoNotifier()
#videoNotifier.event
def on_update_index(newIndex):
# thread has notified of an update
# Change the video here
pass
And for your thread class, you would pass in the dispatcher instance, and use the updateIndex() event to notify:
class Monitor(object):
def __init__(self, dispatcher):
self._stop = False
self._dispatcher = dispatcher
def run(self, inputFile, secs=3):
...
...
# should notify video of new value
line = int(line_from_file)
self._dispatcher.updateIndex(line)
...
...
Hope that gets you started!