示例#1
0
class HeadLaserBeam(pygame.sprite.Sprite):

    def __init__(self, head):

        super(HeadLaserBeam, self).__init__()

        self.head = head # Bad variable name TODO

        self.beam_size_timer = Timer()
        self.beam_height = 1
        self.beam_incrementer = 1

        self.update_beam_image(self.beam_height)
        self.rect.centery = self.head.rect.centery + 10

    def update_beam_height(self):
        """Pulse height, and remove from sprite lists for 1.5 seconds when height=0."""

        max_beam_height = 15
        beam_downtime = 1500 # Time where there is no beam, aka no damage taken by player

        if self.beam_height <= 0:
            enemy_projectile_list.remove(self)

            if self.beam_size_timer.elapsed_time() > beam_downtime:
                enemy_projectile_list.add(self)
                self.beam_height = 1
                self.beam_incrementer = 1

        elif self.beam_size_timer.elapsed_time() > 60:
            enemy_projectile_list.add(self)
            if self.beam_height >= max_beam_height:
                self.beam_incrementer *= -1

            self.beam_height += self.beam_incrementer

            self.beam_size_timer.reset()

    def update_beam_image(self, beam_height):

        self.image = pygame.Surface([640, beam_height])
        self.image.fill(BLUE)
        self.rect = self.image.get_rect()

    def kill(self):

        all_sprites_list.remove(self)
        enemy_projectile_list.remove(self)

    def update(self):

        self.update_beam_height()
        self.update_beam_image(self.beam_height)
        self.rect.centery = self.head.rect.centery + 10
示例#2
0
class TwoWayImage(pygame.sprite.Sprite):
    """Sprite which moves in two directions and loops; used to make background/foreground."""

    def __init__(self, speed, image_dir, rearrange_coeff, image_type):

        super(TwoWayImage, self).__init__()

        self.speed = speed
        self.image_dir = image_dir

        # Total number of images; multiplied for rearrange offset
        self.rearrange_coeff = rearrange_coeff
        self.image_type = image_type

        if self.image_type == "foreground":
            self.image = cache_image.get(self.image_dir, convert_alpha=False)
        else:
            self.image = cache_image.get(self.image_dir)
        self.rect = self.image.get_rect()

        self.alpha_timer = Timer()
        self.alpha = 255
        self.is_dying = False,

        if self.image_type == "foreground":
            self.hitmask = pygame.mask.from_surface(self.image)

    def rearrange(self):
        """Reset the sprite if it goes offscreen."""

        # If screen_width isn't divisible by speed, the offset will vary:
        natural_offset = self.rect.x + SCREEN_WIDTH
        self.rect.x = (self.rearrange_coeff * SCREEN_WIDTH) + natural_offset

    def kill(self):
        """Remove foreground terrain for boss battles by fading its alpha."""

        if self.alpha_timer.elapsed_time() > 60:
            self.alpha -= 3
            self.image.set_alpha(self.alpha)
            self.alpha_timer.reset()

    def update(self):
        """Update image movement, rearranging or killing alpha as necessary."""

        if self.alpha <= 0:
            all_sprites_list.remove(self)
            if self.image_type == "foreground":
                foreground_list.remove(self)

        if self.is_dying == True:
            self.kill()

        self.rect.x -= self.speed

        if self.rect.x < (0 - self.rect.width):
            self.rearrange()
示例#3
0
class BossWarning(pygame.sprite.Sprite):
    """Flash a 'WARNING!' message in the center of the SCREEN."""

    def __init__(self, txt_img, xpos_center, ypos_center, kill_time):
        super(BossWarning, self).__init__()

        self.kill_time = kill_time

        self.image = cache_image.get("screen_messages/boss_warning" + "_F1.png")
        self.image.set_colorkey(BLACK)
        self.rect = self.image.get_rect()
        self.rect.centerx = xpos_center
        self.rect.centery = ypos_center

        self.anim_timer = Timer()
        self.kill_timer = Timer()
        self.frame_idx = 2

    def update(self):
        """Flash the sign by switching images and destroy it after a set time."""

        if self.kill_timer.elapsed_time() > self.kill_time:
            self.kill()
        try:
            flash_time = 360
            if self.anim_timer.elapsed_time() > flash_time:
                self.anim_timer.reset()
                self.image = cache_image.get("screen_messages/boss_warning_F{}.png"
                                             .format(self.frame_idx))
                self.image.set_colorkey(BLACK)
                self.frame_idx += 1
        except pygame.error:
            self.frame_idx = 1

    def kill(self):

        all_sprites_list.remove(self)
