0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pygameを使って RPGを作る(19. 戦闘画面:敵の反撃とGame Over)

Posted at

概要

敵の反撃を追加
PlayerがHPゼロになった場合はGame Overにする

反撃
image.png

Playerが倒される
image.png

Game Over
image.png

Mainクラス

import pygame as pg
from settings import *

from map import Map
from groups import AllSprites
from battle import BattleScreen
from game_over import GameOver

class Game:
    def __init__(self):
        pg.init()

        # screen
        self.display_surface = pg.display.set_mode((WIDTH, HEIGHT))
        # title
        pg.display.set_caption(TITLE)

        # clock
        self.clock = pg.time.Clock()
        self.running = True

        # all sprite
        self.all_sprites = AllSprites()

        self.battle_sprites = AllSprites()

        # self.game_stage = 'main'
        self.game_stage = 'game_over'

        # バトル管理
        self.battle = None  # BattleScreenクラスのインスタンスを保持
        self.init_battle = True  # バトル画面初期化フラグ
        self.update_message_flag = False

        # バトル初期化
        self.battle = BattleScreen(self.battle_sprites)

        # 
        self.game_over_flag = False

        # Map
        self.player, self.current_map = Map(self.all_sprites).create()
        
    def run(self):
        """ゲームループ"""
        dt = self.clock.tick(FPS) / 1000

        while self.running:
            # events
            self.events()
            self.game_stage = self.player.game_stage
            if self.game_stage == 'main':
                self.main_screen(dt)

            elif self.game_stage == 'battle':
                # 敵と衝突した後の画面
                self.show_battle_screen()

            elif self.game_stage == 'game_over':
                self.show_game_over()

            pg.display.flip()

        pg.quit()

    def main_screen(self, dt):
        self.all_sprites.update(dt, self.current_map)
        self.display_surface.fill(BLUE)
        self.all_sprites.draw()

    def show_battle_screen(self):

        # バトル画面の初期化
        if self.init_battle:
            self.battle.battle_message = []
            self.init_battle = False  

            # バトル画面描画
            self.battle.draw(self.player, self.display_surface)
            self.battle_sprites.draw_battle()
        
        # 戦闘時メッセージの更新
        if self.update_message_flag:
            self.battle.update_message(self.display_surface)
            self.battle_sprites.draw_battle()
            self.update_message_flag = False

        # 戦闘コマンドの描画(マウスホーバーを検知するため、ループの外側で実装)
        if self.battle.battle_active:
            self.battle.draw_buttons()

        self.battle.status.draw_status(self.display_surface)
            

    def show_game_over(self):
        self.display_surface.fill((0, 0, 0))  # RGBで黒 (0, 0, 0)
        self.game = GameOver()
        self.game.text.draw(self.display_surface)
        self.game.text2.draw(self.display_surface)

    # メインステージ、敵の数、Playerの数上手くリセットできていない
    def reset_game_state(self):
        # 全てのスプライトを再生成
        self.all_sprites = AllSprites()
        self.battle_sprites = AllSprites()

        # プレイヤーとマップを再生成
        self.player, self.current_map = Map(self.all_sprites).create()

        # バトルの状態を完全にリセット
        self.battle = BattleScreen(self.battle_sprites)
        self.init_battle = True
        self.battle.reset()

        # ゲームステージを"main"に戻す
        self.game_stage = 'main'


    def events(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.running = False

            if event.type == pg.KEYDOWN:
                # バトル終了後、メイン画面に戻る処理
                if event.key == pg.K_SPACE and self.game_stage == 'battle':
                    if self.battle.get_battle_result() == 0:
                        self.player.game_stage = 'game_over'
                    elif self.battle.get_battle_result() == 1:
                        self.init_battle = True
                        self.battle.battle_active = True
                        self.player.game_stage = "main"  # "main"に戻す
                    
                # Game Overからメイン画面に戻る処理
                if event.key == pg.K_SPACE and self.game_stage == 'game_over':
                    self.reset_game_state()

            # BattleScreenのマウスイベントを処理
            self.update_message_flag = self.battle.handle_mouse_event(event)

if __name__ == "__main__":
    new_game = Game()
    new_game.run()
 

Battleクラス

import pygame as pg
from settings import *
from utils import Button, TextSprite
from status import PlayerStatus

class BattleMenu:
    def __init__(self, actions):
        self.display_surface = pg.display.get_surface()
        
        self.px = 30
        self.main_pos_y = [30, 80, 530]
        self.sub_pos_y = [30, 80, 130] 

        self.buttons = {
            "main": self.create_buttons(actions['main'], self.px, self.main_pos_y),
            "sub": self.create_buttons(actions['sub'], self.px, self.sub_pos_y),
        }
        
        self.currend_command = "main"

    def create_buttons(self, actions, x, y_positons):
        btn_width, btn_height = 100, 40
        return [
            Button(x, y_positons[i], btn_width, btn_height, name, action)
            for i, (name, action) in enumerate(actions)
        ]

    # メインコマンドを表示
    def show_main_commands(self):
        self.currend_command = 'main'

    def show_sub_commands(self):
        self.currend_command = 'sub'

    # 描画
    def draw_buttons(self):
        for button in self.buttons[self.currend_command]:
            button.draw(self.display_surface)

    def handle_mouse_event(self, event):
        if event.type == pg.MOUSEBUTTONDOWN:
            mouse_pos = event.pos  # クリックした位置を取得
            for button in self.buttons[self.currend_command]:
                if button.check_click(mouse_pos):  # ボタンがクリックされたか判定
                    button.action()  # ボタンに設定された関数を呼び出し
                    return True

class BattleLayout:
    def __init__(self):
        self.display_surface = pg.display.get_surface()
        self.back_ground_img = pg.image.load('../battle/bg.png')
        self.back_ground_img = pg.transform.scale(self.back_ground_img, (819, HEIGHT))

        self.bg_size = self.back_ground_img.get_size()
        self.rect = self.back_ground_img.get_rect()
        self.off_set = pg.Vector2()
        self.off_set.x = -int((self.rect.centerx - WIDTH /2))
        self.off_set.y = -int((self.rect.centery - HEIGHT /2))
    
    def draw_background(self, screen):
        self.screen = screen
        self.screen.fill((0, 0, 0))
        self.screen.blit(self.back_ground_img, self.rect.topleft + self.off_set)

    def draw_menu_backgroud(self):
        pg.draw.rect(self.display_surface, '#8E7698', [2, 2, self.off_set.x -2, HEIGHT-2])
        pg.draw.rect(self.display_surface,'#D3DED0', [0, 0, self.off_set.x, HEIGHT],5,border_radius=5)

    def draw_background_text_area(self):
        """必要最小限の透明マスクを新規作成し、配置する"""
        self.text_area_rect = pg.Rect(250, 430, self.bg_size[0] * 0.95, self.bg_size[1] * 0.3)

        # 半透明の背景色を設定
        semi_transparent_surface = pg.Surface(self.text_area_rect.size, pg.SRCALPHA)
        semi_transparent_surface.fill((0, 0, 255, 50))  # 半透明の青色
        
        # 角の描画ですこし小さくする
        self.display_surface.blit(semi_transparent_surface, 
                                  [self.text_area_rect.x + 2,
                                   self.text_area_rect.y + 2,
                                   self.text_area_rect.width -2,
                                   self.text_area_rect.height -2])

        # 枠線を再描画
        pg.draw.rect(self.display_surface, (255, 255, 255), self.text_area_rect, 3, border_radius=5)

class BattleScreen(pg.sprite.Sprite):
    def __init__(self, battle_sprites):
        super().__init__()
        self.display_surface = pg.display.get_surface()

        self.font = pg.font.Font("../battle/Meiryo.ttf", MESSAGE_FONT_SIZE)
        # self.battle_message = []

        self.enemy = None

        self.battle_sprites = battle_sprites

        # Layout
        self.layout = BattleLayout()

        self.current_command = 0

        # menu
        self.menu = BattleMenu(self.get_actions())

        # player status
        self.status = PlayerStatus((BG_SIZE_WIDTH, HEIGHT), self.battle_sprites)
        
        # messege
        self.text_sprites = TextSprite('', self.font, (255,255,255),  (0,0,255), 250, 430, self.battle_sprites)

        # Battle state
        self.battle_active = True  # 戦闘がアクティブかどうかを管理するフラグ

    def reset(self):
        # バトル関連の状態をリセット
        self.battle_message = []
        self.battle_active = True
        self.current_command = None
        self.enemy = None


    def get_actions(self):
        return {
            "main": [
                ("攻撃", self.attack),
                ("魔法", self.show_sub_commands),
                ("逃げる", self.escape),
            ],
            "sub": [
                ("ホイミ", self.hoimi),
                ("メラ", self.mera),
                ("ヒャド", self.hyado),
            ],
        }

    def attack(self):
        # 攻撃力は HP* 0.8
        damege = int(self.status.infact_status['ATK'] / 2) - int(self.enemy.mob_info['DEF']/4)
        self.enemy.mob_info['HP'] -= damege
        
        mes = f'{self.status.view_status['name']}は攻撃しました。' + \
            f'{self.enemy.name}{damege}のダメージを受けました。'
        
        self.general_message([mes])
        
        if self.enemy.mob_info['HP'] <= 0:
            mes_02 = f'{self.enemy.name}を倒しました。'
            self.general_message([mes_02])
            self.battle_active = False  # 戦闘終了フラグを設定        
        # 反撃(一回でPlayerが倒される)
        else:
            p_damege = 10
            self.status.view_status['HP'] -= p_damege
            mes = f'{self.enemy.name}の攻撃、' + \
            f'{self.status.view_status['name']}{p_damege}のダメージを受けました。'
            self.general_message([mes])
            if self.status.view_status['HP'] <=0:
                mes_02 = f'{self.status.view_status['name']}は倒れました。'
                self.general_message([mes_02])
                self.battle_active = False  # 戦闘終了フラグを設定


    def get_battle_result(self):
        if self.enemy.mob_info['HP'] <= 0:
            return 1
        elif self.status.view_status['HP'] <=0:
            return 0
        else:
            return 3

    def escape(self):
        self.battle_message.append('逃げる')

    def hoimi(self):
        self.battle_message.append('ホイミ')
        self.menu.show_main_commands()

    def mera(self):
        self.battle_message.append('メラ')
        self.menu.show_main_commands()

    def hyado(self):
        self.battle_message.append('ヒャド')
        self.menu.show_main_commands()

    # サブコマンドを表示
    def show_sub_commands(self):
        self.menu.show_sub_commands()

    def draw(self, player, screen):
        self.enemy = player.collided_enemy
        self.mob_pos =  ((WIDTH - 128) /2,HEIGHT /8)
        self.mob_surface = self.enemy.battle_surface.copy()
        # メッセージ
        self.battle_message.append(f'{self.enemy.name}が現れました!') 
        self.render_scene(screen)

    def render_scene(self, screen):
        self.layout.draw_background(screen)

        if self.battle_active:
            self.display_surface.blit(self.enemy.battle_surface, self.mob_pos)
        elif self.status.view_status['HP'] <=0:
            self.display_surface.blit(self.enemy.battle_surface, self.mob_pos)

        self.layout.draw_background_text_area()

        # ここのテキストはうまく更新できている 
        self.draw_text(screen)

        self.layout.draw_menu_backgroud()

        # player status(この部分が前回の描画したテキストが残っている)
        self.status.draw_status(screen)

    def update_message(self, screen):
        self.render_scene(screen)

    def general_message(self, message_list):
        for m in message_list:
            self.battle_message.append(m)
            if len(self.battle_message) > MAX_MESSAGE:
                del self.battle_message[0]

    def set_message(self):
        if len(self.battle_message) > MAX_MESSAGE:
            del self.battle_message[0]

        view_message = ['  ' + item for item in self.battle_message]
        return '\n'.join(view_message)

    def draw_text(self, screen):
        self.text_sprites.update_message(screen, self.set_message())

    def handle_mouse_event(self, event):
        # 戦闘中のみボタンの押下処理を有効化
        if self.battle_active:
            return self.menu.handle_mouse_event(event)
        return False
    
    def draw_buttons(self):
        self.menu.draw_buttons()

GameOverクラス

import pygame as pg

from settings import *
from utils import TextSprite

class GameOver:
    def __init__(self):
        self.game_over_sprites = pg.sprite.Group()
        self.font = pg.font.Font("../battle/Meiryo.ttf", 36)
        self.text = TextSprite('Game Over', self.font, 
                               (255,255,255),  
                               (0,0,255), 
                               WIDTH / 2 -100, HEIGHT /2, self.game_over_sprites)
        self.font = pg.font.Font("../battle/Meiryo.ttf", 20)
        self.text2 = TextSprite('SPACE KEY TO CONTINUE...', self.font, 
                               (255,255,255),  
                               (0,0,255), 
                               WIDTH / 2 -150, HEIGHT /2 + HEIGHT/4, self.game_over_sprites)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?