Return to repo list

gatemender

Arcade game about fixing warp gates. LibreJam Dec 2020 Entry.
Return to HMagellan.com

sprites.py (18402B)


      1 import pygame, math, random
      2 from .constants import *
      3 
      4 class CustomSprite(pygame.sprite.Sprite):
      5     def __init__(self, manager, image, pos, angle = 0):
      6         
      7         # Constructor
      8         pygame.sprite.Sprite.__init__(self)
      9 
     10         # Values
     11         self.manager = manager
     12         self.image = image
     13         self.base_image = image
     14         self.rect = self.image.get_rect()
     15         self.rect.center = pos
     16         self.position = pygame.Vector2(pos)
     17         self.angle = angle
     18         if angle != 0:
     19             self.rotate(angle)
     20         self.visible = True
     21 
     22     def rotate(self, angle):
     23         self.angle = angle
     24         self.image = pygame.transform.rotate(self.base_image, angle).convert()
     25         self.rect = self.image.get_rect(center = self.rect.center)
     26 
     27     def offset_move(self, offset):
     28         self.rect.center = (self.rect.center[0] + offset[0], self.rect.center[1] + offset[1])
     29 
     30     def direction_move(self, speed):
     31         rad = math.radians(self.angle + DEGREE_CORRECTION)
     32         self.position += pygame.Vector2(math.sin(rad), math.cos(rad)) * speed
     33         self.rect.center = tuple(self.position)
     34 
     35     def act(self):
     36         pass
     37 
     38     def update(self, surface = None):
     39         self.act()
     40         if surface != None and self.visible:
     41             surface.blit(self.image, self.rect)
     42 
     43 class Ship(CustomSprite):
     44 
     45     def __init__(self, manager, image, pos, angle = 0):
     46         super().__init__(manager, image, pos, angle)
     47         self.speed = 0
     48         self.thrust_factor = 0.04
     49         self.shoot_delay = 10
     50         self.shoot_cooldown = 0
     51         
     52         # Bools
     53         self.shooting = False
     54         self.thrusting = False
     55         self.braking = False
     56         self.turning_left = False
     57         self.turning_right = False
     58         self.fixing = False
     59         self.blinking = False
     60         self.bursting = False
     61         self.dead = False
     62 
     63         # Other
     64         self.hp = 3
     65         self.hitbox = pygame.Rect((0, 0), (self.rect.width // 4, self.rect.height // 4))
     66         self.blinking_timer_max = 120
     67         self.blinking_timer = self.blinking_timer_max
     68         self.aura = FixAura(self.manager, self.manager.images["Fix_Aura"], self.rect.center, 0, self)
     69         self.manager.camera_draw_group.add(self.aura)
     70         self.materials = { m : 0 for m in MATERIALS }
     71         self.be_dead_timer = 60
     72         self.burst_counter = 100
     73         self.burst_delay = 2 + self.manager.level_factor
     74         self.thrust_sprite = ThrustSprite(self.manager, self.manager.images["Ship_Fire"], self.rect.center, 0, self)
     75         self.prev_pos = [ self.rect.center for i in range(0, 6) ]
     76 
     77     def thrust(self):
     78         self.manager.play_sound("Thrust", 2, False, 0, False)
     79         if self.speed < SHIP_MAX_SPEED:
     80             self.speed += self.thrust_factor
     81             
     82     def decelerate(self, amount = 1):
     83         self.speed -= amount
     84         if self.speed < 0:
     85             self.speed = 0
     86 
     87     def turn(self, amount):
     88         self.rotate(self.angle + amount)
     89 
     90     def shoot(self):
     91         b = Bullet(self.manager, self.manager.images["Bullet"], self.rect.center, self.angle)
     92         self.manager.camera_draw_group.add(b)
     93         self.manager.bullets.add(b)
     94         self.manager.play_sound("Shoot", 1, True, 0, False)
     95     
     96     def burst(self):
     97         self.manager.play_sound("Flash", 3, True, 0, False)
     98         for a in self.manager.asteroids:
     99             a.destroy()
    100         self.manager.asteroid_spawn_timer = 100
    101         self.burst_counter = 0
    102 
    103     def act(self):
    104 
    105         # If alive
    106         if not self.dead:
    107             # Syncing
    108             self.hitbox.center = self.rect.center
    109 
    110             # Turning
    111             if self.turning_left:
    112                 self.turn(4)
    113             if self.turning_right:
    114                 self.turn(-4)
    115 
    116             #Thrusting / Braking / Slowing
    117             self.thrust_sprite.rect.center = self.rect.center
    118             if self.thrusting:
    119                 self.thrust()
    120                 self.prev_pos.pop(5)
    121                 self.prev_pos.insert(0, self.rect.center)
    122                 if self.speed > 2:
    123                     self.thrust_sprite.visible = True
    124                     self.thrust_sprite.rect.center = self.prev_pos[int(self.speed) // 2]
    125             else:
    126                 if self.braking:
    127                     self.decelerate(5 * self.thrust_factor)
    128                 elif self.speed > 0:
    129                     self.decelerate(2 * self.thrust_factor)
    130                 self.prev_pos = [ self.rect.center for i in range(0, 6) ]
    131                 self.thrust_sprite.visible = False
    132             self.direction_move(self.speed)
    133 
    134             #Shooting
    135             if self.shooting:
    136                 if self.shoot_cooldown <= 0:
    137                     self.shoot()
    138                     self.shoot_cooldown += self.shoot_delay
    139                 else:
    140                     self.shoot_cooldown -= 1
    141 
    142             # Fixing
    143             if self.fixing:
    144                 self.aura.visible = True
    145                 self.manager.play_sound("Fix", 2, False, 0, False)
    146                 for g in self.manager.gate_pieces.sprites():
    147                     if self.rect.colliderect(g.rect) and g.broken and self.materials[g.requirement] > 0:
    148                         self.fix_counter += 1
    149                         if self.fix_counter > 100:
    150                             g.be_fixed()
    151                             self.fix_counter = 0
    152                             self.materials[g.requirement] -= 1
    153                         break
    154             else:
    155                 self.fix_counter = 0
    156                 self.aura.visible = False
    157 
    158             # Check for wrapping
    159             if not self.rect.colliderect(self.manager.camera_surface_rect):
    160                 x = self.rect.centerx
    161                 y = self.rect.centery
    162                 if self.rect.centerx > self.manager.camera_surface_rect.width:
    163                     x = 0 - (self.rect.width / 2)
    164                 elif self.rect.centerx < 0:
    165                     x = self.manager.camera_surface_rect.width + (self.rect.width / 2)
    166                 if self.rect.centery > self.manager.camera_surface_rect.height:
    167                     y = 0 - (self.rect.height / 2)
    168                 elif self.rect.centery < 0:
    169                     y = self.manager.camera_surface_rect.height + (self.rect.height / 2)
    170                 self.rect.center = (x, y)
    171                 self.position = pygame.Vector2(self.rect.center)
    172 
    173             # Burst
    174             if self.burst_counter < 100:
    175                 if self.burst_delay > 0:
    176                     self.burst_delay -= 1
    177                 else:
    178                     self.burst_delay = 2 + self.manager.level_factor
    179                     self.burst_counter += 1
    180             elif self.bursting:
    181                 self.burst()
    182                 self.manager.camera_surface.fill((255, 255, 255))
    183 
    184             # Damage blink and asteroid col
    185             if self.blinking:
    186                 if (self.blinking_timer // 10) % 2 != 0:
    187                     self.thrust_sprite.visible = False
    188                     self.visible = False
    189                 else:
    190                     self.visible = True
    191                 if self.blinking_timer > 0:
    192                     self.blinking_timer -= 1
    193                 else:
    194                     self.blinking = False
    195                     self.blinking_timer = self.blinking_timer_max
    196 
    197             else:
    198                 for s in self.manager.asteroids.sprites():
    199                     if self.hitbox.colliderect(s.rect):
    200                         s.destroy()
    201                         self.hp -= 1
    202                         self.manager.play_sound("Damage", 0, True, 0, False)
    203                         if self.hp > 0:
    204                             self.blinking = True
    205                         else:
    206                             self.dead = True
    207 
    208         # If dead
    209         else:
    210             self.thrust_sprite.visible = False
    211             self.visible = False
    212             self.manager.preserve_gate = True
    213             if self.be_dead_timer > 0:
    214                 self.be_dead_timer -= 1
    215             else:
    216                 self.manager.lives -= 1
    217                 if self.manager.lives >= 0:
    218                     self.manager.change_mode(MODES.Transition)
    219                 else:
    220                     self.manager.change_mode(MODES.GameOver)
    221         
    222     def update(self, surface = None):
    223         self.thrust_sprite.update(surface)
    224         super().update(surface)
    225                 
    226 # Bullet class
    227 class Bullet(CustomSprite):
    228 
    229     def check_collision(self):
    230         for s in self.manager.asteroids.sprites():
    231             if self.rect.colliderect(s.rect):
    232                 s.be_damaged()
    233                 self.kill()
    234 
    235     def act(self):
    236         self.direction_move(BULLET_SPEED)
    237         self.check_collision()
    238         if not self.rect.colliderect(self.manager.screen_rect):
    239             self.kill()
    240 
    241 # Gate components
    242 class GatePiece(CustomSprite):
    243 
    244     def __init__(self, manager, image, pos, angle = 0, or_name = "Straight", broken = False, req = None, needed = None):
    245         super().__init__(manager, image, pos, angle)
    246         self.broken = broken
    247         self.requirement = req
    248         self.or_name = or_name
    249         self.needed = needed
    250         self.needed_text = None
    251         self.needed_text_rect = None
    252         self.halo_image = None
    253         self.halo_rect = None
    254         if self.broken:
    255             self.halo_image = pygame.transform.rotate(self.manager.images[self.requirement.name + "_Halo_" + or_name], self.angle)
    256             self.halo_rect = self.halo_image.get_rect()
    257             self.halo_rect.center = self.rect.center
    258             if needed == None:
    259                 self.needed = random.randint(1, self.manager.level_factor)
    260             self.needed_text = self.manager.font.render(str(self.needed), False, (255, 255, 255))
    261             self.needed_text_rect = self.needed_text.get_rect()
    262             self.needed_text_rect.center = self.rect.center
    263             if or_name == "Straight": 
    264                 if angle == 0:
    265                     self.needed_text_rect.center = (self.needed_text_rect.centerx, self.needed_text_rect.centery - 28)
    266                 elif angle == 90:
    267                     self.needed_text_rect.center = (self.needed_text_rect.centerx - 28, self.needed_text_rect.centery)
    268                 elif angle == 180:
    269                     self.needed_text_rect.center = (self.needed_text_rect.centerx, self.needed_text_rect.centery + 28)
    270                 elif angle == 270:
    271                     self.needed_text_rect.center = (self.needed_text_rect.centerx + 28, self.needed_text_rect.centery)
    272 
    273     def be_fixed(self):
    274         self.needed -= 1
    275         if self.needed > 0:
    276             rc = self.needed_text_rect.center
    277             self.needed_text = self.manager.font.render(str(self.needed), False, (255, 255, 255))
    278             self.needed_text_rect = self.needed_text.get_rect()
    279             self.needed_text_rect.center = rc
    280             self.manager.play_sound("Chime", 0, True, 0, False)
    281         else:
    282             self.broken = False
    283             self.manager.play_sound("Repair", 0, True, 0, False)
    284             self.halo_image = None
    285             self.halo_rect = None
    286             self.image = self.manager.images["Gate_" + self.or_name + "_Fixed"]
    287             self.base_image = self.image
    288             self.rotate(self.angle)
    289 
    290     def update(self, surface = None):
    291         if self.halo_image != None:
    292             surface.blit(self.halo_image, self.halo_rect)
    293         super().update(surface)
    294         if self.broken:
    295             surface.blit(self.needed_text, self.needed_text_rect)
    296 
    297 # Asteroids
    298 class Asteroid(CustomSprite):
    299 
    300     def __init__(self, manager, image, pos, angle = 0, size = 0):
    301         super().__init__(manager, image, pos, angle)
    302         self.size = size # 0 = small; 1 = medium; 2 = large
    303         self.hp = size + 1
    304         self.spin_speed = random.randint(-7, 7)
    305         self.speed = random.randint(1, manager.level_factor + 1)
    306         self.entered_screen = False
    307         self.contents = random.randint(0, 18)
    308 
    309     def be_damaged(self):
    310         self.hp -= 1
    311         if self.hp <= 0:
    312             self.manager.play_sound("Explode", 0, True, 0, False)
    313             self.spawn_pickup()
    314             self.destroy()
    315             self.manager.score += 10 * self.manager.level_factor
    316     
    317     def spawn_pickup(self):
    318         if self.contents in (0, 1):
    319             p = Pickup(self.manager, self.manager.images["Alpha_Icon"], self.rect.center, 0, MATERIALS.Alpha)
    320             self.manager.pickups.add(p)
    321             self.manager.camera_draw_group.add(p)
    322             self.manager.play_sound("Appear", 3, True, 0, False)
    323         elif self.contents in (2, 3):
    324             p = Pickup(self.manager, self.manager.images["Beta_Icon"], self.rect.center, 0, MATERIALS.Beta)
    325             self.manager.pickups.add(p)
    326             self.manager.camera_draw_group.add(p)
    327             self.manager.play_sound("Appear", 3, True, 0, False)
    328         elif self.contents in (4, 5):
    329             p = Pickup(self.manager, self.manager.images["Gamma_Icon"], self.rect.center, 0, MATERIALS.Gamma)
    330             self.manager.pickups.add(p)
    331             self.manager.camera_draw_group.add(p)
    332             self.manager.play_sound("Appear", 3, True, 0, False)
    333         elif self.contents == 17:
    334             p = RepairPickup(self.manager, self.manager.images["Repair_Pickup"], self.rect.center, 0)
    335             self.manager.pickups.add(p)
    336             self.manager.camera_draw_group.add(p)
    337             self.manager.play_sound("Appear", 3, True, 0, False)
    338 
    339     def destroy(self):
    340         x = -1
    341         y = -1
    342         for p in range(0, 4):
    343             self.manager.camera_draw_group.add(AsteroidParticle(self.manager, self.manager.images["Asteroid_Particle"], self.rect.center, 0, (x, y)))
    344             y += 1
    345             if y % 2 != 0:
    346                 x += 2
    347         self.kill()
    348 
    349     def act(self):
    350         self.offset_move((0, self.speed))
    351         self.rotate(self.angle + self.spin_speed)
    352         if self.rect.colliderect(self.manager.screen_rect):
    353             self.entered_screen = True
    354         elif self.entered_screen == True:
    355             self.kill()
    356 
    357 class AsteroidParticle(CustomSprite):
    358 
    359     def __init__(self, manager, image, pos, angle = 0, offset = (1, 1)):
    360         super().__init__(manager, image, pos, angle)
    361         self.spin_speed = random.randint(-7, 7)
    362         self.offset = (offset[0] * random.randint(1, 4), offset[1] * random.randint(1, 4))
    363 
    364     def act(self):
    365         self.offset_move(self.offset)
    366         self.rotate(self.angle + self.spin_speed)
    367         if not self.rect.colliderect(self.manager.screen_rect):
    368             self.kill()
    369 
    370 class FixAura(CustomSprite):
    371 
    372     def __init__(self, manager, image, pos, angle = 0, anchor = None):
    373         super().__init__(manager, image, pos, angle)
    374         self.anchor = anchor
    375 
    376     def act(self):
    377         self.rect.center = self.anchor.rect.center
    378         self.rotate(self.angle + 5)
    379 
    380 class Pickup(CustomSprite):
    381 
    382     def __init__(self, manager, image, pos, angle = 0, material = MATERIALS.Alpha):
    383         super().__init__(manager, image, pos, angle)
    384         self.material = material
    385         self.lifetime = 1000 // max(1, self.manager.level_factor // 2)
    386 
    387     def act(self):
    388         self.rotate(self.angle + 1)
    389         if self.lifetime > 0:
    390             self.lifetime -= 1
    391             if self.lifetime < 180:
    392                 if self.lifetime % 2 == 0 or self.lifetime % 3 == 0:
    393                     self.visible = False
    394                 else:
    395                     self.visible = True
    396         else:
    397             self.kill()
    398         if self.rect.colliderect(self.manager.ship.rect):
    399             self.manager.play_sound("Pickup", 3, True, 0, False)
    400             self.manager.ship.materials[self.material] += 1
    401             self.kill()
    402 
    403 class RepairPickup(CustomSprite):
    404     
    405     def __init__(self, manager, image, pos, angle = 0):
    406         super().__init__(manager, image, pos, angle)
    407         self.lifetime = 1000 // max(1, self.manager.level_factor // 2)
    408 
    409     def act(self):
    410         self.rotate(self.angle + 1)
    411         if self.lifetime > 0:
    412             self.lifetime -= 1
    413             if self.lifetime < 180:
    414                 if self.lifetime % 2 == 0 or self.lifetime % 3 == 0:
    415                     self.visible = False
    416                 else:
    417                     self.visible = True
    418         else:
    419             self.kill()
    420         if self.rect.colliderect(self.manager.ship.rect):
    421             self.manager.play_sound("Pickup", 3, True, 0, False)
    422             if self.manager.ship.hp < 3:
    423                 self.manager.ship.hp += 1
    424             self.kill()
    425 
    426 class Vortex(CustomSprite):
    427 
    428     def __init__(self, manager, image, pos, angle = 0):
    429         super().__init__(manager, image, pos, angle)
    430         self.visible = False
    431         self.passthru = False
    432         self.passrect = pygame.Rect((0, 0), (self.rect.width // 3, self.rect.height // 3))
    433         self.passrect.center = self.rect.center
    434     
    435     def act(self):
    436         self.rotate(self.angle + 3)
    437         if self.passthru and self.passrect.colliderect(self.manager.ship.rect):
    438             self.manager.increment_level()
    439 
    440 class Checkbox(CustomSprite):
    441 
    442     def __init__(self, manager, image, pos, angle = 0, checked = False):
    443         super().__init__(manager, image, pos, angle)
    444         self.checked = checked
    445         self.checked_image = manager.images["Checked_Checkbox"]
    446         self.unchecked_image = manager.images["Empty_Checkbox"]
    447         self.check_cooldown = 0
    448         if checked:
    449             self.image = self.checked_image
    450         else:
    451             self.image = self.unchecked_image
    452 
    453     def be_checked(self):
    454         if self.check_cooldown == 0:
    455             self.manager.play_sound("Click", 1, True, 0, False)
    456             if self.checked:
    457                 self.checked = False
    458                 self.image = self.unchecked_image
    459             else:
    460                 self.checked = True
    461                 self.image = self.checked_image
    462             self.check_cooldown = 40
    463 
    464     def act(self):
    465         if self.check_cooldown > 0:
    466             self.check_cooldown -= 1
    467 
    468 class ThrustSprite(CustomSprite):
    469 
    470     def __init__(self, manager, image, pos, angle = 0, ship = None):
    471         super().__init__(manager, image, pos, angle)
    472         self.ship = ship
    473         self.flip_timer = 10
    474 
    475     def act(self):
    476         if self.visible:
    477             if self.flip_timer > 0:
    478                 self.flip_timer -= 1
    479             else:
    480                 self.base_image = pygame.transform.flip(self.base_image, True, False)
    481                 self.flip_timer = 10
    482             self.rotate(self.ship.angle)