示例#4
0
class FadeIn(pygame.sprite.Sprite):

    def __init__(self, surface, alpha_incrementer):
        super(FadeIn, self).__init__()

        self.surface = surface

        # Alpha value for transparency:
        self.alpha = 0

        # Incrementer will change the 'direction' of the fade:
        self.alpha_incrementer = alpha_incrementer

        # Inc_delay = frames that pass before next alpha update:
        self.alpha_inc_delay = 60 # 60 = One frame

        # Flag for completion:
        self.is_complete = False

        self.image = pygame.Surface([800, 600])
        self.rect = self.image.get_rect()
        self.rect.centerx = SCREEN_WIDTH / 2
        self.rect.centery = SCREEN_HEIGHT / 2
        self.image.fill((0, 0, 0))
        self.image.set_alpha(0)

        self.fade_timer = Timer()
        self.initial_time = pygame.time.get_ticks()

    def render(self):
        pass

    def update(self):

        # Alpha incrementer is not actually an incrementer for the image's alpha;
        # surface.blit STACKS these images on top of each other. Alpha is still
        # tracked via incrementing, however:
        self.image.set_alpha(self.alpha_incrementer)
        self.surface.blit(self.image, (0, 0))

        total_elapsed_time = pygame.time.get_ticks() - self.initial_time

        # End conditions:
        if total_elapsed_time > 4000 or self.alpha > 255:
            self.is_complete = True

        # Transparency incrementation:
        if self.fade_timer.elapsed_time() > self.alpha_inc_delay:
            self.alpha += self.alpha_incrementer
示例#5
0
class FadeOut(pygame.sprite.Sprite):

    def __init__(self, surface):
        super(FadeOut, self).__init__()

        self.surface = surface

        # Alpha value for transparency:
        self.alpha = 255

        # Incrementer will change the 'direction' and speed of the fade:
        self.alpha_incrementer = -25

        # Inc_delay = frames that pass before next alpha update:
        self.alpha_inc_delay = 60

        # Flag for completion:
        self.is_complete = False

        self.image = pygame.Surface((800, 600))
        self.rect = self.image.get_rect()
        self.image.fill((0, 0, 0))
        self.image.set_alpha(255)

        self.fade_timer = Timer()

    def update(self):

        self.image.set_alpha(self.alpha)
        self.surface.blit(self.image, (0, 0))

        if not self.is_complete:

            if self.alpha < 0:
                self.alpha = 0
                self.is_complete = True

            if self.fade_timer.elapsed_time() > self.alpha_inc_delay:
                self.alpha += self.alpha_incrementer
                self.fade_timer.reset()
示例#6
0
class TextToScreen(pygame.sprite.Sprite):
    """Renders text to screen as a sprite image; disappears after a set time."""

    def __init__(self, txt_img, xpos_center, ypos_center, kill_time):
        super(TextToScreen, self).__init__()

        self.kill_time = kill_time
        self.txt_img = txt_img

        self.image = cache_image.get(txt_img + "_F1.png")
        self.rect = self.image.get_rect()
        self.image.set_colorkey(BLACK)
        self.rect.centerx = xpos_center
        self.rect.centery = ypos_center

        self.kill_timer = Timer()

    def update(self):

        if self.kill_timer.elapsed_time() > self.kill_time:
            self.kill()

    def kill(self):
        all_sprites_list.remove(self)
