Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/app/static/doc/myosa/ch020_making-activities-using-pygame.xhtml
blob: f4ad6eeeeffb61cc9d0866f12cf1f95151c0ca88 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><body><h1>Making Activities Using PyGame
</h1>
<h2>Introduction
</h2>
<p><strong>PyGame</strong> and <strong>PyGTK</strong> are two different ways to make a Python program with a graphical user interface.&#160; Normally you would not use both in the same program.&#160; Each of them has its own way of creating a window and each has its own way of handling events.
</p>
<p>The base class Activity we have been using is an extension of the PyGTK Window class and uses PyGTK event handling.&#160; The toolbars all Activities use are PyGTK components.&#160; In short, any Activity written in Python must use PyGTK. &#160; Putting a PyGame program in the middle of a PyGTK program is a bit like putting a model ship in a bottle.&#160; Fortunately there is some Python code called <strong>SugarGame</strong> that will make it possible to do that.
</p>
<p>Before we figure out how we'll get it in the bottle, let's have a look at our ship.
</p>
<h2>Making A Standalone Game Using PyGame
</h2>
<p>As you might expect, it's a good idea to make a standalone Python game using PyGame before you make an Activity out of it.&#160; I am not an experienced PyGame developer, but using the tutorial <em>Rapid Game Development with Python</em> by Richard Jones at this URL:
</p>
<p><a href="http://richard.cgpublisher.com/product/pub.84/prod.11">http://richard.cgpublisher.com/product/pub.84/prod.11</a>
  <br/></p>
<p>I was able to put together a modest game in about a day.&#160; It would have been sooner but the tutorial examples had bugs in them and I had to spend a fair amount of time using <strong>The GIMP</strong> to create image files for the sprites in the game.&#160;
</p>
<p><strong>Sprites</strong> are small images, often animated, that represent objects in a game.&#160; They generally have a transparent background so they can be drawn on top of a background image.&#160; I used the <strong>PNG</strong> format for my sprite files because it supports having an <strong>alpha channel</strong> (another term that indicates that part of the image is transparent).
</p>
<p>PyGame has code to display background images, to create sprites and move them around on the background, and to detect when sprites collide with one another and do something when that happens.&#160; This is the basis for making a lot of 2D games.&#160; There are lots of games written with PyGame that could be easily adapted to be Sugar Activities.
  <br/></p>
<p>My game is similar to the car game in the tutorial, but instead of a car I have an airplane.&#160; The airplane is the <em>Demoiselle</em> created by Alberto Santos-Dumont in 1909.&#160; Instead of having "pads" to collide with I have four students of Otto Lilienthal hovering motionless in their hang gliders.&#160; The hang gliders pitch downwards when Santos-Dumont collides with them.&#160; The controls used for the game have been modified too.&#160; I use the Plus and Minus keys on both the main keyboard and the keypad, plus the keypad 9 and 3 keys, to open and close the throttle and the Up and Down arrows on both the main keyboard and the keypad to move the joystick forward and back.&#160; Using the keypad keys is useful for a couple of reasons.&#160; First, some versions of <strong>sugar-emulator</strong> don't recognize the arrow keys on the main keyboard.&#160; Second, the arrow keys on the keypad map to the game controller on the XO laptop, and the non-arrow keys on the keypad map to the other buttons on the XO laptop screen.&#160; These buttons can be used to play the game when the XO is in tablet mode.
</p>
<p>As a flight simulator it isn't much, but it does demonstrate at least some of the things PyGame can do.&#160; Here is the code for the game, which I'm calling <strong>Demoiselle</strong>:
</p>
<pre>#! /usr/bin/env python
import pygame
import math
import sys

