概要
・ゲーム開始画面を追加
・音楽BGMを追加
画面遷移ロジック
Start画面でStartボタン→フィールドマップへ
敵衝突→Battle画面
戦闘で死亡→gameover
戦闘で残存→フィールドマップへ
gameover→Start画面
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'