def setup_phase3(self): ml.enemy_bullet_group.empty() self.minion1.kill() self.minion1part.kill() self.circle_angle = 90 self.circle_angle_change = 0.8 self.minion_angle = 0 self.phase3miniontimer1 = ml.time() + self.phase_change_delay self.phase3miniontimer2 = ml.time() + self.phase_change_delay + 0.3 # Minion 2 self.minion2 = BossPart('mini_ring.png') self.minion2part = BossPart('mini_ring_purple.png') self.minion2.x, self.minion2.y = self.rect.centerx - 100, self.rect.centery self.minion2.move((self.minion2.x, self.minion2.y)) self.minion2part.move((self.minion2.x, self.minion2.y)) self.phase3minionbd1.parent = self.minion2 # Minion 3 self.minion3 = BossPart('mini_ring.png') self.minion3part = BossPart('mini_ring_purple.png') self.minion3.x, self.minion3.y = self.rect.centerx + 100, self.rect.centery self.minion3.move((self.minion3.x, self.minion3.y)) self.minion3part.move((self.minion3.x, self.minion3.y)) self.phase3minionbd2.parent = self.minion3 self.spawn_powerups(base_powerup_spawn * 6)
def phase1(self): self.move_speed = 2 # After y is lined up, turn toward player and starting moving on the y axis if self.moving_x and \ abs(self.x - ml.player.rect.centerx) <= \ self.move_speed + float(ml.get_upgrade_values('Movement Speed')): self.moving_x = False self.moving_y = True _ = self.shoot(-999, self.circle_bd, self.circle_fd) # After x is lined up, turn toward player and starting moving on the x axis if self.moving_y and \ abs(self.y - ml.player.rect.centery) <= \ self.move_speed + float(ml.get_upgrade_values('Movement Speed')): self.moving_x = True self.moving_y = False _ = self.shoot(-999, self.circle_bd, self.circle_fd) # Move until self.x lines up with player's x if self.rect.centerx != ml.player.rect.centerx and self.moving_x: self.x, self.y, self.current_angle = ml.move_point( self, (ml.player.rect.centerx, self.rect.centery), self.move_speed, 0, 360) else: self.moving_x = False self.moving_y = True # Move until self.y lines up with player's y if self.rect.centery != ml.player.rect.centery and self.moving_y: self.x, self.y, self.current_angle = ml.move_point( self, (self.rect.centerx, ml.player.rect.centery), self.move_speed, 0, 360) else: self.moving_x = True self.moving_y = False
def phase2(self): # Move boss and shoot self.x, self.y, _ = ml.move_point(self, ml.player.rect.center, 0.7, 0, 360) self.phase2timer = self.shoot(self.phase2timer, self.phase2bd, self.phase2fd) # Control minion self.minion1.x, self.minion1.y, self.minion1.current_angle =\ ml.move_point(self.minion1, ml.player.rect.center, 7, self.minion1.current_angle, 0.8) self.minion1part.animate(angle_change=15) self.minion1part.x, self.minion1part.y = self.minion1.x, self.minion1.y self.phase2minion_timer = self.shoot(self.phase2minion_timer, self.phase2minionbd, self.phase2minionfd) # Bounce off walls if self.minion1.rect.left < 0: self.minion1.rect.left = 0 self.minion1.x = self.minion1.rect.centerx self.minion1.current_angle = 180 - self.minion1.current_angle elif self.minion1.rect.right > ml.window_width: self.minion1.rect.right = ml.window_width self.minion1.x = self.minion1.rect.centerx self.minion1.current_angle = 180 - self.minion1.current_angle elif self.minion1.rect.top < 0: self.minion1.rect.top = 0 self.minion1.y = self.minion1.rect.centery self.minion1.current_angle = 360 - self.minion1.current_angle elif self.minion1.rect.bottom > ml.window_height: self.minion1.rect.bottom = ml.window_height self.minion1.y = self.minion1.rect.centery self.minion1.current_angle = 360 - self.minion1.current_angle self.minion1.current_angle = ml.normalize_angle( self.minion1.current_angle)
def __init__(self, x, y, speed, damage, angle, homing_shots) -> pygame.sprite.Sprite: pygame.sprite.Sprite.__init__(self) self.move_speed = ml.normalize_target_fps(float(speed)) self.damage = damage self.current_angle = ml.normalize_angle(angle) self.angle_r = math.radians(angle) self.turning_rate = ml.normalize_target_fps(3.5) self.homing = homing_shots # Set up image, rect, and mask if self.homing: self.image = ml.get_bullet_image('homing') else: self.image = ml.get_bullet_image('player') self.width = self.image.get_width() self.height = self.image.get_height() self.original_image = self.image # self.image = pygame.transform.rotate(self.image, angle) self.x = x self.y = y self.rect = pygame.Rect(0, 0, self.width, self.height) self.rect.center = self.x, self.y self.mask = pygame.mask.from_surface(self.image) # Add sprite to group self.add(ml.player_bullet_group)
def options(): """Shows the options menu.""" global mouse_movement_active x_margin = 150 mouse_button_rect = pygame.Rect( window.get_width() - x_margin - button_width, (window.get_height() / 2) - 50, button_width, button_height) keyboard_button_rect = pygame.Rect(x_margin, (window.get_height() / 2) - 50, button_width, button_height) back_button = ml.get_button_surface(button_width, button_height, 'Back', button_font_size, WHITE) back_button_rect = pygame.Rect(x_margin, window.get_height() - 200, button_width, button_height) while True: # Event handling loop for event in pygame.event.get(): if event.type == QUIT: terminate() # Escape closes the highscores screen elif event.type == KEYDOWN: if event.key == K_ESCAPE: return # Player clicked keyboard button elif event.type == MOUSEBUTTONUP: # noinspection PyArgumentList if keyboard_button_rect.collidepoint(event.pos): mouse_movement_active = False elif mouse_button_rect.collidepoint(event.pos): mouse_movement_active = True elif back_button_rect.collidepoint(event.pos): return window.fill(BLACK) # Change button color to indicate which is active if mouse_movement_active: mouse_color = WHITE keyboard_color = GRAY else: keyboard_color = WHITE mouse_color = GRAY mouse_button = ml.get_button_surface(button_width, button_height, 'Mouse', button_font_size, mouse_color) keyboard_button = ml.get_button_surface(button_width, button_height, 'Keyboard', button_font_size, keyboard_color) window.blit(keyboard_button, keyboard_button_rect) window.blit(mouse_button, mouse_button_rect) window.blit(back_button, back_button_rect) draw_cursor() pygame.display.update() clock.tick(FPS)
def reset_duration(self): if not ml.time() - self.spawn_time > self.duration: self.duration = ml.get_upgrade_values('Shield Duration') self.spawn_tick = ml.ticks self.bar_stage_duration = (ml.get_upgrade_values('Shield Duration') * ml.framerate) / len(self.bar_stage_images) self.ticks_elapsed = 0 self.spawn_time = ml.time()
def highscores(): """Show the highscores screen""" x_margin = 150 top_scores = ml.get_highscores()[:8] pygame.event.clear() window.fill((0, 0, 0)) score_font = pygame.font.Font(ml.font, 28) high_scores_title_font = pygame.font.Font(ml.font, 48) score_name_surfaces = [] score_score_surfaces = [] # Score display for score in top_scores: score_name_surfaces.append( score_font.render('%s:' % score[0], True, WHITE)) for score in top_scores: score_score_surfaces.append( score_font.render(str(score[1]), True, WHITE)) high_scores_title = high_scores_title_font.render('High Scores', True, WHITE) # Back button back_button = ml.get_button_surface(button_width, button_height, 'Back', button_font_size, WHITE) back_button_rect = pygame.Rect(x_margin, window.get_height() - 200, button_width, button_height) while True: # Event handling loop for event in pygame.event.get(): if event.type == QUIT: terminate() # Escape closes the highscores screen elif event.type == KEYDOWN: if event.key == K_ESCAPE: return # Player clicked Back button elif event.type == MOUSEBUTTONUP: # noinspection PyArgumentList if back_button_rect.collidepoint(event.pos): return window.fill(BLACK) # Draw the scores for i, surface in enumerate(score_name_surfaces): window.blit(surface, (x_margin, 125 + (button_height + 15) * i)) for i, surface in enumerate(score_score_surfaces): window.blit(score_score_surfaces[i], (window.get_width() - x_margin - surface.get_width(), 125 + (button_height + 15) * i)) window.blit(back_button, back_button_rect) window.blit(high_scores_title, ((window.get_width() / 2) - (high_scores_title.get_width() / 2), 30)) draw_cursor() pygame.display.update() clock.tick(FPS)
def phase2(self): if ml.time() - self.spiral_timer >= self.spiral_fd.firing_speed: self.spiral_bd.turning_rate *= -1 self.rotation_rate = ml.normalize_target_fps(1.5) self.spiral_timer = self.shoot(self.spiral_timer, self.spiral_bd, self.spiral_fd) self.explode_shot_timer = self.shoot(self.explode_shot_timer, self.explode_shot_bd, self.explode_shot_fd)
def spawn_random(start_point): if ml.time() - PowerUp.spawn_timer > ml.get_upgrade_values('Powerup Interval'): PowerUp.spawn_timer = ml.time() powerup = random.randint(0, 4) if powerup == 0: HomingShots(start_point, start_point) elif powerup == 1: Shield(start_point, start_point) elif powerup == 2: Heal(start_point, start_point) elif powerup == 3: Bomb(start_point, start_point)
def move(self): distance = ml.distance_to_point(self.rect.center, ml.player.rect.center) if distance < self.homing_distance: angle = ml.angle_to_point(self.rect.center, ml.player.rect.center) self.x, self.y, _ = ml.move_point(self, ml.player.rect.center, self.homing_speed, angle, 5) elif self.moving_from_spawn: if self.x == self.target_point[0] and self.y == self.target_point[1]: self.moving_from_spawn = False else: self.x, self.y, _ = ml.move_point(self, self.target_point, self.from_spawn_speed, 0, 360) else: self.y += self.falling_speed
def animate(self): start_rect_center = self.rect.center self.current_angle = ml.normalize_angle(self.current_angle) if ml.time() - self.phase_change_time < self.phase_change_delay: self.invincible = True self.image = pygame.transform.rotate(self.boss_invisible_image, self.current_angle) else: self.invincible = False self.image = pygame.transform.rotate(self.image_original, self.current_angle) self.rect = self.image.get_rect() self.rect.center = start_rect_center
def phase1(self): self.circle_timer = self.shoot(self.circle_timer, self.circle_bd, self.circle_fd) if ml.time() - self.ring_timer >= 1 / self.ring_firing_speed: self.ring_fd.angle += 10 self.ring_timer = self.shoot(self.ring_timer, self.ring_bd, self.ring_fd)
def __init__(self) -> pygame.sprite.Sprite: pygame.sprite.Sprite.__init__(self) self.spawn_time = ml.time() self.spawn_tick = ml.ticks self.duration = ml.get_upgrade_values('Shield Duration') self.image_path = os.path.join('graphics', 'shield.png') self.image = pygame.image.load(self.image_path) self.image_original = pygame.image.load(self.image_path) self.shield_mask = pygame.mask.from_surface(self.image) self.rect = self.image.get_rect() self.add(ml.powerup_effect_group) # Animation self.animation_duration = 0.2 self.duration_ticks = ml.get_upgrade_values('Shield Duration') * ml.framerate # 1 animation stage per frame self.animation_stages = int(ml.framerate * self.animation_duration) self.stage_duration = self.animation_duration / self.animation_stages self.stage_width = self.image.get_width() / self.animation_stages self.stage_height = self.image.get_height() / self.animation_stages self.stage_images = [] for count in range(self.animation_stages): self.stage_images.append(pygame.transform.smoothscale(self.image_original, (int(self.stage_width * count), int(self.stage_height * count)))) self.stage = 0 self.ticks_elapsed = 0 # Set up bar self.bar_image_path = os.path.join('graphics', 'shield_bar.png') self.bar_image = pygame.image.load(self.bar_image_path) assert self.bar_image.get_width() % 2, 'bar_image width must be odd.' self.bar_image_original = self.bar_image self.bar_stage_images = [self.bar_image] # Create list of bar surfaces with decreasing widths for i in range(self.bar_image.get_width() // 2): self.bar_stage_images.append( pygame.transform.smoothscale(self.bar_image_original, (self.bar_stage_images[i].get_width() - 2, self.bar_image.get_height()) )) self.bar_stage_images.append( pygame.transform.smoothscale(self.bar_image_original, (0, self.bar_image.get_height()))) self.bar_stage_duration = (self.duration * ml.framerate) / \ len(self.bar_stage_images) self.next_bar_image = None self.bar = ShieldBar()
def phase4(self): self.rotation_rate = ml.normalize_target_fps(6) if self.move_speed >= self.max_speed: self.move_speed = self.max_speed else: self.move_speed += ml.normalize_target_fps(0.015) if self.turning_rate >= self.max_turning_rate: self.turning_rate = self.max_turning_rate else: self.turning_rate += ml.normalize_target_fps(0.005) self.circle_random_timer = self.shoot(self.circle_random_timer, self.circle_random_bd, self.circle_random_fd) self.move()
def animate(self): """Plays the start or end animation based on elapsed time and duration. Also kills shield when end animation has finished.""" if ml.time() - self.spawn_time > self.duration and \ self.stage > 0: self.stage -= 1 self.image = self.stage_images[self.stage] elif ml.time() - self.spawn_time < self.animation_duration and \ self.stage < len(self.stage_images): self.image = self.stage_images[self.stage] self.stage += 1 else: self.image = self.image_original if self.stage == 0: self.bar.kill() self.kill() self.rect = self.image.get_rect()
def phase3(self): # Move boss in semicircle around player radius = 350 self.circle_angle += self.circle_angle_change circle_x = ml.player.rect.centerx + radius * math.cos( math.radians(self.circle_angle)) circle_y = ml.player.rect.centery - radius * math.sin( math.radians(self.circle_angle)) target_point = circle_x, circle_y if not 30 < self.circle_angle < 150: self.circle_angle_change *= -1 self.x, self.y, _ = ml.move_point(self, target_point, 20, 0, 360) # Boss shoot self.phase3fd.angle = self.circle_angle self.phase3timer = self.shoot(self.phase3timer, self.phase3bd, self.phase3fd) # Minion control self.minion2part.animate(angle_change=15) self.minion3part.animate(angle_change=15) self.minion_angle = ml.normalize_angle(self.minion_angle + 1) radius = 200 circle_x = ml.player.rect.centerx + radius * math.cos( math.radians(self.minion_angle)) circle_y = ml.player.rect.centery - radius * math.sin( math.radians(self.minion_angle)) minion2_point = circle_x, circle_y angle = self.minion_angle + 180 circle_x = ml.player.rect.centerx + radius * math.cos( math.radians(angle)) circle_y = ml.player.rect.centery - radius * math.sin( math.radians(angle)) minion3_point = circle_x, circle_y self.minion2.x, self.minion2.y, _ = ml.move_point( self.minion2, minion2_point, 20, 0, 360) self.minion3.x, self.minion3.y, _ = ml.move_point( self.minion3, minion3_point, 20, 0, 360) self.minion2part.move((self.minion2.x, self.minion2.y)) self.minion3part.move((self.minion3.x, self.minion3.y)) self.phase3miniontimer1 = self.shoot(self.phase3miniontimer1, self.phase3minionbd1, self.phase3minionfd) self.phase3miniontimer2 = self.shoot(self.phase3miniontimer2, self.phase3minionbd2, self.phase3minionfd)
def update_powerups(self): # Update shield if self.shield and not self.shield.alive(): self.shield_active = False self.shield = None elif self.shield: self.shield_active = True # Update homing shots if self.homing_shots_active: if ml.time() - self.homing_shots_activation_time >= \ ml.get_upgrade_values('Homing Duration'): self.homing_shots_active = False self.image = self.image_original if not self.invincibility_active: self.image = self.homing_image # Flicker sprite color when Homing duration is low if ml.time() - self.homing_shots_activation_time > \ ml.get_upgrade_values('Homing Duration') - 1: if ml.time() - self.toggle_time > 0.15: if self.image == self.image_original: self.toggle_time = ml.time() self.image = self.homing_image else: self.toggle_time = ml.time() self.image = self.image_original
def damage_animation(self): """Flashes the player sprite to show invincibility frames. This method must be called before update_powerups() so it doesn't override self.homing_image""" if ml.time() - self.last_hit_time < self.invincibility_duration: if ml.time() - self.hit_toggle_time > 0.05: if self.image in (self.image_original, self.homing_image): self.hit_toggle_time = ml.time() self.image = self.invisible_image else: self.hit_toggle_time = ml.time() if self.homing_shots_active: self.image = self.homing_image else: self.image = self.image_original else: # update_powerups() will set self.image to homing_image if necessary self.invincibility_active = False self.image = self.image_original
def update(self): # Call the appropriate method for each phase if ml.time() - self.phase_change_time > self.phase_change_delay: self.phase_method_list[self.phase - 1]() super().update() # animate_parts() must be called after super().update() to ensure proper coords self.animate_parts()
def shoot(self): if ml.time() - self.firing_timer >= ( 1 / ml.get_upgrade_values('Attack Speed')): shot_angles = ml.multi_shot_angles( ml.get_upgrade_values('Multi Shot'), 90, 10) for i in range(ml.get_upgrade_values('Multi Shot')): PlayerBullet(self.rect.centerx, self.rect.y, ml.get_upgrade_values('Bullet Speed'), ml.get_upgrade_values('Damage'), shot_angles[i], self.homing_shots_active) self.firing_timer = ml.time()
def __init__(self, start_point, target_point, image_name) -> pygame.sprite.Sprite: pygame.sprite.Sprite.__init__(self) self.x, self.y = start_point self.falling_speed = ml.normalize_target_fps(1) self.homing_speed = ml.normalize_target_fps(4) self.from_spawn_speed = ml.normalize_target_fps(7) self.homing_distance = 75 self.moving_from_spawn = True self.target_point = target_point # Set up image, rect, and mask self.image_path = os.path.join('graphics', image_name) self.image = pygame.image.load(self.image_path).convert_alpha() self.rect = self.image.get_rect() self.mask = pygame.mask.from_surface(self.image) self.rect.center = self.x, self.y # Add sprite to group self.add(ml.powerup_group)
def move(self): # Homing shots if ml.enemy_group.sprites() and self.homing: closest_enemy = ml.closest_sprite_in_group(self, ml.enemy_group) if closest_enemy: self.x, self.y, self.current_angle = ml.move_point( self, closest_enemy.rect.center, self.move_speed, self.current_angle, self.turning_rate) else: self.x += self.move_speed * math.cos( math.radians(self.current_angle)) self.y -= self.move_speed * math.sin( math.radians(self.current_angle)) # Non-homing shots else: self.x += self.move_speed * math.cos( math.radians(self.current_angle)) self.y -= self.move_speed * math.sin( math.radians(self.current_angle))
def setup_phase2(self): # Minion 1 self.minion1 = BossPart('mini_ring.png') self.minion1part = BossPart('mini_ring_orange.png') self.minion1.x, self.minion1.y = self.rect.centerx, self.rect.centery - 100 self.minion1.move((self.minion1.x, self.minion1.y)) self.minion1part.move((self.minion1.x, self.minion1.y)) self.phase2minionbd.parent = self.minion1 self.minion1.current_angle = ml.angle_to_point( self.rect.center, ml.player.rect.center) + 180 self.spawn_powerups(base_powerup_spawn * 5)
def move(self): """If a turning rate is defined, the enemy will home on the player. Otherwise, it will move in a straight line at its current angle.""" if self.turning_rate: self.x, self.y, self.current_angle = \ ml.move_point(self, ml.player.rect.center, self.move_speed, self.current_angle, self.turning_rate) else: self.x += self.move_speed * math.cos( math.radians(self.current_angle)) self.y -= self.move_speed * math.sin( math.radians(self.current_angle))
def setup_phase4(self): self.minion3.kill() self.minion3part.kill() self.laser = BossPart('laser_blue.png') self.laser.y = -1000 self.minion1.add(ml.boss_part_group) self.minion1part.add(ml.boss_part_group) self.minion1.move((self.rect.centerx, self.rect.centery - 100)) self.minion1part.move((self.rect.centerx, self.rect.centery - 100)) self.minion2.move((self.rect.centerx - 100, self.rect.centery)) self.minion2part.move((self.rect.centerx - 100, self.rect.centery)) self.minion3 = BossPart('mini_ring.png') self.minion3part = BossPart('mini_ring_red.png') self.minion3.move((self.rect.centerx + 100, self.rect.centery)) self.minion3part.move((self.rect.centerx + 100, self.rect.centery)) self.phase1fd.multi = 6 self.phase1fd.interval = 15 self.phase1fd.firing_speed = 0.5 self.phase1bd.parent = self.minion3 self.phase1bd.speed = 3.5 self.phase1timer1 = ml.time() + self.phase_change_delay self.phase1timer2 = ml.time() + self.phase_change_delay self.phase1timer3 = ml.time() + self.phase_change_delay + (1 / (2 * 0.5)) self.phase1timer4 = ml.time() + self.phase_change_delay + (1 / (2 * 0.5)) self.phase2minionbd.parent = self.minion1 self.minion1.current_angle = ml.angle_to_point( self.rect.center, ml.player.rect.center) + 180 self.minion_angle = 90 self.phase3minionbd1.turning_rate = 0.9 self.spawn_powerups(base_powerup_spawn * 7)
def damage(self, damage: int): if damage > 0 and not self.shield_active and \ ml.time() - self.last_hit_time > self.invincibility_duration: self.health -= damage self.invincibility_active = True self.last_hit_time = ml.time() self.hit_toggle_time = ml.time() # Heal elif damage < 0: self.health -= damage if self.health > ml.get_upgrade_values('Max Health'): self.health = ml.get_upgrade_values('Max Health') ml.update_player_health()
def main(): # Hide cursor (replaced by custom image) pygame.mouse.set_visible(False) # Set up and load things when the game first runs ml.set_window_data(window) ml.loading_screen() pygame.display.update() ml.set_fps_clock(clock) ml.set_framerate(FPS) ml.load_images() if debug: logging.info('Loading time: %f' % (time.time() - start_time)) while True: # Run main game loop if profiling: cProfile.run('run_game()') else: run_game() # run_game() returns when player dies game_over()
def phase2(self): # Mirror player movement rotation_rate = 15 if not self.current_angle % rotation_rate: self.current_angle -= self.current_angle % rotation_rate mirror_point = ml.player.rect.centerx * -1 + ml.window_width, \ ml.player.rect.centery * -1 + ml.window_height self.x, self.y, _ = ml.move_point(self, mirror_point, 20, 0, 360) # Rotate if player moves above/below if self.y > ml.player.rect.centery: if self.current_angle != 90 and self.x > ml.player.rect.centerx: self.current_angle -= rotation_rate elif self.current_angle != 90 and self.x < ml.player.rect.centerx: self.current_angle += rotation_rate else: if self.current_angle != 270 and self.x > ml.player.rect.centerx: self.current_angle += rotation_rate elif self.current_angle != 270 and self.x < ml.player.rect.centerx: self.current_angle -= rotation_rate # Shoot self.phase1homing_timer = self.shoot(self.phase1homing_timer, self.phase1homing_bd, self.phase1homing_fd) bullet_speed = 7 distance = abs(ml.player.rect.centery - self.rect.centery) duration_f = distance / bullet_speed duration_s = duration_f / ml.normalize_target_fps(60) self.phase1_bd = BulletData(self, speed=bullet_speed, duration=duration_s, exploding=self.phase1explode) self.phase1_fd = FiringData(firing_speed=2, angle=self.current_angle) self.phase1_timer = self.shoot(self.phase1_timer, self.phase1_bd, self.phase1_fd)
def phase1(self): angle = 270 # ml.angle_to_point(self.rect.center, ml.player.rect.center) self.x, self.y, _ = ml.move_point(self, ml.player.rect.center, 1, 0, 360) self.phase1fd.angle = angle self.phase1timer1 = self.shoot(self.phase1timer1, self.phase1bd, self.phase1fd) self.phase1fd.angle = angle + 180 self.phase1timer2 = self.shoot(self.phase1timer2, self.phase1bd, self.phase1fd) self.phase1fd.angle = angle + 90 self.phase1timer3 = self.shoot(self.phase1timer3, self.phase1bd, self.phase1fd) self.phase1fd.angle = angle - 90 self.phase1timer4 = self.shoot(self.phase1timer4, self.phase1bd, self.phase1fd)
def move(self): if self.homing_shots_active: self.x, self.y, self.angle = ml.move_point(self, ml.player.rect.center, self.move_speed, self.angle, self.turning_rate) # Spiral shots elif self.spiral: self.x = self.spawn_point[0] + self.radius * math.cos( math.radians(self.angle)) self.y = self.spawn_point[1] + self.radius * math.sin( math.radians(self.angle)) self.radius += self.radial_growth self.angle += self.turning_rate # Normal shots else: self.x += self.diag_move_x self.y -= self.diag_move_y