class Demoiselle:
    "This is a simple demonstration of using PyGame \
    sprites and collision detection."
    def __init__(self):
        self.background = pygame.image.load('sky.jpg')
        self.screen = pygame.display.get_surface()
        self.screen.blit(self.background, (0, 0))
        self.clock = pygame.time.Clock()
        self.running = True

        gliders = [
            GliderSprite((200, 200)),
            GliderSprite((800, 200)),
            GliderSprite((200, 600)),
            GliderSprite((800, 600)),
        ]
        self. glider_group = pygame.sprite.RenderPlain(
            gliders)

    def run(self):
        "This method processes PyGame messages"
        rect = self.screen.get_rect()
        airplane = AirplaneSprite('demoiselle.png',
            rect.center)
        airplane_sprite = pygame.sprite.RenderPlain(
            airplane)

        while self.running:
            self.clock.tick(30)

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.running = False
                    return
                elif event.type == pygame.VIDEORESIZE:
                    pygame.display.set_mode(event.size,
                        pygame.RESIZABLE)
                    self.screen.blit(self.background,
                        (0, 0))

                if not hasattr(event, 'key'):
                    continue
                down = event.type == pygame.KEYDOWN
                if event.key == pygame.K_DOWN or \
                    event.key == pygame.K_KP2:
                    airplane.joystick_back = down * 5
                elif event.key == pygame.K_UP or \
                    event.key == pygame.K_KP8:
                    airplane.joystick_forward = down * -5
                elif event.key == pygame.K_EQUALS or \
                    event.key == pygame.K_KP_PLUS or \
                    event.key == pygame.K_KP9:
                    airplane.throttle_up = down * 2
                elif event.key == pygame.K_MINUS or \
                    event.key == pygame.K_KP_MINUS or \
                    event.key == pygame.K_KP3:
                    airplane.throttle_down = down * -2

            self.glider_group.clear(self.screen,
                self.background)
            airplane_sprite.clear(self.screen,
                self.background)
            collisions = pygame.sprite.spritecollide(
                airplane,
                self.glider_group,  False)
            self.glider_group.update(collisions)
            self.glider_group.draw(self.screen)
            airplane_sprite.update()
            airplane_sprite.draw(self.screen)
            pygame.display.flip()

class AirplaneSprite(pygame.sprite.Sprite):
    "This class represents an airplane, the Demoiselle \
    created by Alberto Santos-Dumont"
    MAX_FORWARD_SPEED = 10
    MIN_FORWARD_SPEED = 1
    ACCELERATION = 2
    TURN_SPEED = 5
    def __init__(self, image, position):
        pygame.sprite.Sprite.__init__(self)
        self.src_image = pygame.image.load(image)
        self.rect = pygame.Rect(
            self.src_image.get_rect())
        self.position = position
        self.rect.center = self.position
        self.speed = 1
        self.direction = 0
        self.joystick_back = self.joystick_forward = \
            self.throttle_down = self.throttle_up = 0

    def update(self):
        "This method redraws the airplane in response\
        to events."
        self.speed += (self.throttle_up +
            self.throttle_down)
        if self.speed &gt; self.MAX_FORWARD_SPEED:
            self.speed = self.MAX_FORWARD_SPEED
        if self.speed &lt; self.MIN_FORWARD_SPEED:
            self.speed = self.MIN_FORWARD_SPEED
        self.direction += (self.joystick_forward + \
            self.joystick_back)
        x_coord, y_coord = self.position
        rad = self.direction * math.pi / 180
        x_coord += -self.speed * math.cos(rad)
        y_coord += -self.speed * math.sin(rad)
        screen = pygame.display.get_surface()
        if y_coord &lt; 0:
            y_coord = screen.get_height()

        if x_coord &lt; 0:
            x_coord = screen.get_width()

        if x_coord &gt; screen.get_width():
            x_coord = 0

        if y_coord &gt; screen.get_height():
            y_coord = 0
        self.position = (x_coord, y_coord)
        self.image = pygame.transform.rotate(
            self.src_image, -self.direction)
        self.rect = self.image.get_rect()
        self.rect.center = self.position

