Posted By

avinashv on 05/25/07


Tagged


Versions (?)

Who likes this?

2 people have marked this snippet as a favorite

n3x
jfherring


spaceship shooter demo


 / Published in: Python
 

Code is GPL

Left/Right to rotate ship, Up for thrust, z to fire. a to toggle anti-aliasing

debug - f for FPS

by Avinash Vora

  1. # spaceship shooter test
  2. #
  3. # Copyright (c) 2007, Avinash Vora
  4. # http://www.avinashv.net
  5. #
  6. # Source code is protected by the GNU GPL
  7. #
  8. # May 26
  9. # - added randomly generated enemies
  10. # - added engine class
  11. # May 25
  12. # - added maximum ship speed
  13. # - added anti-aliasing toggle
  14. # - changed friction from 0.0075 to 0.01
  15. # - implemented bullets and physics
  16. # - fully object-oriented code
  17. # May 24
  18. # - added FPS read-out
  19. # - implemented ship and physics
  20.  
  21. import pygame, math, sys, random
  22. from pygame.locals import *
  23.  
  24. black = 0, 0, 0
  25. white = 255, 255, 255
  26. red = 255, 0, 0
  27. green = 0, 255, 0
  28.  
  29. d2p = lambda x1, y1, x2, y2: math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
  30.  
  31. class engine:
  32. def __init__(self, width, height):
  33. self.size = self.width, self.height = width, height
  34. self.running = True
  35. self.show_fps = False
  36. self.aaline = False
  37.  
  38. self.enemies = []
  39. self.ships = []
  40.  
  41. # state of keypresses
  42. self.right_pressed, self.left_pressed, self.up_pressed = False, False, False
  43. self.firing = False
  44.  
  45. pygame.init()
  46.  
  47. self.window = pygame.display.set_mode(self.size)
  48. self.window.fill(black)
  49.  
  50. def global_draw(self):
  51. for ship in self.ships:
  52. ship.draw()
  53. for bullet in ship.bullets:
  54. bullet.draw()
  55.  
  56. for enemy in self.enemies:
  57. enemy.draw()
  58.  
  59. def global_physics(self):
  60. # apply physics to every ship and bullet
  61. for ship in self.ships:
  62. ship.physics()
  63.  
  64. for bullet in ship.bullets:
  65. bullet.physics()
  66.  
  67. # check boundaries and destroy bullet if outside
  68. if (bullet.x > self.width) or (bullet.x < 0) or (bullet.y > self.height) or (bullet.y < 0):
  69. ship.bullets.remove(bullet)
  70. continue
  71.  
  72. # destroy bullets if they are older than 1000 ms
  73. if bullet.death <= pygame.time.get_ticks():
  74. ship.bullets.remove(bullet)
  75. continue
  76.  
  77. # check for collision
  78. for enemy in self.enemies:
  79. if d2p(bullet.x, bullet.y, enemy.x, enemy.y) < (bullet.radius + enemy.radius):
  80. enemy.health = enemy.health - bullet.strength
  81. ship.bullets.remove(bullet)
  82. if enemy.health <= 0:
  83. self.enemies.remove(enemy)
  84.  
  85. class bullet_class:
  86. def __init__(self, engine, parent):
  87. self.engine = engine
  88. self.parent = parent
  89. self.x_comp, self.y_comp = 0, 0
  90. self.x, self.y = self.parent.x, self.parent.y
  91. self.heading = self.parent.heading
  92. self.facing = self.parent.facing
  93. self.speed = 4.5
  94. self.radius = 4
  95. self.decay = 1000
  96. self.death = pygame.time.get_ticks() + self.decay
  97. self.strength = 3
  98.  
  99. # calculate resultant x and y components
  100. self.x_comp = self.parent.speed * math.sin(self.heading) + self.speed * math.sin(self.facing)
  101. self.y_comp = self.parent.speed * math.cos(self.heading) + self.speed * math.cos(self.facing)
  102.  
  103. # determine resultant speed
  104. self.speed= math.sqrt(self.x_comp ** 2 + self.y_comp ** 2)
  105.  
  106. # calculate resultant heading
  107. if self.y_comp >= 0:
  108. self.heading = math.atan(self.x_comp / self.y_comp)
  109. elif self.y_comp < 0:
  110. self.heading = math.atan(self.x_comp / self.y_comp) + math.pi
  111.  
  112. def draw(self):
  113. if self.engine.aaline:
  114. pygame.draw.circle(self.engine.window, green, (int(self.x), int(self.y)), self.radius, 1)
  115. else:
  116. pygame.draw.circle(self.engine.window, green, (int(self.x), int(self.y)), self.radius, 2)
  117.  
  118. def physics(self):
  119. # adjust (x, y) according to speed
  120. self.x = self.x + self.speed * math.sin(self.heading)
  121. self.y = self.y - self.speed * math.cos(self.heading)
  122.  
  123. class enemy_class:
  124. def __init__(self, engine, health):
  125. self.engine = engine
  126. self.x, self.y = random.randint(1, self.engine.width), random.randint(1, self.engine.height)
  127. self.health = health
  128. self.radius = self.health
  129.  
  130. def draw(self):
  131. if self.engine.aaline:
  132. pygame.draw.circle(self.engine.window, white, (int(self.x), int(self.y)), self.radius, 1)
  133. else:
  134. pygame.draw.circle(self.engine.window, white, (int(self.x), int(self.y)), self.radius, 2)
  135.  
  136. class spaceship_class:
  137. def __init__(self, engine):
  138. self.engine = engine
  139. self.accel = 0.06
  140. self.friction = 0.01
  141. self.max_speed = 4
  142. self.rotation_rate = 5
  143. self.x, self.y = self.engine.width / 2, self.engine.height / 2
  144. self.x_comp, self.y_comp = 0, 0
  145. self.facing = 0
  146. self.heading = 0
  147. self.speed = 0
  148. self.radius = 10
  149. self.bullet_delay = 200
  150. self.last_bullet_time = 0
  151.  
  152. # holds all the bullets created by this instance
  153. self.bullets = []
  154.  
  155. def draw(self):
  156. # calculate vertices of the ship
  157. vertices = [
  158. (self.x + self.radius * math.sin(self.facing), self.y - self.radius * math.cos(self.facing)),
  159. (self.x + self.radius * math.sin(self.facing + 2 * math.pi / 3), self.y - self.radius * math.cos(self.facing + 2 * math.pi / 3)),
  160. (self.x + self.radius * math.sin(self.facing + 4 * math.pi / 3), self.y - self.radius * math.cos(self.facing + 4 * math.pi / 3))
  161. ]
  162.  
  163. # draw lines with the back of the ship as red (anti-aliased if needed)
  164. if self.engine.aaline:
  165. pygame.draw.aaline(self.engine.window, white, vertices[0], vertices[1])
  166. pygame.draw.aaline(self.engine.window, white, vertices[2], vertices[0])
  167. pygame.draw.aaline(self.engine.window, red, vertices[1], vertices[2])
  168. else:
  169. pygame.draw.line(self.engine.window, white, vertices[0], vertices[1], 2)
  170. pygame.draw.line(self.engine.window, white, vertices[2], vertices[0], 2)
  171. pygame.draw.line(self.engine.window, red, vertices[1], vertices[2], 2)
  172.  
  173. def physics(self):
  174. if self.engine.right_pressed:
  175. # determine new direction
  176. self.facing = self.facing + (self.rotation_rate * math.pi) / 180
  177.  
  178. if self.engine.left_pressed:
  179. # determine new direction
  180. self.facing = self.facing - (self.rotation_rate * math.pi) / 180
  181.  
  182. if self.engine.up_pressed:
  183. # determine resultant x and y components
  184. self.x_comp = self.speed * math.sin(self.heading) + self.accel * math.sin(self.facing)
  185. self.y_comp = self.speed * math.cos(self.heading) + self.accel * math.cos(self.facing)
  186.  
  187. # determine resultant speed
  188. if self.speed < self.max_speed:
  189. self.speed = math.sqrt(self.x_comp ** 2 + self.y_comp ** 2)
  190. else:
  191. self.speed = self.max_speed
  192.  
  193. # calculate resultant heading
  194. if self.y_comp >= 0:
  195. self.heading = math.atan(self.x_comp / self.y_comp)
  196. elif self.y_comp < 0:
  197. self.heading = math.atan(self.x_comp / self.y_comp) + math.pi
  198.  
  199. # create new bullet
  200. if self.engine.firing:
  201. if pygame.time.get_ticks() >= (self.last_bullet_time + self.bullet_delay):
  202. self.bullets.append(bullet_class(self.engine, self))
  203. self.last_bullet_time = pygame.time.get_ticks()
  204.  
  205. # adjust speed for friction
  206. if self.speed > 0: self.speed = self.speed - self.friction
  207.  
  208. # adjust (x, y) according to speed
  209. self.x = self.x + self.speed * math.sin(self.heading)
  210. self.y = self.y - self.speed * math.cos(self.heading)
  211.  
  212. # check boundaries
  213. if self.x > self.engine.width: self.x = 0
  214. if self.x < 0: self.x = self.engine.width
  215. if self.y > self.engine.height: self.y = 0
  216. if self.y < 0: self.y = self.engine.height
  217.  
  218. if __name__ == '__main__':
  219. if not pygame.font: print 'Fonts disabled!'
  220. if not pygame.mixer: print 'Sound disabled!'
  221.  
  222. game = engine(640, 480)
  223. clock = pygame.time.Clock()
  224.  
  225. pygame.display.set_caption('shooter test')
  226.  
  227. game.ships.append(spaceship_class(game))
  228.  
  229. for item in range(1, random.randint(5, 15)): game.enemies.append(enemy_class(game, random.randint(5, 15)))
  230.  
  231. while game.running:
  232. # limit fps to 60
  233. clock.tick(60)
  234.  
  235. # clear screen
  236. game.window.fill(black)
  237.  
  238. # show fps
  239. if pygame.font and game.show_fps:
  240. font = pygame.font.Font(None, 16)
  241. text = font.render('fps ' + str(int(clock.get_fps())), 1, (150, 150, 150))
  242. text_position = text.get_rect(centerx = game.width / 2)
  243. game.window.blit(text, text_position)
  244.  
  245. # handle physics and drawing
  246. game.global_physics()
  247. game.global_draw()
  248.  
  249. # flip the screen
  250. pygame.display.update()
  251.  
  252. # handle input
  253. for event in pygame.event.get():
  254. if event.type == QUIT:
  255. running = False
  256.  
  257. # key down
  258. elif event.type == KEYDOWN:
  259. if event.key == K_ESCAPE:
  260. game.running = False
  261. elif event.key == K_RIGHT:
  262. game.right_pressed = True
  263. elif event.key == K_LEFT:
  264. game.left_pressed = True
  265. elif event.key == K_UP:
  266. game.up_pressed = True
  267. elif event.key == K_z:
  268. game.firing = True
  269. elif event.key == K_f:
  270. game.show_fps = not game.show_fps
  271. elif event.key == K_a:
  272. game.aaline = not game.aaline
  273.  
  274. # key up
  275. elif event.type == KEYUP:
  276. if event.key == K_RIGHT:
  277. game.right_pressed = False
  278. elif event.key == K_LEFT:
  279. game.left_pressed = False
  280. elif event.key == K_UP:
  281. game.up_pressed = False
  282. elif event.key == K_z:
  283. game.firing = False

Report this snippet  

Comments

RSS Icon Subscribe to comments
Posted By: n3x on May 25, 2007

30 fps here, this is extremely bad. Your mistake is that you update the entire screen when there is no need to do that. Note that blit returns a rect that describes the area updated, and display.update accepts a list of just these rectangles.

The ship is only three pixels that randomly flicker on and off for me. I can't help with that, though, haven't worked with aaline yet.

Posted By: avinashv on May 25, 2007

Updating the whole screen is not what i would do normally. It was just for this demo. However, i was getting 50+ fps regardless, and was hoping it was the norm so that maybe i don't have to bother working with the rectangles.

aaline works fine for me, but your problem is worrying. the documentation is kind of weak in terms of what support aaline has.

I'll try and find you on IRC.

Posted By: avinashv on May 25, 2007

Updated to use line instead of aaline, and a thickness of 2px for the ship.

You need to login to post a comment.