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()