ORBITAL ODYSSEY 2

2022

I started this project the very day after the game jam mostly because I was a bit salty and wanted to prove to myself that the winning game was not hard to make in a short amount of time (which was accomplished). So I booted up pygame for the first time and set to work. Luckily I had dabbled at a python chess app the month before so at least I was familiar with the language. It ultimately turned out to be the most complete bit of game programming I had done yet, a very valuable learning experience.

The gameplay involves deciding where to place the given planets such that the resulting gravitational field carries your ship to the end without hitting obstacles. There was a state system for handling menus, level select, the editing phase, and playing phase. As well as basic UI interaction, sprite animation and a simple object inheritance system for all the entities.

  1 import copy
  2 import pygame.mouse
  3 from objects import*
  4 
  5 pygame.init()
  6 pygame.font.init()
  7 
  8 window = (1280, 720)
  9 screen = pygame.display.set_mode(window)
 10 pygame.display.set_caption("Orbital Odyssey")
 11 
 12 FPS = 30
 13 GRAV_CONST = 5
 14 VEL = 5
 15 
 16 FONT = pygame.font.SysFont("helveticaneue", 30)
 17 UI_SIZE = [100,100]
 18 num_levels = 6
 19 count = 0
 20 def update_window(objects):
 21     global count
 22     for object in objects:
 23         if isinstance(object, Player):
 24             if count >= 3:
 25                 count = 0
 26             screen.blit(object.animation[int(count)], object.location)
 27             count += 0.1
 28         else:
 29             screen.blit(object.image, object.location)
 30             if isinstance(object, Portal):
 31                 screen.blit(object.image2, object.location2)
 32     pygame.display.flip()
 33 
 34 def update_gravity(rocket, actors):
 35     force_x = 0
 36     force_y = 0
 37     for actor in actors:
 38         if actor.has_gravity:
 39             y = actor.get_center()[1] - rocket.get_center()[1]
 40             x = actor.get_center()[0] - rocket.get_center()[0]
 41             distance_squared = (x ** 2) + (y ** 2)
 42             gravity = GRAV_CONST / distance_squared
 43             force_x += gravity * x
 44             force_y += gravity * y
 45     rocket.velocity[0] += force_x
 46     rocket.velocity[1] += force_y
 47 
 48 def check_collision(rocket, actors):
 49     global cooldown
 50     rocket_rect = pygame.Rect(rocket.location[0], rocket.location[1], rocket.radius[0]*2, rocket.radius[1]*2)
 51     for actor in actors:
 52         if isinstance(actor, Portal):
 53             if rocket_rect.colliderect(actor.rect) and not cooldown:
 54                 rocket.location[0] = actor.rect2[0]
 55                 rocket.location[1] = abs(rocket.location[1]-actor.rect[1]) + actor.rect2[1]
 56                 cooldown = True
 57             elif rocket_rect.colliderect(actor.rect2) and not cooldown:
 58                 rocket.location = actor.location.copy()
 59                 cooldown = True
 60             elif not rocket_rect.colliderect(actor.rect) and not rocket_rect.colliderect(actor.rect2):
 61                 cooldown = False
 62         if rocket_rect.colliderect(actor.rect):
 63             if actor.is_target:
 64                 return 2
 65             elif not isinstance(actor, Portal):
 66                 return 1
 67         elif rocket.location[0] > 1400 or rocket.location[0] < -100 or rocket.location[1] > 800 or rocket.location[1] < -100:
 68             return 1
 69     return 0
 70 
 71 def start_menu():
 72     start_screen = Actor("ART/startmenu.png", [0, 0])
 73     start_button = Actor("ART/startbutton.png", [110,540])
 74     level_select = Actor("ART/levelselect.png", [220, 600])
 75     exit_button = Actor("ART/exitgame.png", [330,660])
 76     objects = [start_screen, start_button, level_select, exit_button]
 77     starting = True
 78     while starting:
 79         for event in pygame.event.get():
 80             if event.type == pygame.QUIT:
 81                 pygame.quit()
 82             if event.type == pygame.MOUSEBUTTONDOWN:
 83                 if start_button.image.get_rect(topleft=start_button.location).collidepoint(pygame.mouse.get_pos()):
 84                     return 0
 85                 if level_select.image.get_rect(topleft=level_select.location).collidepoint(pygame.mouse.get_pos()):
 86                     return select_menu()
 87                 if exit_button.image.get_rect(topleft=exit_button.location).collidepoint(pygame.mouse.get_pos()):
 88                     pygame.quit()
 89         update_window(objects)
 90 
 91 def select_menu():
 92     selecting = True
 93     level_select_screen = Actor("ART/levelselectscreen.png", [0, 0])
 94     back_button = Actor("ART/backbutton.png", [610, 650])
 95     objects = [level_select_screen, back_button]
 96 
 97     buttons = []
 98     texts = []
 99 
