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)
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()