示例#7
0
class Boss2(EnemyLinear):

    def __init__(self, dict_enemy):
        """Boss 2 housing class. Controls all body parts."""
        super(Boss2, self).__init__(dict_enemy)
        self.image_dir = self.params["image"]

        self.has_entered = False
        self.is_attacking = False
        self.is_dying = False
        self.is_dead = False

        self.next_attack_timer = Timer()

        self.attack_scheduler = ActionScheduler()

        self.image = pygame.transform.scale2x(self.image) # Debug only
        self.hitmask = pygame.mask.from_surface(self.image)

        self.initialize_body_parts()

    def initialize_body_parts(self):
        """Set up head and claw body parts as separate sprites."""

        from models.bosses.boss_2_head import Boss2Head

        head = Boss2Head(self)
        self.head = head

    def add_body_parts(self):
        """Add body parts to necessary lists."""

        all_sprites_list.add(self.head)

    def enter(self):

        if self.has_entered:
            return

        if self.rect.centerx <= 550 and self.rect.centery <= SCREEN_HEIGHT / 2:
            self.has_entered = True
            self.next_attack_timer.reset()
            self.head.attack_scheduler.reset()
            self.attack_scheduler.reset()

        if not self.has_entered:
            self.add_body_parts()

            if self.rect.centerx >= 550:
                self.rect.x -= 1

            if self.rect.centery >= SCREEN_HEIGHT / 2:
                self.rect.y -= 1

    def reset_attack_parameters(self):
        """Reset all parameters for boss's (and body parts') attacks."""

        self.next_attack_timer.reset()
        self.attack_scheduler.reset()

        self.head.reset_attack_parameters()

    def laser_head_attack(self, time_offset):

        attack_times =  [750, 10000, 10750]
        attack_speeds = [6,       0,    -6]
        attack_times =  [(x + time_offset) for x in attack_times]

        self.attack_scheduler.update(attack_times)

        if not self.attack_scheduler.is_done_action:
            idx = self.attack_scheduler.action_idx
            current_speed = attack_speeds[idx]
            self.rect.x += current_speed

            if idx == 1:
                self.head.laser_attack(time_offset + 750)

        else:
            self.reset_attack_parameters()

        self.is_attacking = self.attack_scheduler.is_doing_action

    def update(self):

        self.enter()

        if self.has_entered and not self.is_dying:
            if self.next_attack_timer.elapsed_time() > 2500:
                self.laser_head_attack(2500)
示例#8
0
class FallingBlock(EnemyLinear):
    """Environment Block that will fall unless supported by a floor block."""

    def __init__(self, dict_enemy):

        super(FallingBlock, self).__init__(dict_enemy)

        # NOTE: falling_block_list is only used for collision logic
        # FallingBlock()s are added to enemy_sprite_list and use that logic
        falling_block_list.add(self)

        self.fall_timer = Timer()

        self.fall_speed = 3
        self.is_falling = False

    def check_collision(self, sprite_list):
        """Check for vertical collision with a sprite list, skipping itself."""

        for block in sprite_list:
            # Continue falling if self = the only block in the list:
            if self == block:
                if len(sprite_list) == 1:
                    self.is_falling = True
                    self.fall_timer.reset()
                continue

            # 15 = the 'edge cushion' needed to keep the block from falling:
            x_range = range(block.rect.x - self.rect.width + 15,
                            block.rect.x + block.rect.width - 15)
            y_range = range(block.rect.y, block.rect.y + block.rect.height)

            if (self.rect.x in x_range
                and self.rect.y + self.rect.height in y_range):

                    self.rect.y = (block.rect.y
                                   - self.rect.height
                                   + (self.fall_speed / 2)
                                   )

                    self.is_falling = False
            else:
                if not self.is_falling:
                    self.is_falling = True
                    self.fall_timer.reset()

    def fall(self, fall_speed):
        """Move the block downward, with a slight pause at the top for visual effect."""

        if self.fall_timer.elapsed_time() > 60:
            self.rect.y += (fall_speed * 4)
        else:
            self.rect.y += fall_speed


    def update(self):
        """Kill if necessary; check collisions; update fall speed."""

        if (self.rect.centerx < -700
                or self.rect.centerx > SCREEN_WIDTH + 700
                or self.rect.centery < -200
                or self.rect.centery > SCREEN_HEIGHT + 200):
            self.kill()

        self.check_collision(falling_block_list)

        if self.is_falling:
            self.fall(self.fall_speed)

        self.rect.x -= 3