class GliderSprite(pygame.sprite.Sprite):
    "This class represents an individual hang \
    glider as developed by Otto Lilienthal."
    def __init__(self, position):
        pygame.sprite.Sprite.__init__(self)
        self.normal = pygame.image.load(
             'glider_normal.png')
        self.rect = pygame.Rect(self.normal.get_rect())
        self.rect.center = position
        self.image = self.normal
        self.hit = pygame.image.load('glider_hit.png')
    def update(self, hit_list):
        "This method redraws the glider when it collides\
        with the airplane and when it is no longer \
        colliding with the airplane."
        if self in hit_list:
            self.image = self.hit
        else:
            self.image = self.normal

def main():
    "This function is called when the game is run \
    from the command line"
    pygame.init()
    pygame.display.set_mode((0, 0), pygame.RESIZABLE)
    game = Demoiselle()
    game.run()
    sys.exit(0)

if __name__ == '__main__':
    main()
</pre>
<p>And here is the game in action:
</p>
<p><img alt="The Demoiselle standalone game." src="static/ActivitiesGuideSugar-demoiselle1-en.jpg" height="454" width="600"/></p>
<p>You'll find the code for this game in the file <strong>demoiselle.py</strong> in the book examples project in Git.
</p>
<h2>Introducing SugarGame
</h2>
<p><strong>SugarGame</strong> is not part of Sugar proper.&#160; If you want to use it you'll need to include the Python code for SugarGame inside your Activity bundle.&#160; I've included the version of SugarGame I'm using in the book examples project in the <strong>sugargame</strong> directory, but when you make your own games you'll want to be sure and get the latest code to include.&#160; You can do that by downloading the project from Gitorious using these commands:
</p>
<pre><code>mkdir sugargame
cd sugargame
git clone git://git.sugarlabs.org/sugargame/mainline.git</code></pre>
<p>You'll see two subdirectories in this project: <strong>sugargame</strong> and <strong>test</strong>, plus a <strong>README.txt</strong> file that contains information on using sugargame in your own Activities.&#160; The test directory contains a simple PyGame program that can be run either standalone or as an Activity.&#160; The standalone program is in the file named <strong>TestGame.py</strong>.&#160; The Activity, which is a sort of wrapper around the standalone version, is in file <strong>TestActivity.py</strong>.
  <br/></p>
<p>If you run <strong>TestGame.py</strong> from the command line you'll see it displays a bouncing ball on a white background.&#160; To try running the Activity version you'll need to run
</p>
<pre>./setup.py dev</pre>
<p>from the command line first.&#160; I was not able to get the Activity to work under sugar-emulator until I made two changes to it:
</p>
<ul><li>I made a copy of the <strong>sugargame</strong> directory within the <strong>test</strong> directory.</li>
  <li>I removed the line reading "<strong>sys.path.append(<span class="String"><span class="String">'</span>..<span class="String">'</span></span>) </strong><span class="Comment"><strong><span class="Comment">#</span> Import sugargame package from top directory.</strong>" from <strong>TestActivity.py</strong>.&#160; Obviously this line is supposed to help the program find the <strong>sugargame</strong> directory in the project but it didn't work in Fedora 10.&#160; Your own experience may be different.</span>
  <br/></li>
</ul><p><span class="Comment">The Activity looks like this:</span>
</p>
<p><span class="Comment"><img alt="The SugarGame demo Activity" src="static/ActivitiesGuideSugar-sugargame_1-en.jpg" height="453" width="600"/></span>
</p>
<p><span class="Comment">The <strong>PyGame</strong> toolbar has a single button that lets you make the bouncing ball pause and resume bouncing.</span>
</p>
<h2><span class="Comment">Making A Sugar Activity Out Of A PyGame Program</span>
</h2>
<p><span class="Comment">Now it's time to put our ship in that bottle.&#160; The first thing we need to do is make a copy of the <strong>sugargame</strong> directory of the SugarGame project into the mainline directory of our own project.</span>
</p>
<p><span class="Comment">The <strong>README.txt</strong> file in the SugarGame project is worth reading.&#160; It tells us to make an Activity based on the <strong>TestActivity.py</strong> example in the SugarGame project.&#160; This will be our bottle.&#160; Here is the code for mine, which is named <strong>DemoiselleActivity.py</strong>:</span>
</p>
<pre># DemoiselleActivity.py

