This is my brief summary of Pyglet: it's a powerful, efficient, high-tech cage. But once you're in there, once you call pyglet.app.run()
, it's all over - you can't go back. Thus, there is this huge initialization process that you must split up into as many separate pieces as you can or you'll get lost.
I tried to split it into scenes by writing a scene manager. It works, but looks horrible and I'm worried about its efficiency.
""" Pyglet scene manager. """
import pyglet
class Scene_Manager(object):
""" Runs and switches between different scenes. """
def on_step(self, dt):
""" Logic function executed every frame. """
if not self.running:
self.window.close()
pyglet.app.exit()
else:
self.scenes[self.current].on_step(self, dt)
def __init__(self, start, scenes, x = 640, y = 360, title = "Untitled", fps = 30):
""" Initialize and run. """
self.running = True
self.current = start
self.scenes = scenes
self.window = pyglet.window.Window(x, y, title)
pyglet.clock.schedule_interval( self.on_step, 1.0 / fps)
@self.window.event
def on_activate():
self.scenes[self.current].on_activate(self)
@self.window.event
def on_close():
self.scenes[self.current].on_close(self)
@self.window.event
def on_context_lost():
self.scenes[self.current].on_context_lost(self)
@self.window.event
def on_context_state_lost():
self.scenes[self.current].on_context_state_lost(self)
@self.window.event
def on_deactivate():
self.scenes[self.current].on_deactivate(self)
@self.window.event
def on_draw():
self.scenes[self.current].on_draw(self)
@self.window.event
def on_expose():
self.scenes[self.current].on_expose(self)
@self.window.event
def on_hide():
self.scenes[self.current].on_hide(self)
@self.window.event
def on_key_press(symbol, modifiers):
self.scenes[self.current].on_key_press(self, symbol, modifiers)
@self.window.event
def on_key_release(symbol, modifiers):
self.scenes[self.current].on_key_release(self, symbol, modifiers)
@self.window.event
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
self.scenes[self.current].on_mouse_drag(self, x, y, dx, dy, buttons, modifiers)
@self.window.event
def on_mouse_enter(x, y):
self.scenes[self.current].on_mouse_enter(self, x, y)
@self.window.event
def on_mouse_leave(x, y):
self.scenes[self.current].on_mouse_leave(self, x, y)
@self.window.event
def on_mouse_motion(x, y, dx, dy):
self.scenes[self.current].on_mouse_motion(self, x, y, dx, dy)
@self.window.event
def on_mouse_press(x, y, button, modifiers):
self.scenes[self.current].on_mouse_press(self, x, y, button, modifiers)
@self.window.event
def on_mouse_release(x, y, button, modifiers):
self.scenes[self.current].on_mouse_release(self, x, y, button, modifiers)
@self.window.event
def on_mouse_scroll(x, y, scroll_x, scroll_y):
self.scenes[self.current].on_mouse_scroll(self, x, y, scroll_x, scroll_y)
@self.window.event
def on_move(x, y):
self.scenes[self.current].on_move(self, x, y)
@self.window.event
def on_resize(width, height):
self.scenes[self.current].on_resize(self, width, height)
@self.window.event
def on_show():
self.scenes[self.current].on_show(self)
@self.window.event
def on_text(text):
self.scenes[self.current].on_text(self, text)
@self.window.event
def on_text_motion(motion):
self.scenes[self.current].on_text_motion(self, motion)
@self.window.event
def on_text_motion_select(motion):
self.scenes[self.current].on_text_motion_select(self, motion)
pyglet.app.run()
class Scene(object):
""" Scene template. """
def on_step(self, app, dt):
pass
def on_activate(self, app):
pass
def on_close(self, app):
pass
def on_context_lost(self, app):
pass
def on_context_state_lost(self, app):
pass
def on_deactivate(self, app):
pass
def on_draw(self, app):
pass
def on_expose(self, app):
pass
def on_hide(self, app):
pass
def on_key_press(self, app, symbol, modifiers):
pass
def on_key_release(self, app, symbol, modifiers):
pass
def on_mouse_drag(self, app, x, y, dx, dy, buttons, modifiers):
pass
def on_mouse_enter(self, app, x, y):
pass
def on_mouse_leave(self, app, x, y):
pass
def on_mouse_motion(self, app, x, y, dx, dy):
pass
def on_mouse_press(self, app, x, y, button, modifiers):
pass
def on_mouse_release(self, app, x, y, button, modifiers):
pass
def on_mouse_scroll(self, app, x, y, scroll_x, scroll_y):
pass
def on_move(self, app, x, y):
pass
def on_resize(self, app, width, height):
pass
def on_show(self, app):
pass
def on_text(self, app, text):
pass
def on_text_motion(self, app, motion):
pass
def on_text_motion_select(self, app, motion):
pass
You use it like this:
import pyglet
import helper
class Menu_Scene(helper.Scene):
""" Menu scene in progress. """
def __init__(self):
super().__init__()
self.bg = pyglet.image.load("menu_bg.jpg")
self.snake = pyglet.sprite.Sprite ( pyglet.image.load("snake.png") )
self.snake.position = 320, 0
def on_draw(self, manager):
super().on_draw(manager)
manager.window.clear()
self.bg.blit(0, 0)
self.snake.draw()
helper.Scene_Manager("menu", {"menu" : Menu_Scene() } )
Before I'm going even further, splitting scenes into layers like this;
def on_activate(self, app):
for layer in self.layers:
layer.on_activate(app)
Please, tell me if I'm doing something really wrong. I've never written 200 lines of code so easily and quickly before.
1 Answer 1
Changes
It looks good, but have you tested the fps?
The on_draw
method of Scene_Manager
is not limited by the fps
parameter, only the on_step
is.
At Scene_Manager
, at the end of __init__
method, for our testing, add:
self.fps_display = pyglet.window.FPSDisplay(self.window) # for our test
And then edit the on_draw
window event to show it:
@self.window.event
def on_draw():
self.scenes[self.current].on_draw(self)
self.fps_display.draw() # for our test
If you test it now, the fps value goes wild, up to the hundreds.
Pyglet has a built-in function to limit fps. To use it, in your Scene_Manager
's __init__
, add:
pyglet.clock.set_fps_limit(fps)
Note that, despite the pyglet.clock
also having a function to show fps(clock.ClockDisplay()
), it doesn't work, and we should actually use the window.FPSDisplay
as we did.
Also, your class names don't follow pep8 style guide. Unless you have a specific reason not to use it, they should be: SceneManager
instead of Scene_Manager
; MenuScene
instead of Menu_Scene
.
As a last suggestion, as we've already prepared the code to show the fps_display
for our testing, it should be a good idea to take one more argument on SceneManager.__init__
, such as show_fps = False
to allow anyone to easily use it in case of need.
Testable, updated code
You code relies on some images that we don't have access to. The code below reflects the changes discussed above, adds a few minor changes on docstrings to follow pep257, and can be tested, which usually raises more attention/answers to a question (the sprite and bg lines that relied on images not provided were commented):
"""Pyglet scene manager."""
import pyglet
class SceneManager(object):
"""Runs and switches between different scenes."""
def on_step(self, dt):
"""Logic function executed every frame."""
if not self.running:
self.window.close()
pyglet.app.exit()
else:
self.scenes[self.current].on_step(self, dt)
def __init__(self, start, scenes, x=640, y=360, title="Untitled",
fps=30, show_fps=False):
"""Initialize and run."""
self.running = True
self.current = start
self.scenes = scenes
self.window = pyglet.window.Window(x, y, title)
pyglet.clock.schedule_interval(self.on_step, 1.0 / fps)
self.show_fps = show_fps
pyglet.clock.set_fps_limit(fps)
self.fps_display = pyglet.window.FPSDisplay(self.window)
@self.window.event
def on_activate():
self.scenes[self.current].on_activate(self)
@self.window.event
def on_close():
self.scenes[self.current].on_close(self)
@self.window.event
def on_context_lost():
self.scenes[self.current].on_context_lost(self)
@self.window.event
def on_context_state_lost():
self.scenes[self.current].on_context_state_lost(self)
@self.window.event
def on_deactivate():
self.scenes[self.current].on_deactivate(self)
@self.window.event
def on_draw():
self.scenes[self.current].on_draw(self)
if self.show_fps:
self.fps_display.draw()
@self.window.event
def on_expose():
self.scenes[self.current].on_expose(self)
@self.window.event
def on_hide():
self.scenes[self.current].on_hide(self)
@self.window.event
def on_key_press(symbol, modifiers):
self.scenes[self.current].on_key_press(self, symbol, modifiers)
@self.window.event
def on_key_release(symbol, modifiers):
self.scenes[self.current].on_key_release(self, symbol, modifiers)
@self.window.event
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
self.scenes[self.current].on_mouse_drag(
self, x, y, dx, dy, buttons, modifiers)
@self.window.event
def on_mouse_enter(x, y):
self.scenes[self.current].on_mouse_enter(self, x, y)
@self.window.event
def on_mouse_leave(x, y):
self.scenes[self.current].on_mouse_leave(self, x, y)
@self.window.event
def on_mouse_motion(x, y, dx, dy):
self.scenes[self.current].on_mouse_motion(self, x, y, dx, dy)
@self.window.event
def on_mouse_press(x, y, button, modifiers):
self.scenes[self.current].on_mouse_press(
self, x, y, button, modifiers)
@self.window.event
def on_mouse_release(x, y, button, modifiers):
self.scenes[self.current].on_mouse_release(
self, x, y, button, modifiers)
@self.window.event
def on_mouse_scroll(x, y, scroll_x, scroll_y):
self.scenes[self.current].on_mouse_scroll(
self, x, y, scroll_x, scroll_y)
@self.window.event
def on_move(x, y):
self.scenes[self.current].on_move(self, x, y)
@self.window.event
def on_resize(width, height):
self.scenes[self.current].on_resize(self, width, height)
@self.window.event
def on_show():
self.scenes[self.current].on_show(self)
@self.window.event
def on_text(text):
self.scenes[self.current].on_text(self, text)
@self.window.event
def on_text_motion(motion):
self.scenes[self.current].on_text_motion(self, motion)
@self.window.event
def on_text_motion_select(motion):
self.scenes[self.current].on_text_motion_select(self, motion)
pyglet.app.run()
class Scene(object):
"""Scene template."""
def on_step(self, app, dt):
pass
def on_activate(self, app):
pass
def on_close(self, app):
pass
def on_context_lost(self, app):
pass
def on_context_state_lost(self, app):
pass
def on_deactivate(self, app):
pass
def on_draw(self, app):
pass
def on_expose(self, app):
pass
def on_hide(self, app):
pass
def on_key_press(self, app, symbol, modifiers):
pass
def on_key_release(self, app, symbol, modifiers):
pass
def on_mouse_drag(self, app, x, y, dx, dy, buttons, modifiers):
pass
def on_mouse_enter(self, app, x, y):
pass
def on_mouse_leave(self, app, x, y):
pass
def on_mouse_motion(self, app, x, y, dx, dy):
pass
def on_mouse_press(self, app, x, y, button, modifiers):
pass
def on_mouse_release(self, app, x, y, button, modifiers):
pass
def on_mouse_scroll(self, app, x, y, scroll_x, scroll_y):
pass
def on_move(self, app, x, y):
pass
def on_resize(self, app, width, height):
pass
def on_show(self, app):
pass
def on_text(self, app, text):
pass
def on_text_motion(self, app, motion):
pass
def on_text_motion_select(self, app, motion):
pass
class MenuScene(Scene):
"""Menu scene in progress."""
def __init__(self):
super().__init__()
# Commenting the lines that depend on images.
# We don't have access to them.
# self.bg = pyglet.image.load("menu_bg.jpg")
# self.snake = pyglet.sprite.Sprite ( pyglet.image.load("snake.png") )
# self.snake.position = 320, 0
def on_draw(self, manager):
super().on_draw(manager)
manager.window.clear()
# self.bg.blit(0, 0)
# self.snake.draw()
if __name__ == '__main__':
SceneManager("menu", {"menu": MenuScene()}, show_fps=True)