class MG(arcade.Window):
    """メインウィンドウを表示し、キーボードとマウスの操作を行います"""

    def __init__(self, width, height, title):
        """
        Args:
            width = 画面の幅
            height = 画面の高さ
            title = タイトル
            antialiasing = 画像にアンチエイリアスを掛けるかどうか
        """
        super().__init__(width, height, title, antialiasing=False)

        self.engine = GameEngine()

        self.player_direction = None
        self.viewports = None
        self.choice = 0 # messagewindowの選択
        self.game_dict = None# saveファイルの格納に使う

    def setup(self):
        """LOOK機能、character_screen画面, level_up画面、ノーマルstate時のUIの初期化を行う
        ミニマップ作成の情報もここで渡す
        """
        self.engine.setup()
        viewport(self.engine.player.center_x, self.engine.player.center_y)

        # ここでminimapの作成を行う
        # ------------------------------------------------------------------------------

        # ミニマップの描画位置指定
        screen_size = (SCREEN_WIDTH, SCREEN_HEIGHT)
        self.program = self.ctx.load_program(
            vertex_shader=arcade.resources.shaders.vertex.default_projection,
            fragment_shader=arcade.resources.shaders.fragment.texture)

        # ピクセルの色を保存するために色の添付ファイルを追加します
        self.color_attachment = self.ctx.texture((screen_size), components=4,
                                                 filter=(gl.GL_NEAREST, gl.GL_NEAREST))

        # 必要な色を添付したフレームバッファを作成する
        self.offscreen = self.ctx.framebuffer(
            color_attachments=[self.color_attachment])
        self.quad_fs = geometry.quad_2d_fs()
        self.mini_map_quad = geometry.quad_2d(
            size=(0.31, .31), pos=(.824, -.8))
        # -------------------------------------------------------------------------------

        # Lコマンドで呼び出すlook機能
        self.select_UI = SelectUI(engine=self.engine)
        
        self.character_UI = CharacterScreenUI(engine=self.engine)


        self.level_up_window = LevelupUI()
        self.level_up_flower = LevelUpFlower(self.engine)
        self.normal_UI = NormalUI(self.engine)





    def draw_sprites(self):
        """ 全てのスプライトリストをここで描画する """
        # 背景が表示されないように最初に黒で塗りつぶす、他の方法を考えないと…
        arcade.draw_rectangle_filled(-1000, -1000,
                                     10000, 10000, color=arcade.color.BLACK)
        # 以下スプライトリスト
        self.engine.cur_level.floor_sprites.draw(filter=gl.GL_NEAREST)
        self.engine.cur_level.wall_sprites.draw()
        self.engine.cur_level.map_obj_sprites.draw(filter=gl.GL_LO_BIAS_NV)
        self.engine.cur_level.item_sprites.draw(filter=gl.GL_NEAREST)
        self.engine.cur_level.actor_sprites.draw(filter=gl.GL_NEAREST)
        self.engine.flower_sprites.draw(filter=gl.GL_LO_BIAS_NV)
        self.engine.cur_level.chara_sprites.draw(filter=gl.GL_NEAREST)
        self.engine.cur_level.equip_sprites.draw(filter=gl.GL_NEAREST)

        TMP_EFFECT_SPRITES.draw(filter=gl.GL_NEAREST)
        for e in TMP_EFFECT_SPRITES:
            if hasattr(e, "emitter"):
                e.emitter.draw()

    def on_draw(self):
        """全画像の表示"""

        arcade.start_render()

        # minimap start ------------------------------------------------------------------------------------------------------------
        """minimap draw"""
        if self.engine.game_state == GAME_STATE.NORMAL:
            self.offscreen.use()
            # self.offscreen.clear(arcade.color.BLACK)

            arcade.set_viewport(0, GAME_GROUND_WIDTH, 0,
                                GAME_GROUND_HEIGHT+GRID_SIZE*2)

            self.engine.cur_level.map_point_sprites.draw()
            arcade.draw_rectangle_filled(center_x=self.engine.player.center_x,
                                         center_y=self.engine.player.center_y, width=45, height=45, color=arcade.color.BLUE)

            self.engine.cur_level.item_point_sprites.draw()

        self.use()
        self.color_attachment.use(0)
        self.quad_fs.render(self.program)
        # minimap end -------------------------------------------------------------------------------------------------------------




            
        # アタック時はビューポート固定する
        if self.engine.game_state == GAME_STATE.NORMAL:
            if self.engine.player.state == state.ON_MOVE:
                viewport(self.engine.player.center_x, self.engine.player.center_y)
                # viewport(self.engine.player.from_x, self.engine.player.from_y)
            else:
                x, y = (grid_to_pixel(self.engine.player.x, self.engine.player.y))
                viewport(x,y)

        # ビューポート情報取得(この位置で取得しないとバグる)
        self.viewports = arcade.get_viewport()

        # arcadeの光源効果
        with self.engine.light_layer:
            self.draw_sprites()
            self.engine.light_layer.draw(ambient_color=(1,1,1))


        # ノーマルステート時の画面表示
        if self.engine.game_state == GAME_STATE.NORMAL or self.engine.game_state == GAME_STATE.DELAY_WINDOW:
            self.normal_UI.draw_in_normal_state(self.viewports)
            # damage表示
            for i in self.engine.damage_pop:
                if i.dist > 40:
                    self.engine.damage_pop.remove(i)
                elif i.dist < 30:
                    i.draw()


        # Character_Screen表示
        elif self.engine.game_state == GAME_STATE.CHARACTER_SCREEN:
            self.character_UI.draw_character_screen(arcade.get_viewport(), self.engine.selected_item)
            
        # inventory表示
        elif self.engine.game_state == GAME_STATE.INVENTORY:
            draw_inventory(self.engine, self.engine.selected_item, self.viewports)

        # level_up画面表示
        elif self.engine.game_state == GAME_STATE.LEVEL_UP_WINDOW:
            self.level_up_window.window_pop(self.viewports, self.engine)
        elif self.engine.game_state == GAME_STATE.LEVEL_UP_FLOWER:
            self.level_up_flower.window_pop(self.viewports)

        # LOOKシステム
        elif self.engine.game_state == GAME_STATE.SELECT_LOCATION or self.engine.game_state == GAME_STATE.LOOK:
            # lookカーソルにviewportを渡す
            x, y = grid_to_pixel(self.select_UI.grid_select[0]+self.engine.player.x, self.select_UI.grid_select[1]+self.engine.player.y)
            viewport(x, y)
            # Lookメイン関数
            self.select_UI.draw_in_select_ui(self.viewports, self.engine)


        # draw the mini_map(この位置に置かないとバグる)
        if self.engine.game_state == GAME_STATE.NORMAL:
            self.color_attachment.use(0)
            self.mini_map_quad.render(self.program)

                    
    def on_resize(self, width: float, height: float):
        # 光源処理効果の為に必要、まだ理解していない
        self.engine.light_layer.resize(width, height)

    def on_update(self, delta_time):
        """全てのスプライトリストのアップデートを行う
           他にアクションキュー、ターンチェンジ、pcの移動とviewport、expのチェック
        """
        self.engine.process_action_queue(delta_time)


        if self.engine.game_state == GAME_STATE.NORMAL:

            self.engine.cur_level.chara_sprites.update()
            self.engine.cur_level.chara_sprites.update_animation(delta_time)
            self.engine.cur_level.actor_sprites.update()
            self.engine.cur_level.actor_sprites.update_animation(delta_time)
            self.engine.cur_level.equip_sprites.update()
            self.engine.cur_level.equip_sprites.update_animation()
            self.engine.flower_sprites.update()
            self.engine.flower_sprites.update_animation()
            self.engine.cur_level.map_obj_sprites.update_animation()
            TMP_EFFECT_SPRITES.update()
            TMP_EFFECT_SPRITES.update_animation()


            self.engine.normal_state_update(self.player_direction, delta_time)

            self.engine.player_light.position = self.engine.player.position







    def on_key_press(self, key, modifiers):
           # auto_moveキャンセル処理
        if self.engine.player.tmp_state == state.AUTO:
            self.engine.player.tmp_state = state.READY
        if self.engine.player.state == state.AUTO:
            self.engine.player.state = state.READY
            
        # backspace_keyでwindowを閉じた時にjsonにダンプする
        if key == arcade.key.BACKSPACE:
            self.engine.game_state = GAME_STATE.DELAY_WINDOW
            print("save")
            self.game_dict = self.engine.get_dict()

            with open("game_save.json", "w") as write_file:
                json.dump(self.game_dict, write_file, indent=4, sort_keys=True, check_circular=False) 
                
            arcade.close_window()
        # delete_keyで即windowを閉じる
        if key == arcade.key.DELETE:
            arcade.close_window()


                     
        # playerの移動
        self.engine.move_switch = True
        if self.engine.game_state == GAME_STATE.NORMAL:
            self.player_direction = keymap(key, self.engine)


        # ドア開閉
        if self.engine.player.state == state.DOOR:
            door_check = door_key(key, self.engine)
            if door_check:
                self.player_direction = None
                self.engine.action_queue.extend([{"use_door": door_check}])

        # Lコマンド時、スクロール仕様時などのカーソル移動と選択
        elif self.engine.game_state == GAME_STATE.SELECT_LOCATION or self.engine.game_state == GAME_STATE.LOOK:
            grid_select_key(key, self.select_UI)

        # インベントリを操作する
        elif self.engine.game_state == GAME_STATE.INVENTORY:
            inventory_key(key, self.engine)


        # Level states up処理
        elif self.engine.game_state == GAME_STATE.LEVEL_UP_WINDOW:
            self.level_up_window.states_choices(key)
        elif self.engine.game_state == GAME_STATE.LEVEL_UP_FLOWER:
            self.level_up_flower.states_choices(key)

 

        # キャラクタースクリーンでスキルのオンオフ操作
        elif self.engine.game_state == GAME_STATE.CHARACTER_SCREEN:
            character_screen_key(key, self.engine)
        

        if key == arcade.key.F7:

            self.save()
        elif key == arcade.key.F8:

            self.load()
        elif key == arcade.key.F1:
            from level_up_sys import check_experience_level

            self.engine.player.fighter.current_xp += 70
            self.engine.player.equipment.item_exp_add(70)
            check_experience_level(self.engine.player, self.engine)

        if key == arcade.key.SPACE:
            if self.engine.player_light in self.engine.light_layer:
                self.engine.light_layer.remove(self.engine.player_light)
            else:
                self.engine.light_layer.add(self.engine.player_light)



    def on_key_release(self, key, modifiers):
        self.player_direction = None



    @stop_watch
    def save(self):
        self.game_dict = self.engine.get_dict()

    @stop_watch
    def load(self):
        data = None

        if self.game_dict:
            data = self.game_dict
        else:
            with open("game_save.json", "r") as read_file:
                data = json.load(read_file)
        if data:
            self.engine.restore_from_dict(data)
            viewport(self.engine.player.center_x, self.engine.player.center_y)
