Pygtk, however, has a rich widget toolkit, but it's hard to use it from within a pygame app. This is difficult in part because Gtk needs a gtk.main loop, but a typical pygame app is using it's own loop with clock.tick(). As a result, there are threads locking each other out, and generally craziness ensues.
But, it turns out, you can embed a pygame surface within a pygtk app, and it's actually pretty easy. So you can use just the one gtk.main loop, all interupt programming, access to the whole Gtk toolkit, and also all the easy loading of images, playing of sounds, collision detection, and other sprite functionality of pygame. It's really the best of all possible worlds.
I wrote a minimal demo script to show how to do this. The "game" is just a single sprite that you can move with the keyboard. You can see the screen in the screenshot at the top of this post that there is a gtk menu being activated!
I didn't intend this to be a tutorail on pygame, but rather integrating pygame in pygtk, so all the "game" does is let you move a sprite around.
It's probably easiest to look at the whole script, but let me point out some of the steps involved. I'll assume that you've already set up a gtk.Window with your menus and such. So, the first step is to set up a gtk.DrawingArea that will become the pygame surface. It's normal code to add it to your window:
da = gtk.DrawingArea()
da.set_size_request(300,300)
da.show()
vbox.pack_end(da)
da.connect("realize",self._realized)
This last line, connecting to the realize signal, is important. It's important because you use the gtk.gdk.window.id to associate the drawing area with pygame. The drawing area's window won't have an id until it's been rendered by gtk. So the then in the handler for the realize event, we set up the association with the drawing area, and also start actually drawing the game:
def _realized(self, widget, data=None):
os.putenv('SDL_WINDOWID', str(widget.window.xid))
pygame.init()
pygame.display.set_mode((300, 300), 0, 0)
self.screen = pygame.display.get_surface()
gobject.timeout_add(200, self.draw)
The first three lines essentially set up the drawing area as the pygame surface. The fourth line creates a global object for a pygame.screen object that will be used in the draw function. Finally, the draw function gets caslled every 200 milliseconds via a gobject timeout. This is as much easier way of setting up the game loop than the normal pygtk way of blocking the clock for a certain number of ticks, as gobject.timeout_add plays nicely with the gtk.main loop.
In a real game, you'd probably put an "update" function on the timeout to do things like check for collisions, random events, and etc... (You'd also probably use a much smaller time out period than 200 milliseconds). However, our game only has one sprite, and it doesn't do anything but move so we don't need that functionality. But how do we tell it move? In pygame, every time through the main loop you pull all the events off the event stack. With pygtk, you use signals to handle the input when it occurs.
To do this, in the __init__ function for the window, we set up our pygame objects, and then connect to the key-press signal:
#set up the pygame objects
self.image = pygame.image.load("sprite.png")
self.background = pygame.image.load("background.png")
self.x = 150
self.y = 150
#collect key press events
self.connect("key-press-event", self.key_pressed)
Then in the signal handler, we manipulate those objects. Typically, you'd be storing the x and y coordinates in a sprite object, but since there is only one sprite to track in this "game" we can handle it more simply. So in the key_press handler, we just detect if an arrow key was pressed, and we adjust the game data appropriately:
def key_pressed(self, widget, event, data=None):
if event.keyval == 65361:
self.x -= 5
elif event.keyval == 65362:
self.y -= 5
elif event.keyval == 65363:
self.x += 5
elif event.keyval == 65364:
self.y += 5
Then, every 200 milliseconds, the timeout comes buy and tells pygame to draw everything using normal pygame functions:
def draw(self):
self.screen.blit(self.background,[0,0])
rect = self.image.get_rect()
rect.x = self.x
rect.y = self.y
self.screen.blit(self.image, rect)
pygame.display.flip()
return True
Since pygame is getting update in gobject timeout and normal Gtk signal handlers, and not in it's own gameloop, gtk.main is free to run and handle menus, and even display dialogs:
I have been using this method in my own games (PyGTK + PyGame), unfortunately it doesn't work with GObject introspection-based python GTK apps, as the XID of the drawables can not be retrieved anymore, as the get_xid() method is not introspectable.
ReplyDeleteVery great and kind information and beautiful blog post thanks for sharing
ReplyDeleteclipping path service
Glad to know some new thing here. Thank you so much for sharing with us
ReplyDeleteclipping path
clipping path service/Raster To vector
this is epson printer error code 0xea and epson error code 0xea
ReplyDeletethis is epson printer error code 0xf3 & epson error code 0xf3
ReplyDeletethis is epson printer error code 00041 and epson error code 00041 as well as Epson Error Code 1073
ReplyDeletethis is epson printer error code 0x9a and epson error code 0x9a wf-3640 as well as epson code 0x9a
ReplyDelete
ReplyDeletethis is brother printer error code 30 and brother printer error 30
this is brother printer error code CC4-205 and brother error code CC4-205 and also brother printer error CC4-205
this is brother printer error code 32 and brother printer error 32 and at last brother printer 32 error
this is brother printer error code 50 and brother printer error 50