class ActionScheduler():
    """
    Allows for scheduling of actions via an array of times, an action index,
    a timer, and 4 booleans (Note: all booleans won't always be used  on any
    given instance. They exist to cover a range of functionality.)

    This times array...

    action_times = [0, 1000, 2000, 3000]

    ...means that an action occurs at 1-second, 2-seconds, and 3-seconds. These elements
    are considered thresholds for 'sub_actions', and the entire array constitutes one
    total 'action.'

    For example, in enemy pathing, these could be changes of direction. So each change
    in direction would be a 'sub_action', whereas the enemy's entire 'path_times' array
    would be one total 'action.'

    'action_idx' increments at each new time element in the times array.

    'is_doing_action' = True while the timer < last time in the times array.
    (Note that there is no 'is_doing_subaction', because 'action_idx' won't increment
    until the timer hits the next 'subaction' threshold; it will remain the same for the
    duration of a 'subaction', so any behavior logic can simply reference that index.)

    'is_done_action' = True only when timer > last time in times array.

    'is done_subaction' will be True for only one 'update' cycle per 'subaction' completed.
    This sounds confusing, because it is...This was used to solve a problematic edge
    condition that arises when elapsed time > total_time and 'is_done_subaction' is set to
    True: it never gets set back to false. We solve that with some if-else logic and the
    self.kill() method:

    self.kill() and 'is_dead' are used only for actions that occur ONCE per 'subaction'
    threshold.

    Example:

        Condition:
            elapsed time = 1001
            action_times[-1] = 1000

        What happens:
            the first time through self.update(), 'is_done_action' and
            'is_done_subaction' are set to True

            the NEXT time through self.update(), because 'is_done_action'
            is True, we go through our is_looped or kill conditions

            from there, if 'is_dead' == True, update returns None.

    """

    def __init__(self, is_looped=None):

        self.is_doing_action = False
        self.is_done_action = False
        self.is_done_subaction = False
        self.is_dead = False

        self.action_idx = 0
        self.timer = Timer()
        self.is_looped = is_looped

    def reset(self):
        """Loop an action by resetting booleans, index, and timer variables."""

        self.is_doing_action = False
        self.is_done_action = False
        self.action_idx = 0
        self.timer.reset()

    def kill(self):
        """Used to properly assign 'is_done_subaction' to False when a total action is done."""

        self.is_doing_action = False
        self.is_done_action = True
        self.is_done_subaction = False
        self.is_dead = True

    def execute_final_action(self):
        """Determine whether to loop the action again or kill it."""

        if self.is_looped:
            self.reset()
        else:
            self.kill()

    def update(self, action_times):
        """
        Check if 'is_dead' or 'is_done_action', otherwise proceed normally:
        Flip 'is_doing_action' to True.
        Increment 'action_idx' when elapsed time reaches next 'subaction' threshold.
        If timer > total time, flip 'is_doing_action' = False, and 'is_done_action' = True.
        """

        if self.is_dead: return

        self.is_doing_action = True
        self.is_done_subaction = False

        if self.is_done_action:
            self.execute_final_action()

        elif self.timer.elapsed_time() > action_times[-1]:
            self.action_idx = -1
            self.is_done_action = True
            self.is_done_subaction = True

        elif self.timer.elapsed_time() > action_times[self.action_idx]:
            self.is_done_subaction = True
            self.action_idx += 1
