Developing with Freeform Design Surfaces: GooCanvas and PyGame - Instructors: rickspencer3
1 [18:00] <ClassBot> Logs for this session will be available at http://irclogs.ubuntu.com/2011/09/08/%23ubuntu-classroom.html following the conclusion of the session.
2 [18:02] <rickspencer3> sooo
3 [18:02] <rickspencer3> hi everybody
4 [18:02] <rickspencer3> you just missed my best stff
5 [18:03] <rickspencer3> I was talking in teh wrong channel for the last 2 minutes :)
6 [18:03] <rickspencer3> so, let's try again ...
7 [18:03] <rickspencer3> Hello all. Today I will discuss 2 of the APIs that I have used to have a lot of fun with programming for Ubuntnu.
8 [18:03] <rickspencer3> These APIs are GooCanvas and PyGame. They are both similar in the sense that they provide you with a 2d surface on which you can construct interactive GUIs for your users.
9 [18:03] <rickspencer3> seriously, I have had tons of fun writing apps with thes over the years
10 [18:04] <rickspencer3> However, I fonud them to have different strengths and weaknesses. If you choose the correct API it will be more easy and more fun to write yoru app.
11 [18:04] <rickspencer3> so, why goocanvas or pygame at all?
12 [18:04] <rickspencer3> A typical desktop app is composed of widgets that a user is used to. Like buttons, entry boxes, and such.
13 [18:04] <rickspencer3> For these desktop apps, I strongly recommend sticking with PyGtk for the time being.
14 [18:04] <rickspencer3> like the next year, I think
15 [18:05] <rickspencer3> PyGtk is the way to go for what I call "boxy" apps
16 [18:05] <rickspencer3> I use pygtk all the time
17 [18:05] <rickspencer3> However, sometimes part of an app, or pretty much a whole app, won't need buttons and lists and entry boxes, but will need to display, modify, or animate images, drawings, etc...
18 [18:05] <rickspencer3> Sometimes in response to user input, sometimes not.
19 [18:05] <rickspencer3> My goal for this session is to help you choose the right API for those kinds of apps, and to get you started with them.
20 [18:05] <rickspencer3> please ask questions at any time
21 [18:05] <rickspencer3> I will check for questions often
22 [18:06] <rickspencer3> I'll start with GooCanvas because I already did a session on this last year, so there is lots of material.
23 [18:06] <rickspencer3> https://wiki.ubuntu.com/UbuntuOpportunisticDeveloperWeek/GooCanvas
24 [18:06] <rickspencer3> basically, I shall copy and past from there, answering quetsions as I go
25 [18:07] <rickspencer3> thoguh I may skip some to leave room for pygame
26 [18:07] <rickspencer3> So what is a goocanvas?
27 [18:07] <rickspencer3> A goocanvas is a 2d composing surface
28 [18:07] <rickspencer3> You can use it to make pretty much any kind of image
29 [18:08] <rickspencer3> It's kind of like an api around a drawing program
30 [18:08] <rickspencer3> So you can have a ton of fun using a goocanvas, because you are pretty much freed from the constraints of a widget library in creating your UI
31 [18:08] <rickspencer3> goocanvas is cairo under the covers
32 [18:08] <rickspencer3> and is designed to easily integrate into your gtk app
33 [18:08] <rickspencer3> So let's add a goocanvas to a pygtk app
34 [18:09] <rickspencer3> Add it just like a normal pygtk widget
35 [18:09] <rickspencer3> #set up the goo canvas
36 [18:09] <rickspencer3> self.goo_canvas = goocanvas.Canvas() self.goo_canvas.set_size_request(640, 480) self.goo_canvas.show()
37 [18:09] <rickspencer3> tada!
38 [18:09] <rickspencer3> you have a goocanvas
39 [18:09] <rickspencer3> Be sure to set the size, otherwise it defaults to 1000,1000, it does not default to the size alloted to it in your window.
40 [18:09] <rickspencer3> Handle window resizing to resize your goocanvas as well
41 [18:09] <rickspencer3> !!
42 [18:10] <rickspencer3> the goocanvas won't automatically change size if it's container changes size
43 [18:10] <rickspencer3> For example, if your goocanvas is in a VBox, you can do this:
44 [18:10] <rickspencer3> rect = self.builder.get_object("vbox2").get_allocation() self.goo_canvas.set_bounds(0,0,rect.width,rect.height)
45 [18:10] <rickspencer3> remember the root item for your goocanvas, you'll need it later often self.root = self.goo_canvas.get_root_item()
46 [18:10] <rickspencer3> The "root" is like the root of an item tree in XML
47 [18:10] <rickspencer3> So now that we have a goocanvas, we need to add "Items" to it.
48 [18:10] <rickspencer3> Anything that can be added to a goocanvas is an Item. It get's it's capabilities by inheriting from ItemSimple, and by implementing the Item interface.
49 [18:11] <rickspencer3> Let's add an item to the goocanvas to get a look at how it works in general.
50 [18:11] <rickspencer3> We'll start by adding an image.
51 [18:11] <rickspencer3> First, you need to get a gtk.pixbux for your image:
52 [18:11] <rickspencer3> pb = gtk.gdk.pixbuf_new_from_file(path)
53 [18:11] <rickspencer3> Then you calculate where you want the image to show on the goocanvas. You'll need a top and a left to place most items on a goo canvas.
54 [18:11] <rickspencer3> For example, to center the image, I do this:
55 [18:11] <rickspencer3> cont_left, cont_top, cont_right, cont_bottom = self.goo_canvas.get_bounds() img_w = pb.get_width() img_h = pb.get_height() img_left = (cont_right - img_w)/2 img_top = (cont_bottom - img_h)/2
56 [18:11] <rickspencer3> it's a bit hard to read, I guess
57 [18:12] <rickspencer3> but I basically just calculated the pixel center of the goocanvas
58 [18:12] <rickspencer3> and stored the "bounds" that the calculation returned
59 [18:12] <rickspencer3> Now I am ready to create the item.
60 [18:12] <rickspencer3> Note that I create the Item, but there is nothing like goocanvas.add(item) rather, when you create the item, you set it's parent property.
61 [18:12] <rickspencer3> The parent property is the root of the goocanvas
62 [18:12] <rickspencer3> This is why I remember the root
63 [18:12] <rickspencer3> goocanvas.Image(pixbuf=pb,parent=self.root, x=img_left,y=img_top)
64 [18:13] <rickspencer3> This basic pattern is how you add all other types of items.
65 [18:13] <rickspencer3> decide where to put the item, and set it's parent property to the root of the goocanvas.
66 [18:13] <rickspencer3> To remove the item from the goocanvas, you don't tell the goocanvas to remove it
67 [18:13] <rickspencer3> rather you tell the item to remove itself
68 [18:13] <rickspencer3> item.remove()
69 [18:13] <rickspencer3> any questions at all so far?
70 [18:14] <rickspencer3> In a moment, I'll go on to discuss the types of things that you can add to a goocanvas
71 [18:14] <rickspencer3> In my mind, there are really 3 types of items
72 [18:14] <rickspencer3> normal items that you add to draw the stuff you want
73 [18:14] <rickspencer3> this includes:
74 [18:14] <rickspencer3> Ellipse, Image, Path, Polyline, Rect, and Text
75 [18:15] <rickspencer3> the second type is for layout
76 [18:15] <rickspencer3> Layout and gruop items include:
77 [18:15] <rickspencer3> Group, Grid, and Table
78 [18:15] <rickspencer3> then finally,
79 [18:15] <rickspencer3> there is also Widget. Widget is pretty cool.
80 [18:15] <rickspencer3> You can add a gtk widget to your goocanvas, but note that it will live in a world seperate from the goocanvas
81 [18:15] <rickspencer3> In other words, gtk.Widgets won't be rendered if you create images form our goocanvas and such
82 [18:15] <rickspencer3> However, this is a cool way to add in situ editing to your goocanvas
83 [18:15] <rickspencer3> We'll just be talking about normal items for the rest of this class though
84 [18:15] <rickspencer3> So what are some of the things that you do with an item? Well, you compose with it. So you scale it, move it, rotate it, change it's z-order and such
85 [18:16] <rickspencer3> For a lot of things that you want to do with an item, you use set_property and get_property
86 [18:16] <rickspencer3> For example, to set the a might make a Text item like this:
87 [18:16] <rickspencer3> txt = goocanvas.Text(parent=self.root,text="some text", x=100, y=100, fill_color=self.ink_color)
88 [18:16] <rickspencer3> then change the text in it like this:
89 [18:16] <rickspencer3> txt.set_property("text","new text")
90 [18:16] <rickspencer3> Let's look at colors for a moment. There are generally two color properties to work with, stork-color, and fill-color
91 [18:16] <rickspencer3> If you've ever used a tool ink inkscape, this will make sense you to
92 [18:16] <rickspencer3> for something like a rect, stroke-color is the outline of the rectangle, and fill-color is the inside of the rectangle
93 [18:17] <rickspencer3> any questions so far?
94 [18:17] <rickspencer3> okay, moving on
95 [18:17] <rickspencer3> You can move, rotate, resize, and skew items
96 [18:17] <rickspencer3> The APIs for doing this are intuitive, imho
97 [18:17] <rickspencer3> To grow something by 10%
98 [18:17] <rickspencer3> item.scale(1.1,1.1)
99 [18:17] <rickspencer3> And to shrink it a bit:
100 [18:17] <rickspencer3> item.scale(.9,.9)
101 [18:18] <rickspencer3> Note that the items always consider themeselves to be their original size and orientation, so doing this will cause an item to grow twice: item.scale(1.1,1.1) item.scale(1.1,1.1)
102 [18:18] <rickspencer3> Now, when you start rotating and skewing items, some pretty confusing stuff can start happening
103 [18:18] <rickspencer3> Essentially, an item tracks it's own coordinate system, and doesn't much care about the goocanvas's coordinate system
104 [18:18] <rickspencer3> So if you rotate an item, for example, the coordinate systems are totally out of whack
105 [18:18] <rickspencer3> So if you pass the x/ys to an item based on the canvas's coordinate system, it can get waaaay out of whack
106 [18:19] <rickspencer3> Fortunately, goocanvas has some functions on it that just do these transforms for me
107 [18:19] <rickspencer3> let's say I catch a mouse click event on an item
108 [18:19] <rickspencer3> and I want to know where on the item the click happened
109 [18:19] <rickspencer3> well, the click coordinate are reported in the goocanvas's coordinate system, so I need to do a quick calculation to determine where the click happened on the item:
110 [18:19] <rickspencer3> e_x, e_y = self.goo_canvas.convert_to_item_space(self.selected_item,event.x,event.y)
111 [18:19] <rickspencer3>
112 [18:19] <rickspencer3> so, I used all of these facilities and more to make Photobomb
113 [18:20] <rickspencer3> you can check out Photobomb if you want to see some of the things that you can do with a GooCanvas
114 [18:20] <rickspencer3> Photobomb is essentially an image editor
115 [18:20] <rickspencer3> that made it a good candidate for GooCanvas
116 [18:20] <rickspencer3> however, I've also written games
117 [18:20] <rickspencer3> and PyGame is a better API for that
118 [18:21] <rickspencer3> before I go on to PyGame, any questions on Googcanvas?
119 [18:21] <rickspencer3> GooCanvas*
120 [18:21] <ClassBot> bUbu87 asked: how do you work with svg and gooCanvas? is there a simple way to load an svg to a canvas and keep it scaled all the time?
121 [18:21] <rickspencer3> indeed!
122 [18:21] <rickspencer3> there are shapes and paths that are all described with svg
123 [18:22] <rickspencer3> I've actually exported content from InkScape into a Goocanvas in the past
124 [18:22] <rickspencer3> let's look at paths and clipping for an example
125 [18:22] <rickspencer3> A path is essentially a "squiggle"
126 [18:22] <rickspencer3> It is defiened by a string that gets parsed into x,y coords, and then drawn with a bezier curve formula applied
127 [18:22] <rickspencer3> ^for those not totally familiar with svg
128 [18:22] <rickspencer3> here is a string that described a scribble:
129 [18:22] <rickspencer3> line_data = "M 4.0 4.0C4.0 4.0 5.0 4.0 5.0 4.0 5.0 4.0 6.0 4.0 6.0 3.0 10.0 1.0 13.0 2.0 9.0 15.0 6.0 36.0 28.0 11.0 28.0 11.0 29.0 11.0 33.0 12.0 33.0 15.0 32.0 19.0 27.0 51.0 27.0 53.0 27.0 54.0 27.0 54.0 27.0 54.0 36.0 49.0 37.0 49.0"
130 [18:22] <rickspencer3> then I can make a path out of this:
131 [18:22] <rickspencer3> path = goocanvas.Path(data=line_data, parent=self.root, line_width=self.ink_width, stroke_color=self.ink_color)
132 [18:23] <rickspencer3> so this will draw the path in the goocancas
133 [18:23] <rickspencer3> Now, a path is also useful because you can use it to clip another object
134 [18:23] <rickspencer3> You don't use a path object for this, just the string item.set_property("clip-path",line_data)
135 [18:23] <rickspencer3> shall I move on to PyGame?
136 [18:24] <rickspencer3> I put the Pygame notes here:
137 [18:24] <rickspencer3> https://wiki.ubuntu.com/UbuntuOpportunisticDeveloperWeek/PyGame
138 [18:24] <rickspencer3> PyGame is an API that is also for 2d surfaces.
139 [18:24] <rickspencer3> It is best for applications where there is a lot of updating of animation without user input (especially as it uses blitting).
140 [18:24] <rickspencer3> It has a set of baseclasses that make it easier to manage and change teh state of objects.
141 [18:24] <rickspencer3> It also has collision detection routines, which is very useful in game programming.
142 [18:25] <rickspencer3> So, net/net if you are doing something that is a game, or game-like, you're likely to want to use pygame, not GooCanvas
143 [18:25] <rickspencer3> pygame has fairly good reference documentation here: http://pygame.org/docs/ref/index.html
144 [18:25] <rickspencer3> There are also lots of tutorials available on the web. However, it's important to note that I use pygame a bit differently than they do in the typical tutorials.
145 [18:25] <rickspencer3> ^^WARNING WARNING^^
146 [18:25] <rickspencer3> Tutorials typically have you create a pygame window to display you game in, and then create a loop with a pygame clock object.
147 [18:25] <rickspencer3> I don't do it this way anymore. Now I prefer to embed a pygame surface into a Gtk app. This has some benefits to me:
148 [18:26] <rickspencer3> I can use menus for the GUI for things like starting games, pausing etc...
149 [18:26] <rickspencer3> I can use dialog boxes for things like hight scores or collecting information from users
150 [18:26] <rickspencer3> If you try to do these things from within a pygame loop, the gtk.main lool clashes with your pygame loop, and everything is just really hard to use.
151 [18:26] <rickspencer3> So, for the approach I take, I have three samples that you can look at at your leisure:
152 [18:26] <rickspencer3> 1. sample_game code:
153 [18:26] <rickspencer3> http://bazaar.launchpad.net/~rick-rickspencer3/+junk/pygame-pygtk-example/view/head:/game.py
154 [18:26] <rickspencer3> blog posting:
155 [18:26] <rickspencer3> http://theravingrick.blogspot.com/2011/08/using-pygame-in-pygtk-app.html
156 [18:27] <rickspencer3> This is the simplest code that I could make to demonstrate how to embed pygame and handle input.
157 [18:27] <rickspencer3>
158 [18:27] <rickspencer3> 2. jumper:
159 [18:27] <rickspencer3> http://bazaar.launchpad.net/~rick-rickspencer3/+junk/jumper/view/head:/jumper/JumperWindow.py
160 [18:27] <rickspencer3> This is only slightly more complex. It show how animate a sprite by changing the image, and show collision detection and playing a sound.
161 [18:27] <rickspencer3>
162 [18:27] <rickspencer3> 3. smashies:
163 [18:27] <rickspencer3> http://bazaar.launchpad.net/~rick-rickspencer3/+junk/smashies/files/head:/smashies/
164 [18:27] <rickspencer3> This is a full blown game which I have almost completed. I'm considering selling it in the software center when I am done. This one handles all the complexity of lives, scores, pausing, etc...
165 [18:27] <rickspencer3>
166 [18:27] <rickspencer3> smashies is essentially an asteroids clone
167 [18:27] <rickspencer3> I'm stilling thinking of a good name, and I need to replace some artwork
168 [18:28] <rickspencer3> anywho ...
169 [18:28] <rickspencer3> For this tutorial, we'll focus on jumper since it has an animated Sprite.
170 [18:28] <rickspencer3> before I dive in, any general questions about PyGame?
171 [18:28] <rickspencer3> okee let's go
172 [18:28] <rickspencer3> The overall approach is simple
173 [18:28] <rickspencer3> 1. set up a drawing area in Gtk Window
174 [18:29] <rickspencer3> 2. add pygame sprites to it
175 [18:29] <rickspencer3> 3. handle keyboard input from the gtk window
176 [18:29] <rickspencer3> 4. periodically call an update function to:
177 [18:29] <rickspencer3> a. update the data for the sprites
178 [18:29] <rickspencer3> b. update the view
179 [18:29] <rickspencer3> c. detect collisions and respond to them
180 [18:30] <rickspencer3> A game typically needs a background image. I put a background image and the other images and sounds in the data/media directory. Once you get the background image painting, it means you've got the main part of the game set up. So, we'll go through this part with patience.
181 [18:30] <rickspencer3> ^note that jumper is a Quickly app
182 [18:30] <rickspencer3> I put all the code in JumperWindow, so it's easy to see in one place.
183 [18:30] <rickspencer3> let's start making it work
184 [18:30] <rickspencer3> You need to import 3 modules:
185 [18:30] <rickspencer3> import pygame
186 [18:30] <rickspencer3> import os
187 [18:30] <rickspencer3> import gobject
188 [18:31] <rickspencer3> You'll see why you need these each in turn.
189 [18:31] <rickspencer3> First we want to create a a pygame.Image object to hold the background. Once we have that, we can use pygame functions to paint it.
190 === Odd-rationale_ is now known as Odd-rationale
191 [18:31] <rickspencer3> Since Jumper is a Quickly app, it I can use "get_media_file" to load it.
192 [18:31] <rickspencer3> I mean load it from the disk
193 [18:31] <rickspencer3> So I make the background in these 2 lines of code in the finish_initializing function:
194 [18:31] <rickspencer3> bg_image = get_media_file("background.png")
195 [18:31] <rickspencer3> self.background = pygame.image.load(bg_image)
196 [18:31] <rickspencer3> Before I use it, I have to set up the pygame environment though. I do this by adding a gtk.DrawingArea to the gtk.Window, and telling the os module to use the windows xid as a drawing surface.
197 [18:32] <rickspencer3> You can't just do that in the finish_initializing function, though. This is because drawingarea1 may not actually have an xid yet. This is easy to handle by connecting to the drawing area's "realize" signal. At that point, it will have an xid, and you can set up the environment.
198 [18:32] <rickspencer3> basically, you need to make sure that the drawingarea has been put on the screen, otherwise, it has no xid
199 [18:32] <rickspencer3> So, connect to the signal in finish initalizing:
200 [18:32] <rickspencer3> self.ui.drawingarea1.connect("realize",self.realized)
201 [18:32] <rickspencer3> and then write the self.realized function:
202 [18:32] <rickspencer3> def realized(self, widget, data=None):
203 [18:32] <rickspencer3> os.putenv('SDL_WINDOWID', str(self.ui.drawingarea1.window.xid))
204 [18:32] <rickspencer3> pygame.init()
205 [18:32] <rickspencer3> pygame.display.set_mode((300, 300), 0, 0)
206 [18:32] <rickspencer3> self.screen = pygame.display.get_surface()
207 [18:33] <rickspencer3> This function intializes pygame, and also create a pygame.Screen object that you need for drawing.
208 [18:33] <rickspencer3> so now we have a Gtk.DrawingArea ready to be a PyGame surface
209 [18:33] <rickspencer3> any questions before I show how to put the game background on it?
210 [18:34] <rickspencer3> ok, moving on
211 [18:34] <rickspencer3> So now that the drawing area is set up as a pygame surface, we need to actually draw to it.
212 [18:35] <rickspencer3> Actually, we'll want to periodically update the drawing so that it appears animated. So we want to update it over and over again.
213 [18:35] <rickspencer3> So after setting up pygame in tghe realized function, add a gobject timeout to recurringly call a function to update the game:
214 [18:35] <rickspencer3> gobject.timeout_add(200, self.update_game)
215 [18:35] <rickspencer3> the funciton update_game will be called every 200 millliseconds. For a real game, you might want to make it update more often.
216 [18:35] <rickspencer3> So, now we need write the udpate_game function. Eventually it will do a lot more, but for now, it will just tell the game to draw. So we need to write the draw_game function as well.
217 [18:35] <rickspencer3> def update_game(self):
218 [18:35] <rickspencer3> self.draw_game()
219 [18:35] <rickspencer3> return True
220 [18:35] <rickspencer3> def draw_game(self):
221 [18:35] <rickspencer3> self.screen.blit(self.background, [0,0])
222 [18:35] <rickspencer3> pygame.display.flip()
223 [18:35] <rickspencer3> Note that update_game returns True. This is important, because if it returns anything else, gobject will stop calling it.
224 [18:36] <rickspencer3> Looking at draw_game a little more, the first line tells the Screen object to "blit" the background. This means to only update the parts that have changed.
225 [18:36] <rickspencer3> This keeps the game from flickering on slower systems. We also pass in x/y coordinates to tell it to update the whole background.
226 [18:36] <rickspencer3> This doesn't paint to the screen yet, though. It just prepares it in memory.
227 [18:36] <rickspencer3> You can call blit a whole bunch of times for different sprites, but until you call pygame.display.flip() they won't actually be painted to the screen.
228 [18:36] <rickspencer3> In this way, the screen only gets update once, and the animation is smooth.
229 [18:37] <rickspencer3> Now if you run the game, you should see the background painted.
230 [18:37] <rickspencer3> before I go on to animating a sprite, any questions?
231 [18:37] * rickspencer3 drums fingers
232 [18:38] * rickspencer3 scratches head
233 [18:38] * rickspencer3 twiddles thumbs
234 [18:38] <rickspencer3> ok
235 [18:38] <rickspencer3> At this point you have a drawing surface set up, and you are drawing to it in a loop.
236 [18:38] <rickspencer3> Now let's add an animated sprite.
237 [18:38] <rickspencer3> I put 2 png's in the data/media director. One called "guy1.png" and called "guy2.png". We will animate the game by swapping these images back and forth every time the game paints.
238 [18:38] <rickspencer3> WARNING: I am doing something very wrong!
239 [18:38] <rickspencer3> Jumper loads the images as needed from disk. In a real game, this is a bad idea. This is a bad idea because that takes IO time, which can slow the game down.
240 [18:39] <rickspencer3> DON'T DO IT THIS WAY!!!
241 [18:39] <rickspencer3> It's better to load all the images and sounds at once when the game loads. See smashies for how I do that in the __init__.py file.
242 [18:39] <rickspencer3> anyway
243 [18:39] <rickspencer3> I mentioned before that pygame has some useful base classes. One of those base classes is called "Sprite" which is a really old game programming term.
244 [18:39] <rickspencer3> When adding an object to your game, it's best to derive from sprite. It's easier to manage the data for a sprite that way, and also there are useful pygame functions that expect a Sprite object.
245 [18:39] <rickspencer3> So, first create the sprite class and an initialization function:
246 [18:39] <rickspencer3> class Guy(pygame.sprite.Sprite):
247 [18:39] <rickspencer3> def __init__(self):
248 [18:39] <rickspencer3> pygame.sprite.Sprite.__init__(self)
249 [18:39] <rickspencer3> self.animation_stage = 1
250 [18:39] <rickspencer3> self.x = 35
251 [18:39] <rickspencer3> self.y = 180
252 [18:39] <rickspencer3> self.direction = 0
253 [18:40] <rickspencer3> Next, we'll write a function called "update". You'll see in a bit why it's important to call it "update".
254 [18:40] <rickspencer3> For this function, check which animation stage to use, and then use that image:
255 [18:40] <rickspencer3> def update(self):
256 [18:40] <rickspencer3> img = get_media_file("""guy%i.png""" % self.animation_stage)
257 [18:40] <rickspencer3> self.image = pygame.image.load(img)
258 [18:40] <rickspencer3> ^remember don't do it like this
259 [18:40] <rickspencer3> ^load the image from disk once at the beginning fo the program
260 [18:40] <rickspencer3> Next, you need to set the "rect" for the Sprite. The rect will be used in any collision detection functions you might use:
261 [18:40] <rickspencer3> self.rect = self.image.get_rect()
262 [18:40] <rickspencer3> self.rect.x = self.x
263 [18:40] <rickspencer3> self.rect.y = self.y
264 [18:41] <rickspencer3> Finally, update the animation stage.
265 [18:41] <rickspencer3> self.animation_stage += 1
266 [18:41] <rickspencer3> if self.animation_stage > 2:
267 [18:41] <rickspencer3> self.animation_stage = 1
268 [18:41] <rickspencer3> Now you just need to a "Guy" to your background.
269 [18:41] <rickspencer3> First, create a guy in the finish_initializing function.
270 [18:41] <rickspencer3> self.guy = Guy()
271 [18:41] <rickspencer3> Since a game will have a lot of sprites, it's easiest to manage sprites as a group
272 [18:41] <rickspencer3> There is a pygame class for this called a SpriteGroup, which you create by called RenderUpdates.
273 [18:41] <rickspencer3> So, crate a SpriteGroup and the guy to it:
274 [18:41] <rickspencer3> self.sprites =pygame.sprite.RenderUpdates()
275 [18:41] <rickspencer3> self.sprites.add(self.guy)
276 [18:42] <rickspencer3> Remember when we created the update_game function?
277 [18:42] <rickspencer3> Now you can see how useful the SpriteGroup is.
278 [18:43] <rickspencer3> You can call "update" on the sprite group, and it will in turn call update on every sprite in it. So add that call to the update_game function:
279 [18:43] <rickspencer3> self.sprites.update()
280 [18:43] <rickspencer3> Now, you also need to tell the Guy to draw. That's easy too with the SpriteGroup. Add this line to draw_game function:
281 [18:43] <rickspencer3> self.sprites.draw(self.screen)
282 [18:43] <rickspencer3> Now when you run the game, each tick the guy will swap images, and it will look like it's moving.
283 [18:43] * rickspencer3 phew
284 [18:44] <rickspencer3> ok, almost done
285 [18:44] <rickspencer3> any questions?
286 [18:44] <rickspencer3> WARNING
287 [18:44] <rickspencer3> Note that I handle keyboard and mouse input very differently than they describe in most pygame tutorials
288 [18:44] <rickspencer3> Responding to keyboard input is really easy, because you can just use gtk events.
289 [18:44] <rickspencer3> I have found that you need to attach to the key events for the window, not the drawing area.
290 [18:45] <rickspencer3> So, to make the guy jump when the user clicks the space bar, I make a key_press_event signal handler, that calls "jump()" on the guy:
291 [18:45] <rickspencer3> def key_pressed(self, widget, event, data=None):
292 [18:45] <rickspencer3> if event.keyval == 32:
293 [18:45] <rickspencer3> self.guy.jump()
294 [18:45] <rickspencer3> You can look at the jump and update functions in the Guy class to see how a jump was implemented.
295 [18:45] <rickspencer3> you can track mouse events, key up events, etc.. this way too
296 [18:46] <rickspencer3> Pygame also has functions for joysticks and stuff, but I haven't used that
297 [18:46] <rickspencer3> So, that's the essence of creating an animated sprite, which gets you a lot of the way toward making a game.
298 [18:46] <rickspencer3> We don't have time to delve into everything, but I did want to touch on collisions.
299 [18:46] <rickspencer3> Assuming that you've added another sprite called self.apple that tries to hit the guy, you can use one of the many pygame collision detection functions in every call to update_game to see if the apple hit the guy:
300 [18:46] <rickspencer3> if pygame.sprite.collide_rect(self.guy,self.apple):
301 [18:46] <rickspencer3> self.guy.kill()
302 [18:46] <rickspencer3> BELIEVE ME
303 [18:46] <rickspencer3> you don't want to write your own collision detection routines
304 [18:47] <rickspencer3> there are lots of good functions
305 [18:47] <rickspencer3> ones that compare whole groups of sprites, for example
306 [18:47] <rickspencer3> If you set the rect for your Sprite subclass, functions like this work well, and are easy.
307 [18:47] <rickspencer3> I also mentioned sounds.
308 [18:47] <rickspencer3> Pygame has a really rich set of sound functions.
309 [18:47] <rickspencer3> The easiest thing to demo is playing a sound from a file, like this:
310 [18:47] <rickspencer3> sound_path = get_media_file("beep_1.wav")
311 [18:47] <rickspencer3> sound = pygame.mixer.Sound(sound_path)
312 [18:47] <rickspencer3> sound.play()
313 [18:48] <rickspencer3> ....
314 [18:48] <rickspencer3> and
315 [18:48] <rickspencer3> that's everything I prepared for this session
316 [18:48] <rickspencer3> I'm happy to take some questions
317 [18:48] <rickspencer3> or maybe everyone is busy playing smashies right now
318 [18:50] <ClassBot> There are 10 minutes remaining in the current session.
319 [18:51] <rickspencer3> thanks ClassBot