from gettext import gettext as _

import gtk
import pygame
from sugar.activity import activity
from sugar.graphics.toolbutton import ToolButton
import gobject
import sugargame.canvas
import demoiselle2

class DemoiselleActivity(activity.Activity):
    def __init__(self, handle):
        super(DemoiselleActivity, self).__init__(handle)

        # Build the activity toolbar.
        self.build_toolbar()

        # Create the game instance.
        self.game = demoiselle2.Demoiselle()

        # Build the Pygame canvas.
        self._pygamecanvas = \
            sugargame.canvas.PygameCanvas(self)
        # Note that set_canvas implicitly calls
        # read_file when resuming from the Journal.
        self.set_canvas(self._pygamecanvas)
        self.score = ''

        # Start the game running.
        self._pygamecanvas.run_pygame(self.game.run)

    def build_toolbar(self):
        toolbox = activity.ActivityToolbox(self)
        activity_toolbar = toolbox.get_activity_toolbar()
        activity_toolbar.keep.props.visible = False
        activity_toolbar.share.props.visible = False

        self.view_toolbar = ViewToolbar()
        toolbox.add_toolbar(_('View'), self.view_toolbar)
        self.view_toolbar.connect('go-fullscreen',
                self.view_toolbar_go_fullscreen_cb)
        self.view_toolbar.show()

        toolbox.show()
        self.set_toolbox(toolbox)

    def view_toolbar_go_fullscreen_cb(self, view_toolbar):
        self.fullscreen()

    def read_file(self, file_path):
        score_file = open(file_path, "r")
        while score_file:
            self.score = score_file.readline()
            self.game.set_score(int(self.score))
        score_file.close()

    def write_file(self, file_path):
        score = self.game.get_score()
        f = open(file_path, 'wb')
        try:
            f.write(str(score))
        finally:
            f.close

class ViewToolbar(gtk.Toolbar):
    __gtype_name__ = 'ViewToolbar'

    __gsignals__ = {
        'needs-update-size': (gobject.SIGNAL_RUN_FIRST,
                              gobject.TYPE_NONE,
                              ([])),
        'go-fullscreen': (gobject.SIGNAL_RUN_FIRST,
                          gobject.TYPE_NONE,
                          ([]))
    }

    def __init__(self):
        gtk.Toolbar.__init__(self)
        self.fullscreen = ToolButton('view-fullscreen')
        self.fullscreen.set_tooltip(_('Fullscreen'))
        self.fullscreen.connect('clicked',
            self.fullscreen_cb)
        self.insert(self.fullscreen, -1)
        self.fullscreen.show()

    def fullscreen_cb(self, button):
        self.emit('go-fullscreen')
</pre>
<p><span class="Comment">This is a bit fancier than <strong>TestActivity.py</strong>.&#160; I decided that my game didn't really need to be paused and resumed, so I replaced the <strong>PyGame</strong> toolbar with a <strong>View</strong> toolbar that lets the user hide the toolbar when it is not needed.&#160; I use the <em>read_file()</em> and <em>write_file()</em> methods to save and restore the game score.&#160; (Actually this is faked, because I never put in any scoring logic in the game).&#160; I also hide the <strong>Keep</strong> and <strong>Share</strong> controls in the main toolbar.</span>
</p>
<p><span class="Comment">As you would expect, getting a ship in a bottle does require the ship to be modified.&#160; Here is <strong>demoiselle2.py</strong>, which has the modifications:</span>
</p>
<pre>#! /usr/bin/env python
import pygame
import gtk
import math
import sys

