def _handle_horizontal_movement(self, dt): # horizontal velocity has been calculated, now we just need to apply it new_position = make_vector(self._velocity.x * dt, 0.) # attempt to move to new position, if we can hitbox = self._get_active_hitbox() offset = self._get_hitbox_offset() hitbox.position = self.position + offset collisions = hitbox.approach(self.mario_entity.position + new_position + offset, tf_dispatch_events=True) self.position = hitbox.position - offset # don't allow off left of screen if self.position.x < self.mario_entity.level.position.x: self._velocity.x = 0 self.position = make_vector(self.mario_entity.level.position.x, self.position.y) # don't allow off right side of world if self.position.x + self.rect.width > self.mario_entity.level.tile_map.width_pixels: self._velocity.x = 0 self.position = make_vector( self.mario_entity.level.tile_map.width_pixels - self.rect.width, self.position.y) if collisions: # immediately stop horizontal movement on horizontal collision self._velocity.x = 0.
def _create_container(self, font): y_offset = 0 frame = Container( make_vector(10, self.get_title_bar_bottom() + 4), (EntityPickerDialog.SIZE[0] - 50, EntityPickerDialog.SIZE[0] - self.get_title_bar_bottom() - 20)) for name, factory in LevelEntity.Factories.items(): if name == "Mario": # want spawner for mario instead continue entity_button = create_button(self.assets.gui_atlas, make_vector(15, y_offset), EntityPickerDialog.BUTTON_SIZE, name, bind_callback_parameters( self._make_selection, name), font, text_color=pygame.Color('white')) frame.add_child(entity_button) y_offset += entity_button.height + 3 return frame, y_offset
def __init__(self, assets, level, scoring_labels: Labels, mario_stats): super().__init__() self.assets = assets self.scoring_labels = scoring_labels self.mario_stats = mario_stats self.level = level def make_centered(surface): return make_vector(*sr.center) - make_vector(surface.get_width() // 2, surface.get_height() // 2) sr = config.screen_rect tc = pygame.Color('white') self.elapsed = 0 self.world_title = Labels.font_large.render(level.title, True, tc).convert_alpha() self.world_title_pos = make_centered(self.world_title) - make_vector(0, 100) self.x = Labels.font_large.render("x", True, tc) self.x_pos = make_centered(self.x) + make_vector(0, 40) little_mario = assets.character_atlas.load_static("mario_stand_right").image # scale it up self.mario_icon = pygame.transform.scale2x(little_mario).convert() self.mario_pos = copy_vector(self.x_pos) - \ make_vector(self.mario_icon.get_width() * 2, self.mario_icon.get_height() // 4) self.lives = Labels.font_large.render(str(mario_stats.lives), True, tc).convert_alpha() self.lives_pos = self.x_pos + make_vector(self.mario_icon.get_width(), 0)
def __init__(self, mario_entity, input_state, collider_manager, jump_small_sound, jump_super_sound): assert mario_entity is not None assert input_state is not None assert collider_manager is not None assert jump_small_sound is not None assert jump_super_sound is not None self.mario_entity = mario_entity self.collider_manager = collider_manager self.input_state = input_state self.jump_small_sound = jump_small_sound self.jump_super_sound = jump_super_sound self.debug_trajectory = JumpTrajectoryVisualizer( ) if config.debug_jumps else None # state values # todo: consider changing from frame counter to delta time, so avoid locking update loop at 1/60 dt self._velocity = Vector2() self._run_frame_counter = 0 # has nothing to do with animation, see ** in smb_playerphysics.png self._use_skid_deceleration = False self._skidding = False self._jump_stats = level_entry_vertical_physics self._falling_gravity = self._jump_stats.gravity self.jumped = False self._facing_right = True # this value is "sticky": if no input, then is dir of last input self._airborne = False self._crouch = False self._enabled = False # create colliders for mario # mario has two different hitboxes: # one is for small mario and crouched super mario # the other is for super mario # # remember that positions of these colliders != mario's position: they can have offsets and different # sizing # todo: should active be included in these hitboxes? self._small_hitbox = Collider.from_entity(mario_entity, self.collider_manager, constants.Block) self._small_hitbox.rect.width, self._small_hitbox.rect.height = rescale_vector( make_vector(10, 14)) self._small_hitbox_offset = rescale_vector(make_vector(3, 2)) self._small_hitbox.position = self.mario_entity.position + self._small_hitbox_offset self._large_hitbox = Collider.from_entity(mario_entity, self.collider_manager, constants.Block) self._large_hitbox.rect.width, self._large_hitbox.rect.height = rescale_vector( make_vector(13, 25)) self._large_hitbox_offset = rescale_vector(make_vector(2, 7)) self._large_hitbox.position = self.mario_entity.position + self._large_hitbox_offset self.airborne_collider = Collider.from_entity(mario_entity, self.collider_manager, constants.Block)
def __init__(self, assets): font = pygame.font.SysFont(None, 24) super().__init__(config.screen_rect.center, TilePickerDialog.SIZE, assets.gui_atlas.load_sliced("tb_frame"), tb_bkg=assets.gui_atlas.load_sliced("tb_frame"), additional_height=8, text_start_offset=(12, 5), font=font, title="Tiles") self.tileset = assets.tileset # contents window self.scrollable = ScrollableContents( make_vector(6, self.get_title_bar_bottom()), (TilePickerDialog.SIZE[0] - 30, TilePickerDialog.SIZE[1] - self.get_title_bar_bottom() - 26), self._create_tileset_surface()) self.layout() # ensure scrollable is positioned # create scrollbars self.vertical_scroll = create_slider( assets.gui_atlas, make_vector(self.scrollable.rect.right + 4, self.scrollable.rect.top + 10), self.scrollable.rect.height - 15, 0, max(0, self.tileset.surface.get_height() - self.scrollable.height), self._on_scroll_changed, thumb=assets.gui_atlas.load_static("sb_thumb"), thumb_mo=assets.gui_atlas.load_static("sb_thumb_light"), sb_type=ScrollbarType.VERTICAL) self.horizontal_scroll = create_slider( assets.gui_atlas, make_vector(self.scrollable.rect.left + 5, self.scrollable.rect.bottom + 5), self.scrollable.rect.width, 0, max(0, self.tileset.surface.get_width() - self.scrollable.width), self._on_scroll_changed, thumb=assets.gui_atlas.load_static("sb_thumb"), thumb_mo=assets.gui_atlas.load_static("sb_thumb_light"), sb_type=ScrollbarType.HORIZONTAL) self.add_child(self.scrollable) self.add_child(self.vertical_scroll) self.add_child(self.horizontal_scroll) self.layout() self.selected_tile_idx = 0 self.bring_to_front(self.title_bar)
def __init__(self, width, height): self.width = width self.height = height self.walls = [] self.connections = [ make_vector((30, 0)), make_vector((-30, 0)), make_vector((0, 30)), make_vector((0, -30)) ]
def __init__(self, entity, collider_manager, parameters, patrol_range): super().__init__(entity, collider_manager, parameters) self.patrol_range = patrol_range # set up edge detection collider self.edge_detector = Collider.from_entity(entity, collider_manager, constants.Block) self._left_offset = make_vector(-self.entity.rect.width, 1) self._right_offset = make_vector(self.entity.rect.width, 1) # state self._patrolled = 0.
def draw_selection_square(screen, level_map, color, view_rect): tile_coords = pixel_coords_to_tile_coords( pygame.mouse.get_pos() + make_vector(view_rect.x, view_rect.y), level_map.tileset) if level_map.is_in_bounds(tile_coords): r = pygame.Rect( *tile_coords_to_pixel_coords(tile_coords, level_map.tileset), level_map.tileset.tile_width, level_map.tileset.tile_height) r.topleft -= make_vector(*view_rect.topleft) pygame.gfxdraw.rectangle(screen, r, color)
def launch_fireball(self): # initial velocity of fireball depends on direction initial_velocity = make_vector(fireball_parameters.max_horizontal_velocity, fireball_parameters.max_vertical_velocity)\ if self.level.mario.movement.is_facing_right else \ make_vector(-fireball_parameters.max_horizontal_velocity, fireball_parameters.max_vertical_velocity) fb = Fireball(self.level, fireball_parameters, initial_velocity) fb.position = self._get_fireball_position() self.level.entity_manager.register(fb) self.level.asset_manager.sounds['fireball'].play() self.level.mario.animator.throw()
def __init__(self, level): self.pole = level.asset_manager.interactive_atlas.load_static( "flag_pole") self.flag = level.asset_manager.interactive_atlas.load_static("flag") self.level = level self.mario = level.mario self.flag_offset = rescale_vector(make_vector( -13, 16)) # from top-left of pole self.flag_movement = make_vector(0, 0) self._pole_height = self.pole.get_rect( ).height - self.flag_offset.y - self.flag.height super().__init__(self.pole.get_rect())
def __init__(self, game_events, stats, scoring_labels): super().__init__(game_events) self.scoring_labels = scoring_labels self._finished = False self.game_over = Labels.font.render("TIME", True, pygame.Color('white')) self.game_over_pos = make_vector( *config.screen_rect.center) - make_vector( self.game_over.get_width() // 2, self.game_over.get_height() // 2) pygame.mixer_music.stop() self._time_left = TimeOut.DURATION
def set_foot_y_coord(self, ycoord): r = self._get_active_hitbox().rect.copy() r.bottom = ycoord self.position = make_vector(self.position.x, r.top - self._get_hitbox_offset()[1])
def __init__(self, entity, collider_manager, parameters, movement_mask=constants.Block | constants.Enemy, on_collision_callback=None): assert entity is not None assert collider_manager is not None assert parameters is not None super().__init__() self.entity = entity self.collider_manager = collider_manager self.on_collision = on_collision_callback self.horizontal_movement_collider = Collider.from_entity( entity, collider_manager, movement_mask) self.parameters = parameters # type: CharacterParameters self.velocity = make_vector(0., 0.) self.vertical_movement = GravityMovement(entity, collider_manager, parameters) self.vertical_movement.airborne_collider.mask = movement_mask & constants.Block # allow other things to collide with our movement collider. Note they # must have a mask that hits Layer.Enemy to do this collider_manager.register(self.horizontal_movement_collider)
def __init__(self, relative_position, sb_type, width_or_height, sb_background, sb_button_background, sb_max_value, sb_min_value=0, sb_button_mouseover=None, on_value_changed_callback=None): # todo: to implement anchor, some assumptions in positioning code need fixing. low priority for now if sb_type == ScrollbarType.VERTICAL: initial_rect = Rect(*relative_position, Scrollbar.VERTICAL_WIDTH, width_or_height) else: initial_rect = Rect(*relative_position, width_or_height, Scrollbar.HORIZONTAL_HEIGHT) super().__init__(relative_position, initial_rect) assert sb_min_value <= sb_max_value self.sb_type = sb_type self.background = sb_background self.sb_button = sb_button_background self.max_value = sb_max_value self.min_value = sb_min_value self.on_value_changed = None # create slider button self.slider = _SliderButton(make_vector(self.width // 2, self.height // 2), None, self, sb_button_background, sb_button_mouseover) self.add_child(self.slider) self.layout() self.value = sb_min_value self.on_value_changed = on_value_changed_callback
def __init__(self, assets, entity_manager, stats, title="Not Titled"): super().__init__() assert entity_manager is not None assert assets is not None self.entity_manager = entity_manager self.tile_map = TileMap((60, 20), assets.tileset) self.collider_manager = ColliderManager(self.tile_map) self.background_color = (0, 0, 0) self.filename = "" self.normal_physics = True self.stats = stats self.title = title self.loaded_from = "" self.asset_manager = assets self.player_input = PlayerInputHandler() self.mario = Mario(self.player_input, self) self.mario.enabled = False self._scroll_position = make_vector(0, 0) self._view_rect = Rect(0, 0, config.screen_rect.width, config.screen_rect.height) self._cleared = False self._timed_out = False
def create_tool(self, tools, selected_image_name, unselected_image_name, font, selected_cb, deselected_cb, y_offset=0): tool_static = self.gui_atlas.load_static(unselected_image_name) tool_hl_static = self.gui_atlas.load_static(selected_image_name) offset_x = tools[len(tools) - 1].relative_position.x + tool_static.get_rect().width\ if len(tools) > 0 else 10 tool = Option( make_vector(offset_x, self.get_title_bar_bottom() + y_offset + 5), tool_hl_static.get_rect().size, background=self.gui_atlas.load_sliced("control_small_block"), font=font, selected_image=tool_hl_static, unselected_image=tool_static, text="", on_selected_callback=selected_cb, on_deselected_callback=deselected_cb, is_selected=False) self.add_child(tool) tools.append(tool) return tool
def update(self, dt, view_rect): self.collider.position = self.position # is mario standing on the platform? self.platform_tester.update(dt) mario = self.level.mario mario_foot_y = mario.movement.get_foot_position().y if mario.glued or self._pushed: # todo: platform can drag mario into solid blocks, fix pos = self.position pos.y += Platform.FALL_RATE * dt for collision in self.collider.try_move(pos, True): if collision.hit_collider and collision.hit_collider.entity: thing_hit = collision.hit_collider.entity if isinstance(thing_hit, Platform): # shove it out of the way thing_hit.position = make_vector(thing_hit.position.x, thing_hit.position.y + Platform.FALL_RATE * dt) else: self.collider.position = self.position # todo: better way to handle this? hit a block self.position = self.collider.position # want to align mario's feet with our top if he's glued onto the platform if mario.glued: # mario has been glued onto the platform, we're responsible for moving him (ypos) now mario.movement.set_foot_y_coord(self.position.y)
def __init__(self, position, size, background, font, anchor=Anchor.TOP_LEFT, text=None, on_click_callback=None, text_color=config.default_text_color, mouseover_image=None): if size is None or (size[0] == 0 and size[1] == 0): size = background.get_rect().size super().__init__( position, Rect(*position, *size), anchor, ) self._background = background self._background_mouseover = mouseover_image or background self._text = None if text is not None: self._text = Text(make_vector(size[0] // 2 + 1, size[1] // 2 + 2), text, font, text_color, anchor=Anchor.CENTER) self.add_child(self._text) self.on_click = on_click_callback self._click_down = False self._mouseover = False self.layout()
def respawn(self): koopa = KoopaTroopa(self.level) koopa.position = get_aligned_foot_position(self.rect, koopa.rect) koopa.movement.velocity = make_vector(self.original_velocity.x, 0.) # original koopa might've been falling self.level.entity_manager.register(koopa) self.destroy()
def set_foot_position(self, foot_position): r = self._get_active_hitbox().rect.copy() r.midbottom = foot_position self.position = make_vector(r.left - self._get_hitbox_offset()[0], r.top - self._get_hitbox_offset()[1])
def on_hit(self, collision): # note: don't update velocity inside here: it's quite possible multiple collisions have occurred # in the same frame, and if two enemies are very close together, they might end up fighting to # change direction over and over if collision.hit_block: # we hit a block, but it could have been from above. Determine if block is left or right is_left = any(self.horizontal_movement_collider.test( self.horizontal_movement_collider.position - make_vector(1, 0))) is_right = any(self.horizontal_movement_collider.test( self.horizontal_movement_collider.position + make_vector(1, 0))) # only flip directions if it will improve situation if (is_left and self.velocity.x < 0.) or (is_right and self.velocity.x > 0.): self._reverse_direction = True elif collision.hit_collider is not None and collision.hit_collider.layer == constants.Enemy: self._reverse_direction = True
def __init__(self, relative_pos, visible_size, content): super().__init__(relative_pos, initial_rect=Rect(0, 0, *visible_size)) self.content_rect = Rect(0, 0, *content.get_rect().size) self.scroll_pos = make_vector(0, 0) self.content = content self.set_scroll(self.scroll_pos)
def _handle_vertical_movement(self, dt): self.update_airborne() current_velocity = copy_vector(self.velocity) if not self._airborne: current_velocity.y = 0 else: current_velocity.y += (self.parameters.gravity * dt) # limit downward velocity if current_velocity.y > self.parameters.max_vertical_velocity: current_velocity.y = self.parameters.max_vertical_velocity vel = make_vector(0, current_velocity.y) target_pos = self.entity.position + vel * dt self.airborne_collider.position = self.entity.position self.velocity = current_velocity # very important to be accurate with vertical movement because of the single pixel downward we use # for airborne detection. if any(self.airborne_collider.try_move(target_pos, True)): self.airborne_collider.approach(target_pos) self._airborne = False if self.on_hit: self.on_hit() self.entity.position = self.airborne_collider.position
def update(self, dt, view_rect): super().update(dt, view_rect) if self._is_tracking: mario = self.level.mario delta_y = BowserFireball.TRACK_VELOCITY * dt mario_y = mario.movement.get_center_of_mass().y dist = math.fabs(self.position.y - mario_y) if dist < BowserFireball.LOCK_ON_DISTANCE: self._locked_on_time += dt if self._locked_on_time >= BowserFireball.TARGET_ACQUIRE_TIME: self._is_tracking = False else: self._locked_on_time = 0. if self._is_tracking: # approach mario's coordinate if dist < delta_y: # we're close enough to just match it self.position = make_vector(self.position.x, mario_y) else: our_pos = self.position our_pos.y += delta_y * (1. if our_pos.y < mario_y else -1.) # move to this position if we can (if there's a block in the way, stay put and stop tracking) if self.ground_detector.test(our_pos, False): # collided! self._is_tracking = False else: self.position = our_pos
def position(self, new_pos): max_w, max_h = self.tile_map.width_pixels - self._view_rect.width, \ self.tile_map.height_pixels - self._view_rect.height self._scroll_position = make_vector(min(max_w, new_pos[0]), min(max_h, new_pos[1])) self._view_rect.topleft = self._scroll_position
def update(self, dt, view_rect): super().update(dt, view_rect) self.animation.update(dt) self.harm.update(dt) self._timer += dt fraction_hidden = 0. if self._emerging: fraction_hidden = 1 - min( self._timer / PiranhaPlant.EXTEND_PARAMETERS[0], 1.) if self._timer > sum(PiranhaPlant.EXTEND_PARAMETERS): self._timer = 0. self._emerging = False else: # must be retracting fraction_hidden = min( self._timer / PiranhaPlant.RETRACT_PARAMETERS[0], 1.) if self._timer > sum(PiranhaPlant.RETRACT_PARAMETERS): self._timer = 0 self._emerging = True self.position = make_vector( self.visible_rect.left, self.visible_rect.top + self.rect.height * fraction_hidden) self.position_collider.position = self.position
def make_brick_uw(level, values): brick = BrickUw(level, make_vector(0, 0)) if values is not None: brick.deserialize(values) return brick
def explode(self): self._dead = True self.level.entity_manager.unregister(self) explode_anim = self.level.asset_manager.interactive_atlas.load_animation( "fireball_explode") from .corpse import Corpse explosion = Corpse(self.level, explode_anim, Corpse.STATIONARY, explode_anim.duration, True) explosion.position = self.position + make_vector(self.rect.width // 2, self.rect.height // 2) -\ make_vector(explosion.rect.width // 2, explosion.rect.height // 2) self.level.entity_manager.register(explosion)
def update(self, dt): self._elapsed += dt if self._elapsed < LevelCleared.FLAG_SLIDE_TIME: flag_ratio = self._elapsed / LevelCleared.FLAG_SLIDE_TIME self.pole.set_flag_position(flag_ratio) else: if not self.waiting: self.waiting = True self.pole.set_flag_position(1.0) # drop mario and make him move right self.mario.position = make_vector(self.pole.position.x, self.doppler.position.y) self.mario.reset() self.mario.enabled = True self.mario.movement.input_state = _FakeInput() # spawn point value FloatyPoints.display(self.level, self.points, self.pole) # add time and flag to score self.level.stats.score += self.points self.level.stats.score += self.level.stats.remaining_time # now wait for fake mario to reach castle # a trigger should be set up to hide him or move into a pipe self.mario.update(dt, self.level.view_rect) self.level.update_triggers_only(dt)
def draw(self, screen: pygame.Surface, view_rect): super().draw(screen, view_rect) # calc position of selected tile, ignoring scroll position for the moment coords = tile_index_to_coords(self.selected_tile_idx, self.tileset) tile_x = coords[0] * self.tileset.tile_width tile_y = coords[1] * self.tileset.tile_height # account for scrolling top_left = self.scrollable.get_absolute_position() + make_vector( tile_x - self.scrollable.get_scroll().x, tile_y - self.scrollable.get_scroll().y) r = pygame.Rect(*top_left, self.tileset.tile_width, self.tileset.tile_height) r.inflate_ip(2, 2) existing_clip = screen.get_clip() cr = self.rect.copy() cr.top += self.get_title_bar_bottom() cr.height -= self.get_title_bar_bottom() cr.height -= 20 # account for thick frame screen.set_clip(cr) pygame.draw.rect(screen, (255, 0, 0), r, 3) screen.set_clip(existing_clip)