Exemple #2
0
class MyGame(arcade.Window):
    """
    Main application class.
    Manage the GUI
    """
    def __init__(self, width: int, height: int, title: str):
        """

        :param width:
        :param height:
        :param title:
        """
        super().__init__(width, height, title, antialiasing=False)

        # Main game engine, where the game is managed
        self.game_engine = GameEngine()

        # Track the current state of what key is pressed
        self.left_pressed = False
        self.right_pressed = False
        self.up_pressed = False
        self.down_pressed = False
        self.up_left_pressed = False
        self.up_right_pressed = False
        self.down_left_pressed = False
        self.down_right_pressed = False

        # Used for auto-repeat of moves
        self.time_since_last_move_check = 0

        # Where is the mouse?
        self.mouse_position: Optional[Tuple[float, float]] = None

        self.mouse_over_text: Optional[str] = None

        # These are sprites that appear as buttons on the character sheet.
        self.character_sheet_buttons = arcade.SpriteList()

        arcade.set_background_color(colors['background'])

    def setup(self):
        """ Set up the game here. Call this function to restart the game. """

        self.game_engine.setup()

        for button_name, y_value in zip(
            ["attack", "defense", "hp", "capacity"],
                range(SCREEN_HEIGHT - 75, 490, -37)):
            sprite = arcade.Sprite("images/plus_button.png")
            sprite.center_x = 200
            sprite.center_y = y_value
            sprite.name = button_name
            self.character_sheet_buttons.append(sprite)

    def draw_hp_and_status_bar(self):
        text = f"HP: {self.game_engine.player.fighter.hp}/{self.game_engine.player.fighter.max_hp}"
        arcade.draw_text(text, 0, 0, colors["status_panel_text"])

        if self.game_engine.player.fighter.level <= len(EXPERIENCE_PER_LEVEL):
            xp_to_next_level = EXPERIENCE_PER_LEVEL[
                self.game_engine.player.fighter.level - 1]
            text = f"XP: {self.game_engine.player.fighter.current_xp:,}/{xp_to_next_level:,}"
        else:
            text = f"XP: {self.game_engine.player.fighter.current_xp:,}"
        arcade.draw_text(text, 100, 0, colors["status_panel_text"])

        text = f"Level: {self.game_engine.player.fighter.level}"
        arcade.draw_text(text, 200, 0, colors["status_panel_text"])

        size = 65
        margin = 2
        draw_status_bar(
            size / 2 + margin,
            24,
            size,
            10,
            self.game_engine.player.fighter.hp,
            self.game_engine.player.fighter.max_hp,
        )

    def draw_inventory(self):
        capacity = self.game_engine.player.inventory.capacity
        selected_item = self.game_engine.selected_item

        field_width = SCREEN_WIDTH / (capacity + 1)
        for i in range(capacity):
            y = 40
            x = i * field_width
            if i == selected_item:
                arcade.draw_lrtb_rectangle_outline(x - 1, x + field_width - 5,
                                                   y + 20, y,
                                                   arcade.color.BLACK, 2)
            if self.game_engine.player.inventory.items[i]:
                item_name = self.game_engine.player.inventory.items[i].name
            else:
                item_name = ""
            text = f"{i + 1}: {item_name}"
            arcade.draw_text(text, x, y, colors["status_panel_text"])

    def draw_mouse_over_text(self):
        if self.mouse_over_text:
            x, y = self.mouse_position
            arcade.draw_xywh_rectangle_filled(x, y, 100, 16,
                                              arcade.color.BLACK)
            arcade.draw_text(self.mouse_over_text, x, y, arcade.csscolor.WHITE)

    def draw_in_normal_state(self):
        self.draw_hp_and_status_bar()
        self.draw_inventory()
        self.handle_and_draw_messages()
        self.draw_mouse_over_text()

    def draw_in_select_location_state(self):

        # If mouse hasn't been over the window yet, return None
        if self.mouse_position is None:
            return

        mouse_x, mouse_y = self.mouse_position
        grid_x, grid_y = pixel_to_char(mouse_x, mouse_y)
        center_x, center_y = char_to_pixel(grid_x, grid_y)
        arcade.draw_rectangle_outline(
            center_x,
            center_y,
            SPRITE_WIDTH,
            SPRITE_HEIGHT,
            arcade.color.LIGHT_BLUE,
            2,
        )

    def draw_character_screen(self):
        arcade.draw_xywh_rectangle_filled(
            0,
            0,
            SCREEN_WIDTH,
            SCREEN_HEIGHT,
            colors["status_panel_background"],
        )

        spacing = 1.8
        y_value = SCREEN_HEIGHT - 50
        x_value = 10

        text_size = 24
        text = "Character Screen"
        arcade.draw_text(text, x_value, y_value, colors['status_panel_text'],
                         text_size)

        y_value -= text_size * spacing
        text_size = 20

        texts = [
            f"Attack: {self.game_engine.player.fighter.power}",
            f"Defense: {self.game_engine.player.fighter.defense}",
            f"HP: {self.game_engine.player.fighter.hp} / {self.game_engine.player.fighter.max_hp}",
            f"Max Inventory: {self.game_engine.player.inventory.capacity}",
            f"Level: {self.game_engine.player.fighter.level}",
        ]

        for text in texts:
            arcade.draw_text(text, x_value, y_value,
                             colors['status_panel_text'], text_size)
            y_value -= text_size * spacing

        if self.game_engine.player.fighter.ability_points > 0:
            self.character_sheet_buttons.draw()

    def handle_and_draw_messages(self):
        # Check message queue. Limit to 2 lines
        while len(self.game_engine.messages) > 2:
            self.game_engine.messages.pop(0)

        # Draw messages
        y = 20
        for message in self.game_engine.messages:
            arcade.draw_text(message, 300, y, colors["status_panel_text"])
            y -= 20

    def draw_sprites_and_status_panel(self):
        # Draw the sprites
        self.game_engine.cur_level.dungeon_sprites.draw(filter=gl.GL_NEAREST)
        self.game_engine.cur_level.entities.draw(filter=gl.GL_NEAREST)
        self.game_engine.cur_level.creatures.draw(filter=gl.GL_NEAREST)
        self.game_engine.characters.draw(filter=gl.GL_NEAREST)

        # Draw the status panel
        arcade.draw_xywh_rectangle_filled(
            0,
            0,
            SCREEN_WIDTH,
            STATUS_PANEL_HEIGHT,
            colors["status_panel_background"],
        )

    def handle_character_screen_click(self, x, y):
        if self.game_engine.player.fighter.ability_points > 0:
            sprites_clicked = arcade.get_sprites_at_point(
                (x, y), self.character_sheet_buttons)
            for sprite in sprites_clicked:
                if sprite.name == "attack":
                    self.game_engine.player.fighter.power += 1
                    self.game_engine.player.fighter.ability_points -= 1
                elif sprite.name == "defense":
                    self.game_engine.player.fighter.defense += 1
                    self.game_engine.player.fighter.ability_points -= 1
                elif sprite.name == "hp":
                    self.game_engine.player.fighter.max_hp += 5
                    self.game_engine.player.fighter.ability_points -= 1
                elif sprite.name == "capacity":
                    self.game_engine.player.inventory.capacity += 1
                    self.game_engine.player.fighter.ability_points -= 1

    def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
        """
        Handle mouse-down events

        :param x:
        :param y:
        :param button:
        :param modifiers:
        """

        # If we are currently in a 'select location' state, process
        if self.game_engine.game_state == SELECT_LOCATION:
            # Grab grid location
            grid_x, grid_y = pixel_to_char(x, y)
            # Notify game engine
            self.game_engine.grid_click(grid_x, grid_y)

        if self.game_engine.game_state == CHARACTER_SCREEN:
            self.handle_character_screen_click(x, y)

    def on_draw(self):
        """
        Render the screen.
        """
        arcade.start_render()

        self.draw_sprites_and_status_panel()

        if self.game_engine.game_state == NORMAL:
            self.draw_in_normal_state()
        elif self.game_engine.game_state == SELECT_LOCATION:
            self.draw_in_select_location_state()
        elif self.game_engine.game_state == CHARACTER_SCREEN:
            self.draw_character_screen()

    def on_key_press(self, key: int, modifiers: int):
        """
        Manage key-down events

        :param key:
        :param modifiers:
        """

        # Clear the timer for auto-repeat of movement
        self.time_since_last_move_check = None
        if key in KEYMAP_UP:
            self.up_pressed = True
        elif key in KEYMAP_CHARACTER_SCREEN:
            self.game_engine.game_state = CHARACTER_SCREEN
            print("Open character screen")
        elif key in KEYMAP_CANCEL:
            self.game_engine.game_state = NORMAL

        # Movement
        elif key in KEYMAP_DOWN:
            self.down_pressed = True
        elif key in KEYMAP_LEFT:
            self.left_pressed = True
        elif key in KEYMAP_RIGHT:
            self.right_pressed = True
        elif key in KEYMAP_UP_LEFT:
            self.up_left_pressed = True
        elif key in KEYMAP_UP_RIGHT:
            self.up_right_pressed = True
        elif key in KEYMAP_DOWN_LEFT:
            self.down_left_pressed = True
        elif key in KEYMAP_DOWN_RIGHT:
            self.down_right_pressed = True

        # Item management
        elif key in KEYMAP_PICKUP:
            self.game_engine.action_queue.extend([{"pickup": True}])
        elif key in KEYMAP_DROP_ITEM:
            self.game_engine.action_queue.extend([{"drop_item": True}])
        elif key in KEYMAP_SELECT_ITEM_1:
            self.game_engine.action_queue.extend([{"select_item": 1}])
        elif key in KEYMAP_SELECT_ITEM_2:
            self.game_engine.action_queue.extend([{"select_item": 2}])
        elif key in KEYMAP_SELECT_ITEM_3:
            self.game_engine.action_queue.extend([{"select_item": 3}])
        elif key in KEYMAP_SELECT_ITEM_4:
            self.game_engine.action_queue.extend([{"select_item": 4}])
        elif key in KEYMAP_SELECT_ITEM_5:
            self.game_engine.action_queue.extend([{"select_item": 5}])
        elif key in KEYMAP_SELECT_ITEM_6:
            self.game_engine.action_queue.extend([{"select_item": 6}])
        elif key in KEYMAP_SELECT_ITEM_7:
            self.game_engine.action_queue.extend([{"select_item": 7}])
        elif key in KEYMAP_SELECT_ITEM_8:
            self.game_engine.action_queue.extend([{"select_item": 8}])
        elif key in KEYMAP_SELECT_ITEM_9:
            self.game_engine.action_queue.extend([{"select_item": 9}])
        elif key in KEYMAP_SELECT_ITEM_0:
            self.game_engine.action_queue.extend([{"select_item": 0}])
        elif key in KEYMAP_USE_ITEM:
            self.game_engine.action_queue.extend([{"use_item": True}])

        # Save/load
        elif key == arcade.key.S:
            self.save()
        elif key == arcade.key.L:
            self.load()

        elif key in KEYMAP_USE_STAIRS:
            self.game_engine.action_queue.extend([{"use_stairs": True}])

    def on_key_release(self, key: int, modifiers: int):
        """
        Called when the user releases a key.

        :param key:
        :param modifiers:
        """
        if key in KEYMAP_UP:
            self.up_pressed = False
        elif key in KEYMAP_DOWN:
            self.down_pressed = False
        elif key in KEYMAP_LEFT:
            self.left_pressed = False
        elif key in KEYMAP_RIGHT:
            self.right_pressed = False
        elif key in KEYMAP_UP_LEFT:
            self.up_left_pressed = False
        elif key in KEYMAP_UP_RIGHT:
            self.up_right_pressed = False
        elif key in KEYMAP_DOWN_LEFT:
            self.down_left_pressed = False
        elif key in KEYMAP_DOWN_RIGHT:
            self.down_right_pressed = False

    def on_mouse_motion(self, x: float, y: float, dx: float, dy: float):
        """ Handle mouse motion, mostly just used for mouse-over text. """

        # Get current mouse position. Used elsewhere when we need it.
        self.mouse_position = x, y

        # Get the sprites at the current location
        sprite_list = arcade.get_sprites_at_point(
            (x, y), self.game_engine.cur_level.creatures)

        # See if any sprite we are hovering over deserves a mouse-over text
        self.mouse_over_text = None
        for sprite in sprite_list:
            if isinstance(sprite, Entity):
                if sprite.fighter and sprite.is_visible:
                    self.mouse_over_text = (
                        f"{sprite.name} {sprite.fighter.hp}/{sprite.fighter.max_hp}"
                    )
            else:
                raise TypeError("Sprite is not an instance of Entity class.")

    def save(self):
        """ Save the current game to disk. """
        game_dict = self.game_engine.get_dict()

        with open("game_save.json", "w") as write_file:
            json.dump(game_dict, write_file, indent=4, sort_keys=True)

        results = [{"message": "Game has been saved"}]
        self.game_engine.action_queue.extend(results)

    def load(self):
        """ Load the game from disk. """
        with open("game_save.json", "r") as read_file:
            data = json.load(read_file)

        self.game_engine.restore_from_dict(data)

    def check_for_player_movement(self):
        """
        Figure out if we should move the player or not based on keys currently
        held down.
        """

        # Player is dead, don't move her.
        if self.game_engine.player.is_dead:
            return

        # Reset the movement clock used for holding the key down for repeated movement.
        self.time_since_last_move_check = 0

        # cx and cy are the delta in movement. Start with no movement.
        cx = 0
        cy = 0

        # Adjust delta of movement based on keys pressed
        if self.up_pressed or self.up_left_pressed or self.up_right_pressed:
            cy += 1
        if self.down_pressed or self.down_left_pressed or self.down_right_pressed:
            cy -= 1

        if self.left_pressed or self.down_left_pressed or self.up_left_pressed:
            cx -= 1
        if self.right_pressed or self.down_right_pressed or self.up_right_pressed:
            cx += 1

        # If we are trying to move, pass that request to the game_engine
        if cx or cy:
            self.game_engine.move_player(cx, cy)

    def on_update(self, delta_time: float):
        """
        Manage regular updates for the game

        :param delta_time:
        """

        # --- Manage continuous movement while direction keys are held down

        # Time since last check, if we are tracking
        if self.time_since_last_move_check is not None:
            self.time_since_last_move_check += delta_time

        # Check if we should move again based on the clock, or if the clock
        # was set to None as a trigger to move immediate
        if (self.time_since_last_move_check is None
                or self.time_since_last_move_check >= REPEAT_MOVEMENT_DELAY):
            self.check_for_player_movement()

        # --- Process the action queue
        self.game_engine.process_action_queue(delta_time)
        self.game_engine.check_experience_level()