Exemplo n.º 1
0
class PickingHeroView:
    def __init__(self, pygame_screen,
                 images_by_portrait_sprite: Dict[PortraitIconSprite, Any]):
        self.screen_size = pygame_screen.get_size()
        self.screen_render = DrawableArea(pygame_screen)
        self.font_large = pygame.font.Font(DIR_FONTS + 'Merchant Copy.ttf', 32)
        self.font = pygame.font.Font(DIR_FONTS + 'Merchant Copy.ttf', 24)
        self.font_width = 4.5
        self.images_by_portrait_sprite = images_by_portrait_sprite

    def render(self, heroes: List[HeroId], selected_index: int):
        self.screen_render.fill(COLOR_BLACK)
        self.screen_render.rect(
            COLOR_WHITE, Rect(0, 0, self.screen_size[0], self.screen_size[1]),
            1)
        y_0 = 200
        x_mid = self.screen_size[0] // 2
        x_base = x_mid - 175
        self.screen_render.text_centered(self.font_large, "SELECT HERO",
                                         (x_mid, y_0 - 100), 5.5)
        for i, hero in enumerate(heroes):
            hero_data = HEROES[hero]
            sprite = hero_data.portrait_icon_sprite
            image = self.images_by_portrait_sprite[sprite]
            x = x_base + i * (PORTRAIT_ICON_SIZE[0] + 20)
            self.screen_render.image(image, (x, y_0))
            if i == selected_index:
                self.screen_render.rect(
                    COLOR_HIGHLIGHTED_ICON,
                    Rect(x, y_0, PORTRAIT_ICON_SIZE[0], PORTRAIT_ICON_SIZE[1]),
                    3)
            else:
                self.screen_render.rect(
                    COLOR_WHITE,
                    Rect(x, y_0, PORTRAIT_ICON_SIZE[0], PORTRAIT_ICON_SIZE[1]),
                    1)
            self.screen_render.text_centered(
                self.font, hero.name, (x + PORTRAIT_ICON_SIZE[0] // 2,
                                       y_0 + PORTRAIT_ICON_SIZE[1] + 10),
                self.font_width)
        description = HEROES[heroes[selected_index]].description
        description_lines = split_text_into_lines(description, 40)
        y_1 = 350
        for i, description_line in enumerate(description_lines):
            self.screen_render.text(self.font, description_line,
                                    (x_base - 8, y_1 + i * 20))

        y_2 = 500
        self.screen_render.text_centered(self.font, "Choose with arrow-keys",
                                         (x_mid, y_2), self.font_width)
        y_3 = 530
        self.screen_render.text_centered(self.font,
                                         "Press Space/Enter to confirm",
                                         (x_mid, y_3), self.font_width)
Exemplo n.º 2
0
class VictoryScreenScene(AbstractScene):
    def __init__(self, pygame_screen):
        self.screen_size = pygame_screen.get_size()
        self.screen_render = DrawableArea(pygame_screen)
        self.font = pygame.font.Font(DIR_FONTS + 'Merchant Copy.ttf', 24)
        self.time_since_start = Millis(0)

    def run_one_frame(self, time_passed: Millis) -> Optional[SceneTransition]:
        self.time_since_start += time_passed
        return None

    def render(self):
        self.screen_render.fill(COLOR_BLACK)
        x_mid = self.screen_size[0] // 2
        x = x_mid - 280
        lines_y = [150,
                   275, 300,
                   450, 475, 500]
        text_lines = [
            " Well done! You have finished the demo version of this game!",

            "           Don't hesitate to drop any feedback at",
            "   https://github.com/JonathanMurray/python-2d-game/issues  ",

            "                      /",
            "                  O===[====================-",
            "                      \\"
        ]

        num_chars_to_show = self.time_since_start // 30
        accumulated = 0
        for i in range(len(text_lines)):
            if num_chars_to_show > accumulated:
                line = text_lines[i]
                self.screen_render.text(self.font, line[:num_chars_to_show - accumulated], (x, lines_y[i]))
                accumulated += len(line)
