-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathplay_objects.py
454 lines (403 loc) · 18.8 KB
/
play_objects.py
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
from base_objects import PlayObject
import pygame
import random
from utils import SCREEN_WIDTH, SCREEN_HEIGHT
class PlayWall(PlayObject):
wall_list = []
def __init__(self, pos, play_sprites, images):
super().__init__(pos, play_sprites, images["spr_wall"])
def add_to_class_list(self):
PlayWall.wall_list.append(self)
def remove_from_class_list(self):
PlayWall.wall_list.remove(self)
class PlayReverseWall(PlayObject):
reverse_wall_list = []
def __init__(self, pos, play_sprites, images):
# Assuming you need a transparent surface for reverse walls
image = pygame.Surface((24, 24), pygame.SRCALPHA) # Adjust dimensions as necessary
image.fill((0, 0, 0, 0)) # Completely transparent
super().__init__(pos, play_sprites, image)
def add_to_class_list(self):
PlayReverseWall.reverse_wall_list.append(self)
def remove_from_class_list(self):
PlayReverseWall.reverse_wall_list.remove(self)
class PlayFlyer(PlayObject):
flyer_list = []
SPEED = 1
WALL_BOUNDARY_THRESHOLD = 5 # Pixel overlap with wall
def __init__(self, pos, play_sprites, images):
super().__init__(pos, play_sprites, images["spr_flyer"])
self.images = images
self.left_speed = PlayFlyer.SPEED*-1
self.right_speed = PlayFlyer.SPEED
self.right_or_left = random.choice([self.left_speed, self.right_speed])
def update(self):
self.rect.topleft = (self.rect.topleft[0]+self.right_or_left, self.rect.topleft[1])
for wall in PlayWall.wall_list:
if self.rect.colliderect(wall.rect):
if (self.rect.bottom > wall.rect.top+PlayFlyer.WALL_BOUNDARY_THRESHOLD and self.rect.top < wall.rect.bottom-PlayFlyer.WALL_BOUNDARY_THRESHOLD):
self.right_or_left = self.right_or_left*-1
self.rect.topleft = (self.rect.topleft[0]+self.right_or_left, self.rect.topleft[1])
for reverse_wall in PlayReverseWall.reverse_wall_list:
if self.rect.colliderect(reverse_wall.rect):
if (self.rect.bottom > reverse_wall.rect.top+PlayFlyer.WALL_BOUNDARY_THRESHOLD and self.rect.top < reverse_wall.rect.bottom-PlayFlyer.WALL_BOUNDARY_THRESHOLD):
self.right_or_left = self.right_or_left*-1
self.rect.topleft = (self.rect.topleft[0]+self.right_or_left, self.rect.topleft[1])
for sticky_block in PlayStickyBlock.sticky_block_list:
if self.rect.colliderect(sticky_block.rect):
if (self.rect.bottom > sticky_block.rect.top+PlayFlyer.WALL_BOUNDARY_THRESHOLD and self.rect.top < sticky_block.rect.bottom-PlayFlyer.WALL_BOUNDARY_THRESHOLD):
self.right_or_left = self.right_or_left*-1
self.rect.topleft = (self.rect.topleft[0]+self.right_or_left, self.rect.topleft[1])
if self.rect.right > SCREEN_WIDTH or self.rect.left < 0:
self.right_or_left = self.right_or_left*-1
self.rect.topleft = (self.rect.topleft[0]+self.right_or_left, self.rect.topleft[1])
self.sprite_direction(self.images)
def add_to_class_list(self):
PlayFlyer.flyer_list.append(self)
def remove_from_class_list(self):
PlayFlyer.flyer_list.remove(self)
def sprite_direction(self, images):
if self.right_or_left == self.right_speed:
self.image = images["spr_flyer"]
elif self.right_or_left == self.left_speed:
self.image = pygame.transform.flip(images["spr_flyer"], 1, 0)
class PlayDiamonds(PlayObject):
diamonds_list = []
def __init__(self, pos, play_sprites, images):
super().__init__(pos, play_sprites, images["spr_diamonds"])
self.images = images
self.pos = pos
def restart(self):
self.rect.topleft = self.pos
self.image = self.images["spr_diamonds"]
def add_to_class_list(self):
PlayDiamonds.diamonds_list.append(self)
def remove_from_class_list(self):
PlayDiamonds.diamonds_list.remove(self)
class PlayDoor(PlayObject):
def __init__(self, pos, play_sprites, images):
super().__init__(pos, play_sprites, images["spr_door_closed"])
self.pos = pos
self.images = images
def open_or_close(self, score, diamonds_list):
if score == len(diamonds_list):
return self.images["spr_door_open"]
return self.images["spr_door_closed"]
def restart(self):
self.rect.topleft = self.pos
class PlaySmilyRobot(PlayObject):
smily_robot_list = []
SPEED = 2
GRAVITY = 0.25
OUT_OF_PLAY_TOPLEFT = (-100, SCREEN_HEIGHT+100)
def __init__(self, pos, play_sprites, images):
super().__init__(pos, play_sprites, images["spr_smily_robot"])
self.pos = pos
self.rect.topleft = self.pos
self.images = images
self.speed_x = self.SPEED * random.choice([-1, 1])
self.animatetimer = 0
self.speed_y = 0
def update(self):
# Apply horizontal and vertical movements
self.handle_movement()
# Check and handle horizontal collisions first
self.check_horizontal_collisions()
# Apply gravity before checking vertical collisions to ensure accurate collision detection
self.calc_grav()
# Then check and handle vertical collisions
self.check_vertical_collisions()
# Handle interactions with reverse walls, which might affect horizontal direction
self.handle_reverse_walls()
# Update the animation based on the smily robot's current state
self.animate()
def restart(self):
self.rect.topleft = self.pos
self.speed_x = self.SPEED * random.choice([-1, 1])
def handle_movement(self):
# Move horizontally with boundary checking
self.rect.x += self.speed_x
if self.rect.left <= 0 or self.rect.right >= SCREEN_WIDTH:
self.speed_x *= -1 # Change direction if hitting screen bounds
def check_horizontal_collisions(self):
# Predict the next horizontal position
next_position = self.rect.x + self.speed_x
self.rect.x = next_position
# Detect horizontal collisions
collided_blocks = pygame.sprite.spritecollide(self, PlayWall.wall_list + PlayStickyBlock.sticky_block_list, False)
if collided_blocks:
# If moving right but collided, place the robot to the left of the block
if self.speed_x > 0:
self.rect.right = min(block.rect.left for block in collided_blocks)
# If moving left but collided, place the robot to the right of the block
elif self.speed_x < 0:
self.rect.left = max(block.rect.right for block in collided_blocks)
# Reverse direction
self.speed_x *= -1
def check_vertical_collisions(self):
# Predict the next vertical position
next_position = self.rect.y + self.speed_y
self.rect.y = next_position
# Detect vertical collisions
collided_blocks = pygame.sprite.spritecollide(self, PlayWall.wall_list + PlayStickyBlock.sticky_block_list, False)
if collided_blocks:
# If falling down but collided, place the robot on top of the block
if self.speed_y > 0:
self.rect.bottom = min(block.rect.top for block in collided_blocks)
self.speed_y = 0 # Stop the fall
# If moving up but collided, place the robot below the block
elif self.speed_y < 0:
self.rect.top = max(block.rect.bottom for block in collided_blocks)
self.speed_y = 0 # Stop the ascent
def handle_reverse_walls(self):
# Detect collisions with reverse walls
collided_reverse_walls = pygame.sprite.spritecollide(self, PlayReverseWall.reverse_wall_list, False)
if collided_reverse_walls:
# Immediately reverse direction upon collision
self.speed_x *= -1
# Slight position adjustment to prevent repeated collisions
self.rect.x += self.speed_x
def calc_grav(self):
if self.rect.top < SCREEN_HEIGHT:
self.speed_y += self.GRAVITY
else:
self.speed_y = 0
self.rect.topleft = self.OUT_OF_PLAY_TOPLEFT
# Apply vertical movement
self.rect.y += self.speed_y
def animate(self):
# Animation logic remains the same
self.animatetimer += 1
if self.animatetimer > 5:
self.image = self.images["spr_smily_robot_2"]
if self.animatetimer > 10:
self.image = self.images["spr_smily_robot"]
self.animatetimer = 0
def add_to_class_list(self):
PlaySmilyRobot.smily_robot_list.append(self)
def remove_from_class_list(self):
PlaySmilyRobot.smily_robot_list.remove(self)
class PlayStickyBlock(PlayObject):
sticky_block_list = []
def __init__(self, pos, play_sprites, images):
super().__init__(pos, play_sprites, images["spr_sticky_block"])
self.pos = pos
self.rect.topleft = self.pos
def add_to_class_list(self):
PlayStickyBlock.sticky_block_list.append(self)
def remove_from_class_list(self):
PlayStickyBlock.sticky_block_list.remove(self)
class PlayStandSpikes(PlayObject):
stand_spikes_list = []
def __init__(self, pos, play_sprites, images, rotate=0):
super().__init__(pos, play_sprites, images["spr_stand_spikes_0_degrees"])
self.images = images
self.rotate = rotate
if self.rotate == 0:
self.image = self.images["spr_stand_spikes_0_degrees"]
elif self.rotate == 90:
self.image = self.images["spr_stand_spikes_90_degrees"]
elif self.rotate == 180:
self.image = self.images["spr_stand_spikes_180_degrees"]
elif self.rotate == 270:
self.image = self.images["spr_stand_spikes_270_degrees"]
self.pos = pos
self.fall_var = 0
def restart(self):
self.rect.topleft = self.pos
def add_to_class_list(self):
PlayStandSpikes.stand_spikes_list.append(self)
def remove_from_class_list(self):
PlayStandSpikes.stand_spikes_list.remove(self)
class PlayFallSpikes(PlayObject):
fall_spikes_list = []
def __init__(self, pos, play_sprites, images):
super().__init__(pos, play_sprites, images["spr_fall_spikes"])
self.pos = pos
self.fall_var = 0
def restart(self):
self.rect.topleft = self.pos
self.fall_var = 0
def add_to_class_list(self):
PlayFallSpikes.fall_spikes_list.append(self)
def remove_from_class_list(self):
PlayFallSpikes.fall_spikes_list.remove(self)
class PlaySpring(PlayObject):
spring_list = []
def __init__(self, pos, play_sprites, images):
super().__init__(pos, play_sprites, images["spr_spring"])
self.pos = pos
def restart(self):
self.rect.topleft = self.pos
def add_to_class_list(self):
PlaySpring.spring_list.append(self)
def remove_from_class_list(self):
PlaySpring.spring_list.remove(self)
class PlayPlayer(PlayObject):
# Constants for physics and gameplay adjustments
GRAVITY = 0.35
PROP_GRAVITY = 0.01
MAX_PROP_SPEED = -2
PROP_ACCELERATION = 0.25
JUMP_SPEED = -7
DOUBLE_JUMP_SPEED = -2
MOVE_SPEED = 4
def __init__(self, pos, play_sprites, images, sounds):
super().__init__(pos, play_sprites, images["spr_player"])
self.images = images
self.sounds = sounds
self.pos = pos
self.speed_x, self.speed_y = 0, 0
self.jumps_left = 2
self.propeller, self.playerproptimer = 0, 0
self.last_pressed_r = 1
self.score = 0
self.death_count = 0
self.wall_hit_list = []
self.stickyblock_hit_list = []
self.jump_counter = 0
def update(self):
self.calc_grav()
self.rect.x += self.speed_x
#####################
# WALLS
#####################
self.wall_hit_list = pygame.sprite.spritecollide(self, PlayWall.wall_list, False)
for wall in self.wall_hit_list:
if self.speed_x > 0: #player moves right and collides into wall
self.rect.right = wall.rect.left
elif self.speed_x < 0: #player moves left and collides into wall
self.rect.left = wall.rect.right
self.stickyblock_hit_list = pygame.sprite.spritecollide(self, PlayStickyBlock.sticky_block_list, False)
for stickyblock in self.stickyblock_hit_list:
if self.speed_x > 0: #player moves right and collides into wall
self.rect.right = stickyblock.rect.left
elif self.speed_x < 0: #player moves left and collides into wall
self.rect.left = stickyblock.rect.right
self.rect.y += self.speed_y
# Check and see if we hit anything
self.wall_hit_list = pygame.sprite.spritecollide(self, PlayWall.wall_list, False)
for wall in self.wall_hit_list:
# Reset our position based on the top/bottom of the object.
if self.speed_y > 0:
self.rect.bottom = wall.rect.top #On top of the wall
self.jumps_left = 2
elif self.speed_y < 0:
self.rect.top = wall.rect.bottom #Below the wall
# Stop our vertical movement
self.speed_y = 0
self.propeller = 0
self.stickyblock_hit_list = pygame.sprite.spritecollide(self, PlayStickyBlock.sticky_block_list, False)
for stickyblock in self.stickyblock_hit_list:
# Reset our position based on the top/bottom of the object.
if self.speed_y > 0:
self.rect.bottom = stickyblock.rect.top #On top
self.jumps_left = 2
elif self.speed_y < 0:
self.rect.top = stickyblock.rect.bottom #Below
# Stop our vertical movement
self.speed_y = 0
self.propeller = 0
self.animate_images()
def move_left(self):
self.speed_x = -self.MOVE_SPEED
self.last_pressed_r = 0 # Indicates moving left
def move_right(self):
self.speed_x = self.MOVE_SPEED
self.last_pressed_r = 1 # Indicates moving right
def stop(self):
self.speed_x = 0
def calc_grav(self):
if self.speed_y == 0:
self.speed_y = 1
elif self.propeller:
self.speed_y += self.PROP_GRAVITY
if self.speed_y >= self.MAX_PROP_SPEED:
self.sounds["snd_propeller"].play()
if self.speed_y > -1.5:
self.propeller = 0
self.speed_y += self.PROP_ACCELERATION
else:
self.sounds["snd_propeller"].stop()
self.speed_y += self.GRAVITY
def on_ground(self):
self.rect.y += 1 # Temporarily adjust position to check for collisions below
wall_hit_list = pygame.sprite.spritecollide(self, PlayWall.wall_list, False)
stickyblock_hit_list = pygame.sprite.spritecollide(self, PlayStickyBlock.sticky_block_list, False)
self.rect.y -= 1 # Reset position
on_ground = bool(wall_hit_list or stickyblock_hit_list)
on_sticky = bool(stickyblock_hit_list)
return on_ground, on_sticky
def jump(self):
on_ground, on_sticky = self.on_ground()
if on_ground and not on_sticky:
# Perform the first jump if on the ground and not on a sticky block.
self.speed_y = self.JUMP_SPEED
self.jumps_left = 1 # Set to allow for the propeller jump next.
self.animate_jump()
elif not on_ground and self.jumps_left >= 1:
# Allows for a propeller jump if in the air and haven't used the propeller jump yet.
self.speed_y = self.DOUBLE_JUMP_SPEED
self.propeller = 1
self.jumps_left = 0 # No more jumps left after this.
self.animate_jump()
def can_jump(self):
return self.jumps_left > 0
def perform_jump(self):
if self.wall_hit_list: # On a platform
self.speed_y = self.JUMP_SPEED
self.jumps_left -= 1
elif not self.wall_hit_list and self.jumps_left == 1: # Mid-air jump
self.speed_y = self.DOUBLE_JUMP_SPEED
self.propeller = 1
self.jumps_left = 0
def animate_images(self):
if self.propeller == 1:
self.playerproptimer += 1
if self.last_pressed_r == 1:
if self.playerproptimer > 1:
self.image = self.images["spr_player_propeller"]
if self.playerproptimer > 3:
self.image = self.images["spr_player"]
self.playerproptimer = 0
if self.last_pressed_r == 0:
if self.playerproptimer > 1:
self.image = pygame.transform.flip(self.images["spr_player_propeller"], 1, 0)
if self.playerproptimer > 3:
self.image = pygame.transform.flip(self.images["spr_player"], 1, 0)
self.playerproptimer = 0
else:
if self.last_pressed_r == 1:
self.image = self.images["spr_player"]
else:
self.image = pygame.transform.flip(self.images["spr_player"], 1, 0)
def animate_jump(self):
# Check if the propeller is to be used, indicating a second jump in a double jump
if self.propeller:
# Reset or initialize the propeller animation timer
self.playerproptimer = 0
# Play the propeller sound if it exists
if "snd_propeller" in self.sounds:
self.sounds["snd_propeller"].play()
# The actual propeller animation logic will continue to be handled by `animate_images`
# so no additional logic is needed here to change the image directly.
else:
# For a regular jump (first jump in the double jump sequence or a single jump),
# you might want to reset the player image or set any initial jump animation
# This is more relevant if you have a specific animation for the start of a jump.
self.image = self.images["spr_player"]
# If there's a jump sound, you could play it here as well
if "snd_jump" in self.sounds:
self.sounds["snd_jump"].play()
# Note: The continuous animation of the propeller (if applicable) and resetting back to the normal
# state after the propeller animation ends are handled within the `animate_images` method,
# which should be called in your game loop's update phase.
def restart(self):
# Game Reset
self.jumps_left = 2
self.speed_y = 0
self.propeller = 0
self.last_pressed_r = 1
self.score = 0
self.death_count += 1
self.rect.topleft = self.pos