class Player(Renderable): def __init__(self, world: 'World'): super().__init__(world.window) self.world = world self.size = Vector(*PLAYER_SIZE) self.pos = world.level.start_pos self.last_pos = self.pos.copy() self.last_x = 0 self.vel = Vector(0, 0) self.accel = Vector(0, 0) self.on_ground = False self.jumping = False self.moving_x = False self.is_dying = False self.desired_platform = None self.score = 0 self.lives = 3 self.sprite_cols = 8 self.sprite = Sprite('assets/player.png', self.sprite_cols, 1) self.roll = 0 def jump(self): self.on_ground = False self.vel.y = -PLAYER_VELOCITY[1] self.accel.y = -ACCEL_GRAVITY def get_bounds(self) -> BoundingBox: pos = self.pos size = self.size return BoundingBox(pos, pos + size) def get_render_bounds(self) -> BoundingBox: bounds = self.get_bounds() dpi_factor = self.window.hidpi_factor return BoundingBox(bounds.min * dpi_factor, bounds.max * dpi_factor) def on_key_down(self, key: int): if not self.is_dying: if key == Key.SPACE: self.jumping = True # Allow holding jump button. if self.on_ground: self.jump() elif key == Key.KEY_A: self.vel.x = -PLAYER_VELOCITY[0] elif key == Key.KEY_D: self.vel.x = PLAYER_VELOCITY[0] def on_key_up(self, key: int): if not self.is_dying: if key == Key.SPACE: self.jumping = False elif key == Key.KEY_A: if self.vel.x < 0: self.vel.x = 0 elif key == Key.KEY_D: if self.vel.x > 0: self.vel.x = 0 def on_death(self): self.lives -= 1 if self.lives == 0: self.world.window.handler = self.world.source else: self.vel.x = 0 self.vel.y = 0 if self.desired_platform is None: self.pos = Vector(PLAYER_RESPAWN_X_OFFSET, -self.size.y) else: bounds = self.desired_platform.get_bounds() if bounds.max.x <= 0: self.pos = Vector(PLAYER_RESPAWN_X_OFFSET, -self.size.y) else: # Place player on platform they last touched self.pos = Vector( bounds.max.x - self.size.x - PLAYER_RESPAWN_X_OFFSET, bounds.min.y - self.size.y) self.is_dying = False def render(self, canvas: simplegui.Canvas): bounds = self.get_bounds() dpi_factor = self.window.hidpi_factor # Draw player. if PLAYER_POTATO: dest_center = self.pos + self.size / 2 index = (self.roll // self.sprite_cols) % self.sprite_cols self.sprite.draw(canvas, dest_center * dpi_factor, self.size * dpi_factor, (index, 0)) else: point_list = [p.multiply(dpi_factor).into_tuple() for p in bounds] color = Color(120, 120, 200) canvas.draw_polygon(point_list, 1, str(color), str(color)) # Update position. self.last_pos = self.pos.copy() self.pos.add(self.vel) self.roll += self.vel.x * 2 / PLAYER_VELOCITY[0] self.roll += 1 if abs(self.vel.x) > PLAYER_VELOCITY[0]: self.vel.x = math.copysign(PLAYER_VELOCITY[0], self.vel.x) # Check collisions position. self.on_ground = False bounds = self.get_bounds() if not self.is_dying: for item in self.world.level.items: if item.collides_with(bounds): item.on_collide(self) self.vel.add(self.accel) # Do gravity and platform collision. if self.on_ground: self.vel.y = 0 self.accel.y = 0 else: self.accel.y = -ACCEL_GRAVITY if bounds.max.y >= WINDOW_SIZE[1] or bounds.min.x <= 0: self.on_death()
class Level(object): """ A game level. """ def __init__(self, world: 'World', level: int, start_pos: Tuple[int, int], scroll: Vector = Vector(0.05, 0)): self.level = level self.start_pos = Vector( start_pos[0] * BLOCK_SIZE, (GRID_SIZE[1] - start_pos[1] - 1) * BLOCK_SIZE ) self.offset = Vector(0, 0) self.scroll = scroll self.items: List[LevelItem] = [] self.finished = False self.counter = 0 self.world = world self.world.source.last_active_level = self self.window_size = world.window.get_size() # Just some initialisation stuff here; less to compute later. self.background_offset = self.window_size[0] / 2 self.background_image = load_image(LEVEL_BACKGROUND_IMAGE) self.bg_size = (self.background_image.get_width(), self.background_image.get_height()) self.bg_center = (self.bg_size[0] / 2, self.bg_size[1] / 2) def get_score(self): return self.world.player.score def add_item(self, item: LevelItem): """ Adds the item to the level. """ self.items.append(item) def finish(self): """ Finishes the level. """ self.finished = True def render(self, world: 'World', canvas: simplegui.Canvas): """ Called on every game tick to render the level. """ # Draw background if LEVEL_USE_BACKGROUND: center_dest1 = ( -(self.offset.x % self.window_size[0]) + self.background_offset, self.window_size[1] / 2 ) center_dest2 = ( center_dest1[0] + self.window_size[0], center_dest1[1] ) canvas.draw_image(self.background_image, self.bg_center, self.bg_size, center_dest1, self.window_size) canvas.draw_image(self.background_image, self.bg_center, self.bg_size, center_dest2, self.window_size) self.counter += 1 if self.counter % BLOCK_SIZE == 0: self.counter = 0 world.player.score += 1 dpi_factor = world.window.hidpi_factor font = world.text_font font_color = world.text_font_color score_text = "SCORE // {0:d}".format(world.player.score) lives_text = "LIVES // {0:d}".format(world.player.lives) canvas.draw_text(score_text, (10 * dpi_factor, 20 * dpi_factor), font.get_size(), str(font_color), font.get_face()) canvas.draw_text(lives_text, (10 * dpi_factor, 40 * dpi_factor), font.get_size(), str(font_color), font.get_face()) # Render items for item in self.items: bounds = item.get_bounds() if bounds.max.x > 0 and bounds.min.x < WINDOW_SIZE[0]: item.render(canvas) # Render player world.player.render(canvas) # Add the level scroll, mutating the current offset. self.offset.add(self.scroll * BLOCK_SIZE) # Load next level. if self.finished: levels = world.levels next_level = self.level + 1 if len(levels) >= next_level: target_level = levels[next_level - 1] world.player.pos = target_level.start_pos world.level = target_level else: world.level = None