class Demoiselle:
    "This is a simple demonstration of using PyGame \
    sprites and collision detection."
    def __init__(self):
        self.clock = pygame.time.Clock()
        self.running = True
        self.background = pygame.image.load('sky.jpg')

    def get_score(self):
        return '99'

    def run(self):
        "This method processes PyGame messages"

        screen = pygame.display.get_surface()
        screen.blit(self.background, (0, 0))

        gliders = [
            GliderSprite((200, 200)),
            GliderSprite((800, 200)),
            GliderSprite((200, 600)),
            GliderSprite((800, 600)),
        ]
        glider_group = pygame.sprite.RenderPlain(gliders)

        rect = screen.get_rect()
        airplane = AirplaneSprite('demoiselle.png',
            rect.center)
        airplane_sprite = pygame.sprite.RenderPlain(
            airplane)

        while self.running:
            self.clock.tick(30)

            # Pump GTK messages.
            while gtk.events_pending():
                gtk.main_iteration()

            # Pump PyGame messages.
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.running = False
                    return
                elif event.type == pygame.VIDEORESIZE:
                    pygame.display.set_mode(event.size,
                        pygame.RESIZABLE)
                    screen.blit(self.background, (0, 0))

                if not hasattr(event, 'key'):
                    continue
                down = event.type == pygame.KEYDOWN
                if event.key == pygame.K_DOWN or \
                    event.key == pygame.K_KP2:
                    airplane.joystick_back = down * 5
                elif event.key == pygame.K_UP or \
                    event.key == pygame.K_KP8:
                    airplane.joystick_forward = down * -5
                elif event.key == pygame.K_EQUALS or \
                    event.key == pygame.K_KP_PLUS or \
                    event.key == pygame.K_KP9:
                    airplane.throttle_up = down * 2
                elif event.key == pygame.K_MINUS or \
                    event.key == pygame.K_KP_MINUS or \
                    event.key == pygame.K_KP3:
                    airplane.throttle_down = down * -2

            glider_group.clear(screen, self.background)
            airplane_sprite.clear(screen, self.background)
            collisions = pygame.sprite.spritecollide(
                airplane,
                glider_group, False)
            glider_group.update(collisions)
            glider_group.draw(screen)
            airplane_sprite.update()
            airplane_sprite.draw(screen)
            pygame.display.flip()

class AirplaneSprite(pygame.sprite.Sprite):
    "This class represents an airplane, the Demoiselle \
    created by Alberto Santos-Dumont"
    MAX_FORWARD_SPEED = 10
    MIN_FORWARD_SPEED = 1
    ACCELERATION = 2
    TURN_SPEED = 5
    def __init__(self, image, position):
        pygame.sprite.Sprite.__init__(self)
        self.src_image = pygame.image.load(image)
        self.rect = pygame.Rect(self.src_image.get_rect())
        self.position = position
        self.rect.center = self.position
        self.speed = 1
        self.direction = 0
        self.joystick_back = self.joystick_forward = \
            self.throttle_down = self.throttle_up = 0

    def update(self):
        "This method redraws the airplane in response\
        to events."
        self.speed += (self.throttle_up +
            self.throttle_down)
        if self.speed &gt; self.MAX_FORWARD_SPEED:
            self.speed = self.MAX_FORWARD_SPEED
        if self.speed &lt; self.MIN_FORWARD_SPEED:
            self.speed = self.MIN_FORWARD_SPEED
        self.direction += (self.joystick_forward +
            self.joystick_back)
        x_coord, y_coord = self.position
        rad = self.direction * math.pi / 180
        x_coord += -self.speed * math.cos(rad)
        y_coord += -self.speed * math.sin(rad)
        screen = pygame.display.get_surface()
        if y_coord &lt; 0:
            y_coord = screen.get_height()

        if x_coord &lt; 0:
            x_coord = screen.get_width()

        if x_coord &gt; screen.get_width():
            x_coord = 0

        if y_coord &gt; screen.get_height():
            y_coord = 0
        self.position = (x_coord, y_coord)
        self.image = pygame.transform.rotate(
            self.src_image, -self.direction)
        self.rect = self.image.get_rect()
        self.rect.center = self.position