Exemplo n.º 3
0
class GameWorldView:

    def __init__(self, pygame_screen, camera_size: Tuple[int, int], screen_size: Tuple[int, int],
                 images_by_sprite: Dict[Sprite, Dict[Direction, List[ImageWithRelativePosition]]]):
        pygame.font.init()
        self.screen_render = DrawableArea(pygame_screen)
        self.ui_render = DrawableArea(pygame_screen, self._translate_ui_position_to_screen)
        self.world_render = DrawableArea(pygame_screen, self._translate_world_position_to_screen)

        self.ui_screen_area = Rect(0, camera_size[1], screen_size[0], screen_size[1] - camera_size[1])
        self.camera_size = camera_size
        self.screen_size = screen_size

        self.font_npc_action = pygame.font.Font(DIR_FONTS + 'Monaco.dfont', 12)
        self.font_debug_info = pygame.font.Font(DIR_FONTS + 'Arial Rounded Bold.ttf', 19)
        self.font_visual_text_small = pygame.font.Font(DIR_FONTS + 'Courier New Bold.ttf', 12)
        self.font_visual_text = pygame.font.Font(DIR_FONTS + 'Courier New Bold.ttf', 14)
        self.font_visual_text_large = pygame.font.Font(DIR_FONTS + 'Courier New Bold.ttf', 16)
        self.font_quest_giver_mark = pygame.font.Font(DIR_FONTS + 'Courier New Bold.ttf', 28)

        self.images_by_sprite: Dict[Sprite, Dict[Direction, List[ImageWithRelativePosition]]] = images_by_sprite

        # This is updated every time the view is called
        self.camera_world_area = None

    # ------------------------------------
    #         TRANSLATING COORDINATES
    # ------------------------------------

    def _translate_world_position_to_screen(self, world_position):
        return (self._translate_world_x_to_screen(world_position[0]),
                self._translate_world_y_to_screen(world_position[1]))

    def _translate_screen_position_to_world(self, screen_position):
        return int(screen_position[0] + self.camera_world_area.x), int(screen_position[1] + self.camera_world_area.y)

    def _translate_world_x_to_screen(self, world_x):
        return int(world_x - self.camera_world_area.x)

    def _translate_world_y_to_screen(self, world_y):
        return int(world_y - self.camera_world_area.y)

    def _translate_ui_position_to_screen(self, position):
        return position[0] + self.ui_screen_area.x, position[1] + self.ui_screen_area.y

    def _translate_screen_position_to_ui(self, position: Tuple[int, int]):
        return position[0] - self.ui_screen_area.x, position[1] - self.ui_screen_area.y

    # ------------------------------------
    #       DRAWING THE GAME WORLD
    # ------------------------------------

    def _world_ground(self, entire_world_area: Rect):
        grid_width = 35
        # TODO num squares should depend on map size. Ideally this dumb looping logic should change.
        num_squares = 200
        column_screen_y_0 = self._translate_world_y_to_screen(self.camera_world_area.y)
        column_screen_y_1 = self._translate_world_y_to_screen(
            min(entire_world_area.y + entire_world_area.h, self.camera_world_area.y + self.camera_world_area.h))
        for i_col in range(num_squares):
            world_x = entire_world_area.x + i_col * grid_width
            if entire_world_area.x < world_x < entire_world_area.x + entire_world_area.w:
                screen_x = self._translate_world_x_to_screen(world_x)
                self.screen_render.line(COLOR_BACKGROUND_LINES, (screen_x, column_screen_y_0),
                                        (screen_x, column_screen_y_1),
                                        1)
        row_screen_x_0 = self._translate_world_x_to_screen(self.camera_world_area.x)
        row_screen_x_1 = self._translate_world_x_to_screen(
            min(entire_world_area.x + entire_world_area.w, self.camera_world_area.x + self.camera_world_area.w))
        for i_row in range(num_squares):
            world_y = entire_world_area.y + i_row * grid_width
            if entire_world_area.y < world_y < entire_world_area.y + entire_world_area.h:
                screen_y = self._translate_world_y_to_screen(world_y)
                self.screen_render.line(COLOR_BACKGROUND_LINES, (row_screen_x_0, screen_y), (row_screen_x_1, screen_y),
                                        1)

        if RENDER_WORLD_COORDINATES:
            for i_col in range(num_squares):
                for i_row in range(num_squares):
                    if i_col % 4 == 0 and i_row % 4 == 0:
                        world_x = entire_world_area.x + i_col * grid_width
                        screen_x = self._translate_world_x_to_screen(world_x)
                        world_y = entire_world_area.y + i_row * grid_width
                        screen_y = self._translate_world_y_to_screen(world_y)
                        self.screen_render.text(self.font_debug_info, str(world_x) + "," + str(world_y),
                                                (screen_x, screen_y),
                                                (250, 250, 250))

    def _world_entity(self, entity: Union[WorldEntity, DecorationEntity]):
        if not entity.visible:
            return
        if entity.sprite is None:
            raise Exception("Entity has no sprite value: " + str(entity))
        elif entity.sprite in self.images_by_sprite:
            image_with_relative_position = self._get_image_for_sprite(
                entity.sprite, entity.direction, entity.movement_animation_progress)
            self.world_render.image_with_relative_pos(image_with_relative_position, entity.get_position())
        elif entity.sprite == Sprite.NONE:
            # This value is used by entities that don't use sprites. They might have other graphics (like VisualEffects)
            pass
        else:
            raise Exception("Unhandled sprite: " + str(entity.sprite))

    def _get_image_for_sprite(self, sprite: Sprite, direction: Direction,
                              animation_progress: float) -> ImageWithRelativePosition:

        images: Dict[Direction, List[ImageWithRelativePosition]] = self.images_by_sprite[sprite]
        if direction in images:
            images_for_this_direction = images[direction]
        else:
            images_for_this_direction = next(iter(images.values()))

        animation_frame_index = int(len(images_for_this_direction) * animation_progress)
        return images_for_this_direction[animation_frame_index]

    def _visual_effect(self, visual_effect):
        if isinstance(visual_effect, VisualLine):
            self._visual_line(visual_effect)
        elif isinstance(visual_effect, VisualCircle):
            self._visual_circle(visual_effect)
        elif isinstance(visual_effect, VisualRect):
            self._visual_rect(visual_effect)
        elif isinstance(visual_effect, VisualCross):
            self._visual_cross(visual_effect)
        elif isinstance(visual_effect, VisualText):
            self._visual_text(visual_effect)
        elif isinstance(visual_effect, VisualSprite):
            self._visual_sprite(visual_effect)
        elif isinstance(visual_effect, VisualParticleSystem):
            self._visual_particle_system(visual_effect)
        else:
            raise Exception("Unhandled visual effect: " + str(visual_effect))

    def _visual_line(self, line: VisualLine):
        self.world_render.line(line.color, line.start_position, line.end_position, line.line_width)

    def _visual_circle(self, visual_circle: VisualCircle):
        position = visual_circle.circle()[0]
        radius = visual_circle.circle()[1]
        self.world_render.circle(visual_circle.color, position, radius, visual_circle.line_width)

    def _visual_rect(self, visual_rect: VisualRect):
        self.world_render.rect(visual_rect.color, visual_rect.rect(), visual_rect.line_width)

    def _visual_cross(self, visual_cross: VisualCross):
        for start_pos, end_pos in visual_cross.lines():
            self.world_render.line(visual_cross.color, start_pos, end_pos, visual_cross.line_width)

    def _visual_text(self, visual_text: VisualText):
        text = visual_text.text
        position = visual_text.position()
        # Adjust position so that long texts don't appear too far to the right
        translated_position = (position[0] - 3 * len(text), position[1])
        # limit the space long texts claim on the screen (example "BLOCK" and "DODGE")
        if len(text) >= 4:
            font = self.font_visual_text_small
        elif visual_text.emphasis:
            font = self.font_visual_text_large
        else:
            font = self.font_visual_text
        self.world_render.text(font, text, translated_position, visual_text.color)

    def _visual_sprite(self, visual_sprite: VisualSprite):
        position = visual_sprite.position
        animation_progress = visual_sprite.animation_progress()
        sprite = visual_sprite.sprite
        if sprite in self.images_by_sprite:
            image_with_relative_position = self._get_image_for_sprite(
                sprite, Direction.DOWN, animation_progress)
            self.world_render.image_with_relative_pos(image_with_relative_position, position)
        else:
            raise Exception("Unhandled sprite: " + str(sprite))

    def _visual_particle_system(self, visual_particle_system: VisualParticleSystem):
        for particle in visual_particle_system.particles():
            self.world_render.rect_transparent(particle.rect, particle.alpha, particle.color)

    def _stat_bar_for_world_entity(self, world_entity, h, relative_y, ratio, color, border_color=None):
        self.world_render.stat_bar(world_entity.x + 1, world_entity.y + relative_y,
                                   world_entity.pygame_collision_rect.w - 2, h, ratio, color, border_color=border_color)

    def _entity_action_text(self, entity_action_text: EntityActionText):
        entity_center_pos = entity_action_text.entity.get_center_position()
        header_prefix = "[Space] "
        header_line = header_prefix + entity_action_text.text
        detail_lines = []
        for detail_entry in entity_action_text.details:
            detail_lines += split_text_into_lines(detail_entry, 30)
        if detail_lines:
            line_length = max(max([len(line) for line in detail_lines]), len(header_line))
        else:
            line_length = len(header_line)
        rect_width = line_length * 8
        rect_height = 16 + len(detail_lines) * 16
        rect_pos = (entity_center_pos[0] - rect_width // 2, entity_center_pos[1] - 60)
        self.world_render.rect_transparent(Rect(rect_pos[0], rect_pos[1], rect_width, rect_height), 150, (0, 0, 0))
        if entity_action_text.style == EntityActionTextStyle.LOOT_RARE:
            color = (190, 150, 250)
        elif entity_action_text.style == EntityActionTextStyle.LOOT_UNIQUE:
            color = (250, 250, 150)
        else:
            color = (255, 255, 255)
        self.world_render.text(self.font_npc_action, header_prefix, (rect_pos[0] + 4, rect_pos[1]))
        prefix_w = self.font_npc_action.size(header_prefix)[0]
        self.world_render.text(self.font_npc_action, entity_action_text.text, (rect_pos[0] + 4 + prefix_w, rect_pos[1]),
                               color)
        for i, detail_line in enumerate(detail_lines):
            self.world_render.text(self.font_npc_action, detail_line, (rect_pos[0] + 4, rect_pos[1] + (i + 1) * 16))

    def _quest_giver_mark(self, npc: NonPlayerCharacter):
        entity_pos = npc.world_entity.get_center_position()
        if npc.quest_giver_state == QuestGiverState.CAN_GIVE_NEW_QUEST:
            color = (255, 215, 0)
            mark = "!"
        elif npc.quest_giver_state == QuestGiverState.WAITING_FOR_PLAYER:
            color = (192, 192, 192)
            mark = "?"
        else:
            color = (255, 215, 0)
            mark = "?"
        self.world_render.text(self.font_quest_giver_mark, mark, (entity_pos[0] - 8, entity_pos[1] - 64), (0, 0, 0))
        self.world_render.text(self.font_quest_giver_mark, mark, (entity_pos[0] - 9, entity_pos[1] - 65), color)

    def render_world(self, all_entities_to_render: List[WorldEntity], decorations_to_render: List[DecorationEntity],
                     camera_world_area, non_player_characters: List[NonPlayerCharacter], is_player_invisible: bool,
                     player_active_buffs: List[BuffWithDuration],
                     player_entity: WorldEntity, visual_effects, render_hit_and_collision_boxes, player_health,
                     player_max_health, entire_world_area: Rect, entity_action_text: Optional[EntityActionText]):
        self.camera_world_area = camera_world_area

        self.screen_render.fill(COLOR_BACKGROUND)
        self._world_ground(entire_world_area)

        all_entities_to_render.sort(key=lambda entry: (-entry.view_z, entry.y))

        for decoration_entity in decorations_to_render:
            self._world_entity(decoration_entity)

        for entity in all_entities_to_render:
            self._world_entity(entity)
            if entity == player_entity and is_player_invisible:
                self.world_render.rect((200, 100, 250), player_entity.rect(), 2)

        player_sprite_y_relative_to_entity = \
            ENTITY_SPRITE_INITIALIZERS[player_entity.sprite][Direction.DOWN].position_relative_to_entity[1]
        if player_entity.visible:
            self._stat_bar_for_world_entity(player_entity, 5, player_sprite_y_relative_to_entity - 5,
                                            player_health / player_max_health, (100, 200, 0))

        # Buffs related to channeling something are rendered above player's head with progress from left to right
        for buff in player_active_buffs:
            if buff.buff_effect.get_buff_type() in CHANNELING_BUFFS:
                ratio = 1 - buff.get_ratio_duration_remaining()
                self._stat_bar_for_world_entity(player_entity, 3, player_sprite_y_relative_to_entity - 11, ratio,
                                                (150, 150, 250))

        if render_hit_and_collision_boxes:
            for entity in all_entities_to_render:
                self.world_render.rect((250, 250, 250), entity.rect(), 1)

        for npc in non_player_characters:
            if npc.is_enemy:
                if npc.is_boss:
                    healthbar_color = (255, 215, 0)
                    border_color = COLOR_RED
                else:
                    healthbar_color = COLOR_RED
                    border_color = None
            else:
                healthbar_color = (250, 250, 0)
                border_color = None
                if npc.quest_giver_state is not None:
                    self._quest_giver_mark(npc)

            npc_sprite_y_relative_to_entity = \
                ENTITY_SPRITE_INITIALIZERS[npc.world_entity.sprite][Direction.DOWN].position_relative_to_entity[1]
            if not npc.is_neutral:
                self._stat_bar_for_world_entity(npc.world_entity, 3, npc_sprite_y_relative_to_entity - 5,
                                                npc.health_resource.get_partial(), healthbar_color, border_color)
            if npc.active_buffs:
                buff = npc.active_buffs[0]
                if buff.should_duration_be_visualized_on_enemies():
                    self._stat_bar_for_world_entity(npc.world_entity, 2, npc_sprite_y_relative_to_entity - 9,
                                                    buff.get_ratio_duration_remaining(), (250, 250, 250))
        for visual_effect in visual_effects:
            self._visual_effect(visual_effect)

        if entity_action_text:
            self._entity_action_text(entity_action_text)
Exemplo n.º 4
0
class MainMenuView:
    def __init__(self, pygame_screen,
                 images_by_portrait_sprite: Dict[PortraitIconSprite, Any]):
        self._screen_size = pygame_screen.get_size()
        self._screen_render = DrawableArea(pygame_screen)
        self._font = pygame.font.Font(DIR_FONTS + 'Merchant Copy.ttf', 24)
        self._images_by_portrait_sprite = images_by_portrait_sprite
        self._font_width = 2.5

    def render(self, saved_characters: List[SavedPlayerState],
               selected_option_index: int, first_shown_index: int):
        self._screen_render.fill(COLOR_BLACK)
        self._screen_render.rect(
            COLOR_WHITE, Rect(0, 0, self._screen_size[0],
                              self._screen_size[1]), 1)
        x_mid = self._screen_size[0] // 2
        x = x_mid - 175
        x_text = x + PORTRAIT_ICON_SIZE[0] + 25
        y = 50

        if len(saved_characters) == 1:
            top_text = "You have 1 saved character"
        else:
            top_text = "You have " + str(
                len(saved_characters)) + " saved characters"
        x_mid = 350
        self._screen_render.text_centered(self._font, top_text, (x_mid, y),
                                          self._font_width)
        y += 100

        w_rect = 340
        h_rect = 80
        padding = 5
        for i in range(
                first_shown_index,
                min(len(saved_characters),
                    first_shown_index + NUM_SHOWN_SAVE_FILES)):
            saved_player_state = saved_characters[i]
            color = COLOR_HIGHLIGHTED_RECT if i == selected_option_index else COLOR_RECT
            self._screen_render.rect(color, Rect(x, y, w_rect, h_rect), 2)
            image = self._get_portrait_image(saved_player_state)
            self._screen_render.image(image, (x + padding, y + padding))
            self._screen_render.rect(
                COLOR_WHITE,
                Rect(x + padding, y + padding, PORTRAIT_ICON_SIZE[0],
                     PORTRAIT_ICON_SIZE[1]), 1)
            text_1 = saved_player_state.hero_id + " LEVEL " + str(
                saved_player_state.level)

            self._screen_render.text(self._font, text_1, (x_text, y + 20))
            text_2 = "played time: " + _get_time_str(
                saved_player_state.total_time_played_on_character)
            self._screen_render.text(self._font, text_2, (x_text, y + 45))
            y += 90

        y += 40
        color = COLOR_HIGHLIGHTED_RECT if selected_option_index == len(
            saved_characters) else COLOR_RECT
        self._screen_render.rect(color, Rect(x, y, w_rect, h_rect), 2)
        self._screen_render.text_centered(self._font, "CREATE NEW CHARACTER",
                                          (x_mid, y + 32), self._font_width)

    def _get_portrait_image(self, saved_player_state):
        hero_data: HeroData = HEROES[HeroId[saved_player_state.hero_id]]
        sprite = hero_data.portrait_icon_sprite
        image = self._images_by_portrait_sprite[sprite]
        return image