示例#10
0
class FourWayImage(pygame.sprite.Sprite):
    """Sprite that can move in four directions on a loop; used to create background/foreground."""

    def __init__(self, params, image_dir, image_type):

        super(FourWayImage, self).__init__()

        self.params = params
        self.image_type = image_type

        self.directions = self.params["directions"]
        self.speed = self.params["speed"]

        self.image_dir = image_dir

        if self.image_type == "foreground":
            self.image = cache_image.get(self.image_dir, convert_alpha=False)
        else:
            self.image = cache_image.get(self.image_dir)

        self.image = pygame.transform.scale2x(self.image)
        self.rect = self.image.get_rect()

        if self.image_type == "foreground":
            self.hitmask = pygame.mask.from_surface(self.image)

        # Used for re-arranging later:
        self.initial_x_pos = 0
        self.initial_y_pos = 0

        # Used to ensure accurate movement to screen edges:
        self.width_check = 0
        self.height_check = 0

        self.path_pointer = 0

        # For perfect diagonal movement, y and x must move at different speeds,
        # so we get a fraction here as a coefficient for later:
        self.diagonal_speed_coeff = Fraction(SCREEN_HEIGHT, SCREEN_WIDTH) * self.speed
        self.diagonal_counter = 1

        self.is_dying = False
        self.alpha = 255
        self.alpha_timer = Timer()

    def path(self, direction, coefficient):
        """
        Move the sprite in various directions.

        Diagonal movement was very tricky, as the screen is not square,
        so 1 : 1 movement doesn't produce desired results.

        Neither does decimal movement, as trying to move by decimals of a pixel
        results in de-syncing the background.

        Instead we have to produce whole-number pixel movement based on a ratio
        we calculate using the speed, screen height, and screen width:

        *********************************************************************
        EXAMPLE:

        SCREEN_HEIGHT / SCREEN_WIDTH = (3/4)
        speed: 5
        coefficient = 5 * (3/4) = (15/4)
        diagonal_counter = 1

        ticker will increment up to coefficient's denominator,
        y will increment by the denominator amount.

        when the ticker == the denominator,
        y += total moved space, aka: ((denominator - 1) * denominator)
        and diagonal_counter will reset to 1:

        Ticker      Moved_Space
        1           4
        2           8
        3           12
        4           15

        4 updates = 15 moved pixels = our original coefficient!

        (Some additional if-else logic had to be implemented for coefficients
        that were < 1 (i.e., if speed == 1 or 2), so as to not get negative movement.
        """

        if direction == "right":
            self.rect.x -= self.speed
            self.width_check += self.speed

        elif direction == "down":
            self.rect.y -= self.speed
            self.height_check += self.speed

        elif direction == "up":
            self.rect.y += self.speed
            self.height_check += self.speed

        elif direction in ["upright", "downright"]:

            # For diagonal movement, we have to use our coefficient:
            if self.diagonal_counter < coefficient.denominator:

                if direction == "upright":
                    # Special conditions if coefficient < 1:
                    if coefficient < 1:
                        self.rect.y += 1
                    else:
                        self.rect.y += coefficient.denominator
                elif direction == "downright":
                    if coefficient < 1:
                        self.rect.y -= 1
                    else:
                        self.rect.y -= coefficient.denominator

                self.diagonal_counter += 1

            else:
                if coefficient < 1:
                    next_move = 0
                else:
                    moved_space = (coefficient.denominator - 1) * coefficient.denominator
                    next_move = coefficient.numerator - moved_space

                if direction == "upright":
                    self.rect.y += next_move
                elif direction == "downright":
                    self.rect.y -= next_move

                self.diagonal_counter = 1

            self.rect.x -= self.speed
            self.width_check += self.speed
            self.height_check += float(self.diagonal_speed_coeff)

    def kill(self):

        if self.alpha_timer.elapsed_time() > 60:
            self.alpha_timer.reset()
            self.alpha -= 3
            self.image.set_alpha(self.alpha)

    def update(self):
        """Update based on 'directions' parameter; loop when finished."""

        if self.alpha <= 0:
            all_sprites_list.remove(self)
            if self.image_type == "foreground":
                foreground_list.remove(self)

        if self.is_dying:
            self.kill()

        # Continuous x-loop for foreground only:
        if self.image_type == "foreground" and self.rect.x < (self.initial_x_pos - 1600):
            self.rect.x = 0

        try:
            current_direction = self.directions[self.path_pointer]

            # Reset flags if they reach screen dimensions (more precise than a timer):
            if self.image_type == "background":
                if self.width_check >= SCREEN_WIDTH or self.height_check >= SCREEN_HEIGHT:

                    # TODO: Fix looped clipping:
                    self.width_check = 0
                    self.height_check = 0

                    self.path_pointer += 1

            else:
                # Foreground moves at 2x the speed of background, thus double the checks:
                if self.width_check >= SCREEN_WIDTH * 2 or self.height_check >= SCREEN_HEIGHT * 2:

                    self.width_check = 0
                    self.height_check = 0

                    self.path_pointer += 1

            self.path(current_direction, self.diagonal_speed_coeff)

        except IndexError:

            self.rearrange()

    def rearrange(self):
        """Reset images for looping."""

        self.rect.x = self.initial_x_pos - self.speed
        self.rect.y = self.initial_y_pos
        self.path_pointer = 0