100     n = 0
101     for i in range(4):
102         for j in range(3):
103             buttons.append(Actor("ART/levelbutton.png", [130 + 420*j, 180 + 100*i]))
104             buttons[n].image = pygame.transform.scale(buttons[n].image, [180,40])
105             text_surface = FONT.render("LEVEL " + str(n+1), False, (255,255,255))
106             text = Text(text_surface, [130 + 420*j, 180 + 100*i])
107             texts.append(text)
108             n += 1
109 
110     objects.extend(buttons)
111     objects.extend(texts)
112             
113     while selecting:
114         for event in pygame.event.get():
115             if event.type == pygame.QUIT:
116                 pygame.quit()
117             if event.type == pygame.MOUSEBUTTONDOWN:
118                 if back_button.image.get_rect(topleft=back_button.location).collidepoint(pygame.mouse.get_pos()):
119                     selecting = False
120                     start_menu()
121                 for i in range(len(buttons)):
122                     if buttons[i].image.get_rect(topleft=buttons[i].location).collidepoint(pygame.mouse.get_pos()):
123                         return i
124 
125         update_window(objects)
126 
127 def end_screen():
128     end = Actor("ART/amogus.png", [0,0])
129     you_win = Actor("ART/youwin.png", [400,200])
130     end.image = pygame.transform.scale(end.image, window)
131     objects = [end, you_win]
132     ending = True
133     while ending:
134         for event in pygame.event.get():
135             if event.type == pygame.QUIT:
136                 pygame.quit()
137         update_window(objects)
138 
139 def success():
140     running = True
141     success = Actor("ART/success.png", [0, 100])
142     next_level_button = Actor("ART/nextlevel.png", [480, 550])
143     main_menu = Actor("ART/mainmenubutton.png", [460, 640])
144     objects = [success, next_level_button, main_menu]
145     while running:
146         for event in pygame.event.get():
147             if event.type == pygame.QUIT:
148                 pygame.quit()
149             if event.type == pygame.MOUSEBUTTONDOWN:
150                 if next_level_button.image.get_rect(topleft=next_level_button.location).collidepoint(pygame.mouse.get_pos()):
151                     return True
152                 elif main_menu.image.get_rect(topleft=main_menu.location).collidepoint(pygame.mouse.get_pos()):
153                     return False
154         update_window(objects)
155 
156 def failure():
157     running = True
158     failure = Actor("ART/failure.png", [0, 100])
159     retry = Actor("ART/retry.png", [480, 550])
160     main_menu = Actor("ART/mainmenubutton.png", [460, 640])
161     objects = [failure, retry, main_menu]
162     while running:
163         for event in pygame.event.get():
164             if event.type == pygame.QUIT:
165                 pygame.quit()
166             if event.type == pygame.MOUSEBUTTONDOWN:
167                 if retry.image.get_rect(topleft=retry.location).collidepoint(pygame.mouse.get_pos()):
168                     return True
169                 elif main_menu.image.get_rect(topleft=main_menu.location).collidepoint(pygame.mouse.get_pos()):
170                     return False
171         update_window(objects)
172 
173 def make_level(actors, background):
174     rocket = Player("ART/rocket.png", [0, 10], ["ART/rocket.png", "ART/rocket2.png", "ART/rocket3.png"])
175     ui = Actor("ART/ui.png", [0, 600])
176 
177     originals = []
178     moveables = []
179     for actor in actors:
180         new = copy.copy(actor)
181         if new.can_move:
182             new.image = pygame.transform.scale(new.image, UI_SIZE)
183             moveables.append(new)
184         else:
185             originals.append(new)
186 
187     objects = [background] + originals + [rocket] + [ui] + moveables
188 
189     launching = False
190     designing = True
191     has_selected = False
192     running = True
193     retry = False
194     next_level = False
195     while running:
196         for event in pygame.event.get():
197             if event.type == pygame.QUIT:
198                 pygame.quit()
199 
200             if event.type == pygame.MOUSEBUTTONDOWN:
201                 has_selected = True
202             elif event.type == pygame.MOUSEBUTTONUP:
203                 has_selected = False
204             if event.type == pygame.MOUSEMOTION and designing:
205                 mouse_pos = pygame.mouse.get_pos()
206 
207         if designing:
208             keys = pygame.key.get_pressed()
209             if keys[pygame.K_w]:
210                 rocket.location[1] -= VEL
211             elif keys[pygame.K_s]:
212                 rocket.location[1] += VEL
213             if keys[pygame.K_SPACE]:
214                 designing = False
215                 launching = True
216             if has_selected:
217                 for actor in (moveables):
218                     if actor.image.get_rect(topleft=actor.location).collidepoint(pygame.mouse.get_pos()) and actor.can_move:
219                         actor.has_gravity = True
220                         actor.image = pygame.transform.scale(actor.image, actor.original_size)
221                         actor.location = (mouse_pos[0] - actor.radius[0], mouse_pos[1] - actor.radius[1])
222                         actor.rect = pygame.Rect(actor.location[0], actor.location[1], actor.radius[0]*2, actor.radius[1]*2)
223 
224         if launching:
225             update_gravity(rocket, originals + moveables)
226             rocket.location[0] += rocket.velocity[0]
227             rocket.location[1] += rocket.velocity[1]
228 
229             check = check_collision(rocket, originals + moveables)
230 
231             if check == 1:
232                 objects.remove(rocket)
233                 update_window(objects)
234                 running = False
235                 retry = failure()
236             elif check == 2:
237                 next_level = success()
238                 running = False
239 
240         update_window(objects)
241     if retry:
242         return 0
243     if not retry and not next_level:
244         return 1
245     else:
246         return 2
247 
248 def main():
249     running = True
250     levels = []
251     for i in range(num_levels):
252         levels.append("level" + str(i+1))
253     while running:
254         clock = pygame.time.Clock()
255         clock.tick(FPS)
256 
257         level_data = {"level1": [[make_target([950, 250])], Actor("ART/spacebackground.jpg", [0, 0])],
258                       "level2": [[make_target([950, 100]), make_astroids([400, 450]), make_astroids([450,-100])], Actor("ART/spacebackground.jpg", [0, 0])],
259                       "level3": [
260                           [make_target([950, 300]), make_astroids([650,160]), make_redplanet([100, 610]), 
261                            make_redplanet([400, 610])], Actor("ART/spacebackground.jpg", [0, 0])],
262                       "level4": [
263                           [make_target([950,50]), make_astroids([300,0]), make_astroids([700, 400]), 
264                            make_redplanet([100, 610]), make_redplanet([400, 610])], Actor("ART/spacebackground.jpg", [0, 0])],
265                       "level5": [
266                           [make_target([950, 300]), make_portal([400,100],[800,500]), 
267                            make_astroids([500,0]), make_astroids([700,400])], Actor("ART/spacebackground.jpg", [0, 0])],
268                       "level6": [[make_target([200, 450]), make_astroids([100, 200]), make_astroids([110,400]), 
269                                   make_portal([800, 400],[500,50]), make_redplanet([100, 610]), make_redplanet([400, 610])], Actor("ART/spacebackground.jpg", [0, 0])]
270                       }
271 
272         pygame.mixer.music.load("MUSIC/menu.mp3")
273         pygame.mixer.music.play(-1)
274         i = start_menu()
275 
276         pygame.mixer.music.load("MUSIC/level.mp3")
277         pygame.mixer.music.play(-1)
278         while i < (len(levels)):
279             data = level_data.get(levels[i])
280             next = make_level(*data)
281             if next == 0:
282                 i -= 1
283             elif next == 1:
284                 break
285             i += 1
286             if next == 2 and i == num_levels:
287                 end_screen()
288     pygame.quit()
289 
290 
291 if __name__ == "__main__":
292     main()