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を作る(21.Start画面)

Posted at

概要

・ゲーム開始画面を追加
・音楽BGMを追加

画面遷移ロジック

Start画面でStartボタン→フィールドマップへ
敵衝突→Battle画面
戦闘で死亡→gameover
戦闘で残存→フィールドマップへ
gameover→Start画面

Start画面
image.png

フィールドマップ
image.png

バトル画面
image.png

Game Over
image.png

Start Gameクラス

class StartMenu:
    def __init__(self, parent):
        self.parent = parent
        self.screen = self.parent.display_surface
        self.start_sprites = pg.sprite.Group()
        self.font_title = pg.font.Font("appmin.otf", 36)
        self.font_description = pg.font.Font("appmin.otf", 24)
        self.forecolor = "#FFFFFF"
        self.text = TextSprite('北境の黎明', self.font_title, 
                               self.forecolor,  
                               (0,0,255), 
                               WIDTH / 2 - 200, 50, self.start_sprites)
        
        self.speed = 50
        
        
        self.img = pg.image.load('../img/winter-forest1.jpg')
        self.back_ground_img = pg.transform.scale(self.img, (WIDTH, HEIGHT))
        self.rect = self.back_ground_img.get_rect()
        
        self.story_description = '清朝末期。歴史に稀を見る時代は混乱を極み、\n\
中原の人々は天災、戦乱、飢饉により、酷寒で漢民族の禁足の地の満州に渡った、\n\
その中、ひとりの若者が静かに動き出すのだった...'
        self.descriptions = TextAnimation(
            self.font_description,
            self.forecolor, (0,0,255),
            WIDTH*0.15 , HEIGHT /2 -200 , 
            self.speed,
            self.screen
        )
        
        self.px = WIDTH / 2
        self.pos_y = [HEIGHT /2 + 100, HEIGHT /2 + 150, HEIGHT /2 + 200]

        self.buttons = self.create_buttons(self.get_actions(), 
                                           self.px, 
                                           self.pos_y)
        
        self.counter = 0

    def get_actions(self):
        return [
            ("Start", self.start),
            ("Load", self.load),
            ("Settings", self.option),
        ]
    
    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 draw(self):
        self.screen.blit(self.back_ground_img, self.rect)
        self.text_area = pg.Rect(WIDTH*(0.15)-5, HEIGHT*0.05, WIDTH * 0.7, HEIGHT * 0.4)
        surf = pg.Surface(self.text_area.size, pg.SRCALPHA)
        surf.fill((10,15,5,128))
        self.screen.blit(surf, self.text_area)
        self.text.draw(self.screen)
        for button in self.buttons:
            button.draw(self.screen)

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

    def start(self):
        print('start')
        self.parent.reset_game_state()
        self.parent.game_stage = 'main'

    def load(self):
        print('load')

    def option(self):
        print('option')

メインクラス

import pygame as pg
from settings import *

from map import Map
from groups import AllSprites
from battle import BattleScreen
from game_over import GameOver, StartMenu
from utils import Backmusic
import os


class Game:
    def __init__(self):
        pg.mixer.init()
        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 = 'start_menu'
        self.current_stage = self.game_stage

        self.start = StartMenu(self)

        
        # バトル管理
        self.init_battle = True  # バトル画面初期化フラグ
        self.update_message_flag = False

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

        # Map
        self.player, self.current_map = Map(self, self.all_sprites).create()

        self.bgm_dict ={
            "start_menu": '../music/kaisou/winter_flut.mp3',
            "main": '../music/town/Sky-Airship.mp3',
            "battle": '../music/battle/Battle-Rosemoon.mp3',
            "game_over": '../music/kaisou/reminiscence.mp3',
        }

    def show_game_menu(self, stage, dt):
        menus = {
            "main": self.main_screen,
            "battle": self.show_battle_screen,
            "game_over": self.show_game_over,
            "start_menu": self.start_menu
        }
        return menus[stage](dt)
    
    def play_background_music(self, stage):
        """ 音楽を再生する """
        bgm_path = self.bgm_dict.get(stage)

        if bgm_path and os.path.exists(bgm_path):  # ファイルの存在チェック
            self.bgm = Backmusic(bgm_path)
            self.bgm.play()
            print(bgm_path)
        else:
            print(f"音楽ファイルが見つかりません: {bgm_path}")

        
    def run(self):
        """ゲームループ"""
        dt = self.clock.tick(FPS) /1000

        while self.running:

            # events
            self.events()
   
            self.show_game_menu(self.game_stage, dt)

            if self.current_stage != self.game_stage:
                self.play_background_music(self.game_stage)
                self.current_stage = self.game_stage

            if not pg.mixer.music.get_busy():
                self.play_background_music(self.game_stage)

            pg.display.flip()
        
        pg.quit()

    def start_menu(self, dt):
        self.display_surface.fill((BLUE))
        
        if self.start.counter >= len(self.start.story_description) * self.start.speed:
            pass
        else:
            self.start.counter += 1
        # print(self.start.counter)
        # スタート画面
        # self.start = StartMenu(self)
        self.start.draw()
        self.start.descriptions.draw_anime(self.start.story_description, self.start.counter)
        

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

    def cal_text_speed(self):
        if len(self.battle.battle_message) > MAX_MESSAGE:
            del self.battle.battle_message[0]
        if len(self.battle.battle_message) > 0:
            if self.battle.counter <= self.battle.speed * len(self.battle.battle_message[-1]):
                self.battle.counter += 1

    def replace_message(self, message):
        temp_message = []
        for m in message:
            sp = m.split('\n')
            for s in sp:
                temp_message.append(s)
                if len(temp_message) > MAX_MESSAGE:
                    del temp_message[0]

        return temp_message

    def show_battle_screen(self, dt):

        # バトル画面の初期化
        if self.init_battle:
            self.battle.battle_message = []
            self.battle.counter = 0
            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.battle.battle_message = self.replace_message(self.battle.battle_message)
            # self.battle.get_battle_result()
            self.update_message_flag = False
            self.battle.counter = 0

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

        self.battle.status.draw_status(self.display_surface)
        
        self.battle.text_sprites.update(dt)

        self.cal_text_speed()
        
        if len(self.battle.battle_message) > 0:
            # 戦闘メッセージの描画
            self.battle.text_sprites.draw(self.battle.battle_message, self.battle.counter)
        

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

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

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

        # バトルの状態を完全にリセット
        self.battle = BattleScreen(self, 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':
                    self.battle.get_battle_result()

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

            self.start.handle_mouse_event(event)

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

感想

ゲームコードを書いているとどんどん冗長になってしまうので、
1.とりあえず、動くものをつくる
2.コードをクラス分割
3.コードスリム化

今回は、バトル画面終了判定について、クラス内へ移動し、クラス内で画面遷移できるようにした。
メリットとしては、メインクラスのスリム化ができて、画面遷移は、各クラス内に移譲することで、
個別管理ができるので、問題があった時の影響範囲は小さくなると考えられる。

before

def events(self):
    # バトル終了後、メイン画面に戻る処理
    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()

after

if event.key == pg.K_SPACE and self.game_stage == 'battle':
    self.battle.get_battle_result()

battleクラス内
before

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

after

def get_battle_result(self):
    if self.enemy.mob_info['HP'] <= 0:
        self.parent.init_battle = True
        self.battle_active = True
        self.parent.game_stage = 'main'
    elif self.status.view_status['HP'] == 0:
        self.parent.game_stage = 'game_over'
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?