class AnimationScheduler():
    """Class to handle various types of sprite animations."""

    def __init__(self, is_looped, is_seesaw=False):

        self.is_looped = is_looped
        self.is_seesaw = is_seesaw
        self.is_done_animating = False

        self.frame_idx = 0
        self.timer = Timer()
        if self.is_seesaw:
            self.frame_incrementer = 1

    def reset(self):
        """Reset boolean, frame index, and timer."""
    
        self.is_done_animating = False
        self.frame_idx = 0
        self.timer.reset()

    def looped_update(self, anim_times, static_time_threshold):
        """Update looped animation based on a schedule of times or a single static threshold."""

        if anim_times is not None:
            if self.timer.elapsed_time() > anim_times[self.frame_idx]:
                self.frame_idx += 1
                self.timer.reset()

        elif self.timer.elapsed_time() > static_time_threshold:
            self.frame_idx += 1
            self.timer.reset()

    def seesaw_update(self, anim_times, static_time_threshold):
        """Update an animation that loops by playing itself backwards and forwards."""

        try:
            if self.timer.elapsed_time() > static_time_threshold:
                self.frame_idx += self.frame_incrementer
                self.timer.reset()
        except pygame.error:
            self.frame_incrementer *= -1

    def normal_update(self, anim_times):
        """Update linear animation that plays one time before flipping 'is_done_animating'."""

        if self.is_done_animating:
            return

        # Necessary edge condition:
        elif len(anim_times) < 2:
            if self.timer.elapsed_time() > anim_times[self.frame_idx]:
                self.frame_idx += 1
                self.is_done_animating = True

        elif self.frame_idx == len(anim_times) - 1 and self.timer.elapsed_time() > anim_times[-1]:
            self.is_done_animating = True

        # This needs to be separated from the above if-statement to preserve the last frame of
        # animation. If we tried to flip 'is_done_animating' AND increment frame_idx at the same
        # time, we don't get to see the last frame of the animation. Separating here ensures that:
        elif self.frame_idx == len(anim_times) - 2 and self.timer.elapsed_time() > anim_times[-2]:
            self.frame_idx += 1

        elif self.timer.elapsed_time() > anim_times[self.frame_idx]:
            self.frame_idx += 1
            self.timer.reset()

    def get_next_frame(self, image_dir, convert_alpha=True):
        """Return next frame in the animation."""

        # Using aTry-Except block allows me to easily add new frame assets to a
        # looped animation without updating any code whatsoever!
        try:
            image = cache_image.get(image_dir + "_F{}.png".format(self.frame_idx), convert_alpha)
        except pygame.error:
            if self.is_looped:
                self.reset()
                image = cache_image.get(image_dir + "_F{}.png".format(self.frame_idx), convert_alpha)
            elif self.is_seesaw:
                self.frame_incrementer *= -1
                self.frame_idx += self.frame_incrementer
                image = cache_image.get(image_dir + "_F{}.png".format(self.frame_idx), convert_alpha)

        return image

    def update(self, anim_times=None, static_time_threshold=120):
        """Update based on type of animation."""

        if self.is_looped:
            self.looped_update(anim_times, static_time_threshold)

        elif self.is_seesaw:
            self.seesaw_update(anim_times, static_time_threshold)

        else:
            self.normal_update(anim_times)
