Are you looking for an open-source library to create video games and other multimedia applications in your GUI App? This post will get you to understand how to use the pygame Python Library using Python4Delphi (P4D) in the Delphi/C++ Builder application and create interesting Aliens or Space Invaders games.
Pygame is a free and open-source cross-platform library for the development of multimedia applications like video games using Python. It uses the Simple DirectMedia Layer (SDL) library and several other popular libraries to abstract the most common functions, making writing these programs a more intuitive task.
Pygame is highly portable and runs on nearly every platform and operating system. Pygame is free and has been downloaded millions of times. Pygame is released under the LGPL licence, which you can create open source, freeware, shareware, and commercial games with it.
Table of Contents
Demo: Create a Space Invaders Game
This post will guide you on how to create a simple but addicting game using Python’s Pygame and then display it in the Delphi Windows GUI app.
First, open and run our Python GUI using project Demo1 from Python4Delphi with RAD Studio. Then insert the script into the lower Memo, click the Execute button, and get the result in the upper Memo. You can find the Demo1 source on GitHub. The behind the scene details of how Delphi manages to run your Python code in this amazing Python GUI can be found at this link.
Let’s run the Space Invaders Game demo of the Pygame library. It will show you a mini-game where you have to defend against aliens. The following code is credited to René Dudfield (@illume), Thomas Kluyver (@takluyver), and Charles (@charlesej) (visit the source here):
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 |
import random import os # Import basic pygame modules import pygame as pg # See if we can load more than standard BMP if not pg.image.get_extended(): raise SystemExit("Sorry, extended image module required") # Game constants MAX_SHOTS = 2 # Most player bullets on screen ALIEN_ODDS = 22 # Chances a new alien appears BOMB_ODDS = 60 # Chances a new bomb will drop ALIEN_RELOAD = 12 # Frames between new aliens SCREENRECT = pg.Rect(0, 0, 640, 480) SCORE = 0 main_dir = "D:(2021) Upwork 1 - pythongui.org" def load_image(file): """ Loads an image, prepares it for play """ file = os.path.join(main_dir, "data", file) try: surface = pg.image.load(file) except pg.error: raise SystemExit('Could not load image "%s" %s' % (file, pg.get_error())) return surface.convert() def load_sound(file): """ Because pygame can be compiled without a mixer. """ if not pg.mixer: return None file = os.path.join(main_dir, "data", file) try: sound = pg.mixer.Sound(file) return sound except pg.error: print("Warning, unable to load, %s" % file) return None # Each type of game object gets an init and an update function. # The update function is called once per frame, and it is when each object should # change it's current position and state. # # The Player object actually gets a "move" function instead of update, # since it is passed extra information about the keyboard. class Player(pg.sprite.Sprite): """ Representing the player as a moon buggy type car. """ speed = 10 bounce = 24 gun_offset = -11 images = [] def __init__(self): pg.sprite.Sprite.__init__(self, self.containers) self.image = self.images[0] self.rect = self.image.get_rect(midbottom=SCREENRECT.midbottom) self.reloading = 0 self.origtop = self.rect.top self.facing = -1 def move(self, direction): if direction: self.facing = direction self.rect.move_ip(direction * self.speed, 0) self.rect = self.rect.clamp(SCREENRECT) if direction < 0: self.image = self.images[0] elif direction > 0: self.image = self.images[1] self.rect.top = self.origtop - (self.rect.left // self.bounce % 2) def gunpos(self): pos = self.facing * self.gun_offset + self.rect.centerx return pos, self.rect.top class Alien(pg.sprite.Sprite): """ An alien spaceship. That slowly moves down the screen. """ speed = 13 animcycle = 12 images = [] def __init__(self): pg.sprite.Sprite.__init__(self, self.containers) self.image = self.images[0] self.rect = self.image.get_rect() self.facing = random.choice((-1, 1)) * Alien.speed self.frame = 0 if self.facing < 0: self.rect.right = SCREENRECT.right def update(self): self.rect.move_ip(self.facing, 0) if not SCREENRECT.contains(self.rect): self.facing = -self.facing self.rect.top = self.rect.bottom + 1 self.rect = self.rect.clamp(SCREENRECT) self.frame = self.frame + 1 self.image = self.images[self.frame // self.animcycle % 3] class Explosion(pg.sprite.Sprite): """ An explosion. Hopefully the Alien and not the player! """ defaultlife = 12 animcycle = 3 images = [] def __init__(self, actor): pg.sprite.Sprite.__init__(self, self.containers) self.image = self.images[0] self.rect = self.image.get_rect(center=actor.rect.center) self.life = self.defaultlife def update(self): """ Called every time around the game loop. Show the explosion surface for 'defaultlife'. Every game tick(update), we decrease the 'life'. Also we animate the explosion. """ self.life = self.life - 1 self.image = self.images[self.life // self.animcycle % 2] if self.life <= 0: self.kill() class Shot(pg.sprite.Sprite): """ A bullet the Player sprite fires. """ speed = -11 images = [] def __init__(self, pos): pg.sprite.Sprite.__init__(self, self.containers) self.image = self.images[0] self.rect = self.image.get_rect(midbottom=pos) def update(self): """ Called every time around the game loop. Every tick we move the shot upwards. """ self.rect.move_ip(0, self.speed) if self.rect.top <= 0: self.kill() class Bomb(pg.sprite.Sprite): """ A bomb the aliens drop. """ speed = 9 images = [] def __init__(self, alien): pg.sprite.Sprite.__init__(self, self.containers) self.image = self.images[0] self.rect = self.image.get_rect(midbottom=alien.rect.move(0, 5).midbottom) def update(self): """ Called every time around the game loop. Every frame we move the sprite 'rect' down. When it reaches the bottom we: - Make an explosion. - Remove the Bomb. """ self.rect.move_ip(0, self.speed) if self.rect.bottom >= 470: Explosion(self) self.kill() class Score(pg.sprite.Sprite): """ To keep track of the score. """ def __init__(self): pg.sprite.Sprite.__init__(self) self.font = pg.font.Font(None, 20) self.font.set_italic(1) self.color = pg.Color("white") self.lastscore = -1 self.update() self.rect = self.image.get_rect().move(10, 450) def update(self): """ We only update the score in update() when it has changed. """ if SCORE != self.lastscore: self.lastscore = SCORE msg = "Score: %d" % SCORE self.image = self.font.render(msg, 0, self.color) def main(winstyle=0): # Initialize pygame if pg.get_sdl_version()[0] == 2: pg.mixer.pre_init(44100, 32, 2, 1024) pg.init() if pg.mixer and not pg.mixer.get_init(): print("Warning, no sound") pg.mixer = None fullscreen = False # Set the display mode winstyle = 0 # |FULLSCREEN bestdepth = pg.display.mode_ok(SCREENRECT.size, winstyle, 32) screen = pg.display.set_mode(SCREENRECT.size, winstyle, bestdepth) # Load images, assign to sprite classes # (do this before the classes are used, after screen setup) img = load_image("player1.gif") Player.images = [img, pg.transform.flip(img, 1, 0)] img = load_image("explosion1.gif") Explosion.images = [img, pg.transform.flip(img, 1, 1)] Alien.images = [load_image(im) for im in ("alien1.gif", "alien2.gif", "alien3.gif")] Bomb.images = [load_image("bomb.gif")] Shot.images = [load_image("shot.gif")] # Decorate the game window icon = pg.transform.scale(Alien.images[0], (32, 32)) pg.display.set_icon(icon) pg.display.set_caption("Pygame Aliens") pg.mouse.set_visible(0) # Create the background, tile the bgd image bgdtile = load_image("background.gif") background = pg.Surface(SCREENRECT.size) for x in range(0, SCREENRECT.width, bgdtile.get_width()): background.blit(bgdtile, (x, 0)) screen.blit(background, (0, 0)) pg.display.flip() # Load the sound effects boom_sound = load_sound("boom.wav") shoot_sound = load_sound("car_door.wav") if pg.mixer: music = os.path.join(main_dir, "data", "house_lo.wav") pg.mixer.music.load(music) pg.mixer.music.play(-1) # Initialize Game Groups aliens = pg.sprite.Group() shots = pg.sprite.Group() bombs = pg.sprite.Group() all = pg.sprite.RenderUpdates() lastalien = pg.sprite.GroupSingle() # Assign default groups to each sprite class Player.containers = all Alien.containers = aliens, all, lastalien Shot.containers = shots, all Bomb.containers = bombs, all Explosion.containers = all Score.containers = all # Create Some Starting Values global score alienreload = ALIEN_RELOAD clock = pg.time.Clock() # Initialize our starting sprites global SCORE player = Player() Alien() # Note, this 'lives' because it goes into a sprite group if pg.font: all.add(Score()) # Run our main loop whilst the player is alive. while player.alive(): # Get input for event in pg.event.get(): if event.type == pg.QUIT: return if event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE: return elif event.type == pg.KEYDOWN: if event.key == pg.K_f: if not fullscreen: print("Changing to FULLSCREEN") screen_backup = screen.copy() screen = pg.display.set_mode( SCREENRECT.size, winstyle | pg.FULLSCREEN, bestdepth ) screen.blit(screen_backup, (0, 0)) else: print("Changing to windowed mode") screen_backup = screen.copy() screen = pg.display.set_mode( SCREENRECT.size, winstyle, bestdepth ) screen.blit(screen_backup, (0, 0)) pg.display.flip() fullscreen = not fullscreen keystate = pg.key.get_pressed() # Clear/erase the last drawn sprites all.clear(screen, background) # Update all the sprites all.update() # Handle player input direction = keystate[pg.K_RIGHT] - keystate[pg.K_LEFT] player.move(direction) firing = keystate[pg.K_SPACE] if not player.reloading and firing and len(shots) < MAX_SHOTS: Shot(player.gunpos()) if pg.mixer: shoot_sound.play() player.reloading = firing # Create new alien if alienreload: alienreload = alienreload - 1 elif not int(random.random() * ALIEN_ODDS): Alien() alienreload = ALIEN_RELOAD # Drop bombs if lastalien and not int(random.random() * BOMB_ODDS): Bomb(lastalien.sprite) # Detect collisions between aliens and players. for alien in pg.sprite.spritecollide(player, aliens, 1): if pg.mixer: boom_sound.play() Explosion(alien) Explosion(player) SCORE = SCORE + 1 player.kill() # See if shots hit the aliens. for alien in pg.sprite.groupcollide(shots, aliens, 1, 1).keys(): if pg.mixer: boom_sound.play() Explosion(alien) SCORE = SCORE + 1 # See if alien bombs hit the player. for bomb in pg.sprite.spritecollide(player, bombs, 1): if pg.mixer: boom_sound.play() Explosion(player) Explosion(bomb) player.kill() # Draw the scene dirty = all.draw(screen) pg.display.update(dirty) # Cap the framerate at 40fps. Also called 40HZ or 40 times per second. clock.tick(40) if pg.mixer: pg.mixer.music.fadeout(1000) pg.time.wait(1000) pg.quit() # Call the "main" function if running this script if __name__ == "__main__": main() |
What does it show you about Pygame?
- pg.sprite, the difference between Sprite and Group.
- Dirty rectangle optimization for processing for speed.
- Music with pg.mixer.music, including fadeout.
- Sound effects with pg.Sound.
- Event processing, keyboard handling, QUIT handling.
- The main loop frame is limited with a game clock from pg.time.Clock.
- Fullscreen switching.
Controls
- Left and right arrows to move.
- Spacebar to shoot
- f key to toggle between fullscreen.
Let’s see the result in Python GUI, and enjoy your game!
Congratulations, now you have learned how to create a simple but addicting game using Python’s Pygame and then display it in the Delphi Windows GUI app. Now you can try all interesting examples from their Github using the Pygame library and Python4Delphi.
Check out the Pygame library for Python and use it in your projects: https://pypi.org/project/pygame/ and
Check out Python4Delphi which easily allows you to build Python GUIs for Windows using Delphi: https://github.com/pyscripter/python4delphi