class GliderSprite(pygame.sprite.Sprite):
    "This class represents an individual hang \
    glider as developed by Otto Lilienthal."
    def __init__(self, position):
        pygame.sprite.Sprite.__init__(self)
        self.normal = pygame.image.load(
            'glider_normal.png')
        self.rect = pygame.Rect(self.normal.get_rect())
        self.rect.center = position
        self.image = self.normal
        self.hit = pygame.image.load('glider_hit.png')
    def update(self, hit_list):
        "This method redraws the glider when it collides\
        with the airplane and when it is no longer \
        colliding with the airplane."
        if self in hit_list:
            self.image = self.hit
        else:
            self.image = self.normal

def main():
    "This function is called when the game is run \
    from the command line"
    pygame.init()
    pygame.display.set_mode((0, 0), pygame.RESIZABLE)
    game = Demoiselle()
    game.run()
    sys.exit(0)

if __name__ == '__main__':
    main()
</pre>
<p><span class="Comment">Why not load both <strong>demoiselle.py</strong> and <strong>demoiselle2.py</strong> in Eric and take a few minutes to see if you can figure out what changed between the two versions?</span>
</p>
<p><span class="Comment">Surprisingly little is different.&#160; I added some code to the PyGame main loop to check for PyGTK events and deal with them:</span>
</p>
<pre>        while self.running:
            self.clock.tick(30)

            <strong># Pump GTK messages.
            while gtk.events_pending():
                gtk.main_iteration()</strong>

            # Pump PyGame messages.
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.running = False
                    return
                elif event.type == pygame.VIDEORESIZE:
                    pygame.display.set_mode(event.size,
                        pygame.RESIZABLE)
                    screen.blit(self.background, (0, 0))

                if not hasattr(event, 'key'):
                    continue
                down = event.type == pygame.KEYDOWN
                if event.key == pygame.K_DOWN or \

<em>... continue dealing with PyGame events ...</em>
</pre>
<p><span class="Comment">This has the effect of making PyGame and PyGTK take turns handling events.&#160; If this code was not present GTK events would be ignored and you'd have no way to close the Activity, hide the toolbar, etc.&#160; You need to add <strong>import gtk</strong> at the top of the file so these methods can be found.</span>
</p>
<p><span class="Comment">Of course I also added the methods to set and return scores:</span>
</p>
<pre>&#160;    def get_score(self):
        return self.score

     def set_score(self, score):
        self.score = score
</pre>
<p><span class="Comment">The biggest change is in the <em>__init__()</em> method of the <strong>Demoiselle</strong> class.&#160; Originally I had code to display the background image on the screen:</span>
</p>
<pre>    def __init__(self):
        self.background = pygame.image.load('sky.jpg')
        self.screen = pygame.display.get_surface()
        self.screen.blit(self.background, (0, 0))
</pre>
<p><span class="Comment">The problem with this is that sugargame is going to create a special PyGTK Canvas object to replace the PyGame display and the DemoiselleActivity code hasn't done that yet, so <strong>self.screen</strong> will have a value of None.&#160; The only way to get around that is to move any code that refers to the <strong>display</strong> out of the <em>__init__()</em> method of the class and into the beginning of the method that contains the event loop.&#160; This may leave you with an <em>__init__()</em> method that does little or nothing.&#160; About the only thing you'll want there is code to create instance variables.</span>
</p>
<p><span class="Comment">Nothing we have done to <strong>demoiselle2.py</strong> will prevent it from being run as a standalone Python program.</span>
</p>To try out the game <span class="Comment">run <strong>./setup.py dev</strong> from within the <strong>Making_Activities_Using_PyGame</strong> directory.&#160; When you try out the Activity it should look like this:</span>
<p>
</p>
<p><span class="Comment"><img alt="The Demoiselle Activity." src="static/ActivitiesGuideSugar-demoiselle2_1-en.jpg" height="448" width="600"/><br/></span>
</p></body></html>