示例#12
0
class Level(object):
    """Deploy waves of enemies based on a dictionary of parameters."""

    def __init__(self, dict_level):

        self.params = dict_level

        self.wave_timer = Timer()
        self.wave_idx = 1
        self.level_scheduler = ActionScheduler()
        self.wave_scheduler = ActionScheduler()

        self.level_number = self.params["level_number"]
        self.is_complete = False

        self.initialize_level_assets()
        self.boss = self.initialize_boss()

        # Wave parameters + deployment times
        self.deployment_times = {0: 0}
        self.waves = {0: 0}
        # self.initialize_waves()

        self.last_level = key_or_none("last", self.params)

    def initialize_level_assets(self):
        """Initialize background and foreground art for the level."""

        # Boolean which determines if level is sidescrolling or multi-directional
        self.has_directional_bg = key_or_none("has_directional_bg", self.params)

        if self.has_directional_bg is None:
            self.background = TwoWayBackground(self.params["bg_params"])
            self.foreground = TwoWayForeground(self.params["fg_params"])
        else:
            self.background = FourWayBackground(self.params["bg_params"])
            self.foreground = FourWayForeground(self.params["fg_params"])

    def initialize_boss(self):
        """Initialize the level's boss, based on params."""

        params_dict = self.params["boss"]
        klass = params_dict["class"]
        params = params_dict["params"]

        boss = klass(params)

        return boss

    def initialize_waves(self):
        """Unpack wave and deployment time parameters into separate dicts."""

        # NOTE: DO NOT TOUCH THESE BLOCKS!!!

        # Loops for collecting Deployment Times:
        deployment_times = self.params["deployment_times"]

        for wave_num in deployment_times:
            try:
                for subwave_times in deployment_times[wave_num]:
                    self.deployment_times[len(self.deployment_times)] = subwave_times
            except TypeError:
                self.deployment_times[len(self.deployment_times)] = deployment_times[wave_num]
        # Multiply times by 1000 to account for milliseconds:
        for wave_num in self.deployment_times:
            self.deployment_times[wave_num] *= 1000

        # Loop for collecting Waves:
        waves = self.params["waves"]

        for wave_num in waves:
            if isinstance(waves[wave_num][0], list):
                for wave_params in waves[wave_num]:
                    self.waves[len(self.waves)] = wave_params
            else:
                self.waves[len(self.waves)] = waves[wave_num]

    def load_wave(self, params_wave):
        """
        Return a wave of enemies based on the parameters dictionary, accounting
        for positional offsets, vertical flips, and horizontal flips.
        """

        # Get parameters from the dict:
        next_wave = []

        num_enemy = params_wave[0]
        klass = params_wave[1]
        params_enemy = params_wave[2]
        offsets = params_wave[3]
        vertical_flips = params_wave[4]
        horizontal_flips = params_wave[5]

        # Special offsets applied to FallingBlocks to spawn simultaneously:
        if klass == FallingBlock:
            for i in range(num_enemy):
                enemy = klass(params_enemy)
                if offsets is not None:
                    enemy.rect.centerx += offsets[i][0]
                    enemy.rect.centery += offsets[i][1]

                next_wave.append(enemy)

        else:
            # Apply any x- or y- shifts:
            for i in range(0, num_enemy):
                enemy = klass(params_enemy)
                if offsets is not None:
                    enemy.rect.centerx += offsets[0]
                    enemy.rect.centery += offsets[1]

                next_wave.append(enemy)

            # Apply flips ([i - 1] because params are not 0-indexed):
            if horizontal_flips is not None:
                for i in horizontal_flips:
                    next_wave[i - 1].flip_path_horizontal()

            if vertical_flips is not None:
                for i in vertical_flips:
                    next_wave[i - 1].flip_path_vertical()

        all_sprites_list.add(next_wave)
        enemy_sprite_list.add(next_wave)

    def display_boss_warning(self):
        """Flash a warning message that the boss is approaching."""

        warning = BossWarning("screen_messages/boss_warning",
                              SCREEN_WIDTH / 2, 200, 6000)
        all_sprites_list.add(warning)

        message = TextToScreen("screen_messages/boss_approaching",
                               SCREEN_WIDTH / 2, 300, 6000)
        all_sprites_list.add(message)

    def release_wave(self, wave, offset):

        if self.wave_scheduler.is_done_action:
            self.wave_scheduler.reset()
            self.wave_timer.reset()
            self.wave_idx += 1
            return

        wave_times = [((x * 1000) + offset) for x in wave["wave_times"]]
        enemy_params = wave["enemies"]

        self.wave_scheduler.update(wave_times)
        wave_idx = self.wave_scheduler.action_idx - 1

        #TODO: Temporary hack, should fix
        if wave_idx == -2:
            wave_idx = -1

        if self.wave_scheduler.is_done_subaction or self.wave_scheduler.is_done_action:
            self.load_wave(enemy_params[wave_idx])

    def run(self):
        """Load all waves and deploy them based on a timed schedule."""

        # try:

        # Level completion conditions:
        if self.boss.is_dead == True:
            self.is_complete = True

        last_wave = self.params["waves"][self.wave_idx - 1]
        current_wave = self.params["waves"][self.wave_idx]

        start_time = (current_wave["deployment_time"] - last_wave["deployment_time"]) * 1000


        if self.wave_timer.elapsed_time() > start_time:
            if current_wave["wave_times"] == "BOSS":
                all_sprites_list.add(self.boss)
                boss_list.add(self.boss)

            elif current_wave["wave_times"] == "WARNING":
                self.wave_timer.reset()
                self.wave_idx += 1

                self.display_boss_warning()
                self.foreground.kill()

            else:
                self.release_wave(current_wave, start_time)
示例#13
0
class EnemyNonlinear(Enemy):
    """Instance of Enemy that moves in non-linear directions."""

    def __init__(self, dict_enemy):

        super(EnemyNonlinear, self).__init__(dict_enemy)

        self.rect.centerx = self.params["pos_x"]
        self.speed = self.params["speed"]

        self.path_type = self.params["path_type"]
        self.initialize_specific_params()
        self.path_timer = Timer()

    def initialize_specific_params(self):
        """Initialize pathing parameters specific to enemy's path_type."""

        # For wave (sin/cos/tan) patterns:
        if self.path_type == "sin":
            self.y_offset = self.params["y_offset"]
            self.path_coeff = self.params["path_coeff"]
            self.wave_height = self.params["wave_height"]
            self.wave_frequency = self.params["wave_frequency"]
            self.step = 0

        # For y = mx+b patterns (parabolas, etc.):
        elif self.path_type == "squared":
            self.step = self.params["step"]
            self.step_increment = self.params["step_increment"]

        # For a curved hook pattern:
        elif self.path_type == "C":
            self.pos_y = self.params["pos_y"]
            self.rect.centery = self.pos_y

            self.speed = self.params["speed"]
            self.step = 0
            self.step_increment = self.params["step_increment"]
            # Change to 1 for self.flip_path_vertical():
            self.vertical_dir = -1

    def sin_path(self):
        """Path in a wave-pattern."""

        self.step += self.wave_frequency
        self.rect.centerx += self.speed
        self.rect.centery = (self.path_coeff
                             * (math.sin(self.step)
                                * self.wave_height)
                             + self.y_offset)

    def squared_path(self):
        """Path in a parabola pattern."""

        self.rect.centerx += self.speed
        self.step += self.step_increment
        self.rect.centery = ((self.step ** 2)
                             + (150 - (3 * abs(self.step))))

    def c_path(self):
        """Path in a c-shaped pattern."""

        if self.path_timer.elapsed_time() < 2000:
            self.rect.centerx += 2
        else:
            self.rect.centerx += self.speed

        self.step += self.step_increment
        self.rect.centery = (self.pos_y
                             + (self.vertical_dir  # Coefficient for verticality
                                * ((3000 - (1500 * self.step))
                                   * (math.sqrt(self.step)
                                      * (1 / (0.8 + (8 * self.step))))))
                             )

    def path(self):
        """
        Sets up non-linear enemy paths, using 4 variables.
        Each variable is a parameter which determines the enemy path.
        This example...

        "path_type": "sin",
        "path_coeff": 1,
        "wave_height": 100,
        "wave_frequency": 0.08,
        "speed": -5,
        "y_offset": 100

        ...uses this equation:

        Y = path_coeff * (sin(step) * wave_height) + y_offset

        ...to make a "wavy" path, where "wave_frequency" controls the
        wave speed, and "wave_height" controls the wave height.

        ...and would do the following:

        >The enemy's X-position will move left at a speed of 5
        >The enemy's Y-location will represent the result of the
        above equation, as "step" continues to increment (offset
        from the top of the screen by "y_offset" pixels.)

        The ability to flip the path vertically is built into the
        class, using the method "flip_path_vertical", which reverses
        the sign "path_coeff".
        """

        # y = sin(x)
        if self.path_type == "sin":
            self.sin_path()

        # y = x ** 2
        elif self.path_type == "squared":
            self.squared_path()

        # y = 1500 * (sqrt(x) * (1 / (0.8 + (3 * x))
        elif self.path_type == "C":
            self.c_path()

    def flip_path_vertical(self):
        """Flip enemy's path's y-directions and initial y-position."""

        if self.path_type == "sin":
            self.y_offset = (SCREEN_HEIGHT - self.y_offset)
            self.path_coeff *= -1

        if self.path_type == "C":
            self.pos_y = 0 - (self.pos_y % SCREEN_HEIGHT)
            self.vertical_dir *= -1

    def flip_path_horizontal(self):
        """Flip enemy's path's x-directions and initial x-position."""
        # TODO: Figure out how to flip non-linear functions.
        self.image = pygame.transform.flip(self.image, True, False)