pygameで始めるpythonプログラム 5(クラスを実装していく)
はじめに
前回の記事でクラス化を考えてみました。今回はクラスを実装し、作りながらクラス構造も変えていきました。
いきなりですが改造した結果
前回からだいぶ改造しています。小出しにするつもりが大幅に変えてしまいました。。。改造したコードはこんな感じです。
import pygame
import os,sys
# 定数の設定
SCREEN_WIDTH = 1000
SCREEN_HEIGHT = 800
FLOOR_HEIGHT = 88
# リソースファイル設定
RES_PATH = "resources" # リソースを保存するフォルダ名
ENEMEY_FILES = ["enemy.png"] # 敵キャラまずは1つ
BULLET_FILES = ["bullet1.png", "bullet2.png"] # キャラクターが撃つ弾の画像
CHARACTER_FILES = ["character1.png", "character2.png"] # キャラクター画像
BACKGROUND_FILE = "background.png" # 背景画像
# 登場オブジェクトクラス
class GameObject:
def __init__(self, x, y, width, height, image_path):
self.image = pygame.image.load(image_path)
self.image = pygame.transform.scale(self.image, (width, height))
self.obj = self.image.get_rect(topleft=(x, y))
def draw(self, screen):
screen.blit(self.image, self.obj)
# キャラクタークラス
class Character(GameObject):
def __init__(self, x, y, width, height, image_path, index):
super().__init__(x, y, width, height, image_path)
self.jump_speed = -20
self.gravity = 1
self.velocity_y = 0
self.is_jumping = False
self.index = index
self.direction = 1
self.beams = []
self.attacking = False
def jump(self):
if not self.is_jumping:
self.velocity_y = self.jump_speed
self.is_jumping = True
def update(self):
# キャラクターの画面更新処理
key_actions = {}
if self.index == 0:
key_actions = {'LEFT' : pygame.K_LEFT, 'RIGHT' : pygame.K_RIGHT, 'JUMP' : pygame.K_UP, 'ATTACK': pygame.K_DOWN}
elif self.index == 1:
key_actions = {'LEFT' : pygame.K_a, 'RIGHT' : pygame.K_d, 'JUMP' : pygame.K_w, 'ATTACK': pygame.K_s}
# キー判定
press_key = pygame.key.get_pressed()
if press_key[key_actions['LEFT']]:
if self.direction == 1:
# 右向きだった画像を反転させる
self.image = pygame.transform.flip(self.image, True, False)
self.move(-5, 0)
self.direction = -1
elif press_key[key_actions['RIGHT']]:
if self.direction == -1:
# 左向きだった画像を反転させる
self.image = pygame.transform.flip(self.image, True, False)
self.move(5, 0)
self.direction = 1
if press_key[key_actions['JUMP']]:
self.jump()
if press_key[key_actions['ATTACK']]:
if self.attacking == False:
self.beams.append(self.attack(self.index))
self.attacking = True
# 重力を適用
self.velocity_y += self.gravity
self.obj.y += self.velocity_y
# 地面に着地したときの処理
if self.obj.bottom >= SCREEN_HEIGHT - FLOOR_HEIGHT:
self.obj.bottom = SCREEN_HEIGHT - FLOOR_HEIGHT
self.is_jumping = False
self.velocity_y = 0
# ビーム更新
for i, beam in enumerate(self.beams):
# 画面から飛び出したビームはリストから消える
if beam.x > SCREEN_WIDTH or beam.x < 0:
self.beams.pop(i)
beam.update()
def move(self, dx, dy):
# 横移動
if self.obj.x + dx < 0:
self.obj.x = 0
elif self.obj.x + dx > SCREEN_WIDTH - 50:
self.obj.x = SCREEN_WIDTH - 50
else:
self.obj.x += dx
# 縦移動
self.obj.y += dy
def attack(self, beam_type):
# ビームを生成
beam_x = self.obj.right if self.direction == 1 else self.obj.left
beam_y = self.obj.centery
# 弾のリソースを設定
attack_res = os.path.join(RES_PATH, BULLET_FILES[beam_type])
return Beam(beam_x, beam_y, 50, 10, attack_res, self.direction)
def draw(self, screen):
super().draw(screen)
for beam in self.beams:
beam.draw(screen)
def key_released(self):
self.attacking = False
# Beamクラスの定義
class Beam(GameObject):
def __init__(self, x, y, width, height, image_path, direction):
super().__init__(x, y, width, height, image_path)
self.speed = 10 * direction
self.direction = direction
self.x = x
self.y = y
def update(self):
self.obj.x += self.speed
self.x = self.obj.x
# 障害物オブジェクト
class Obstruction(GameObject):
def __init__(self, x, y, width, height, image_path):
super().__init__(x, y, width, height, image_path)
def check_collision(self, character):
return self.obj.colliderect(character.rect)
# ゲームのメインクラス
class Game:
def __init__(self):
# Pygameの初期化
pygame.init()
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("ゲームタイトル")
# フレームレート設定のためのクロックオブジェクト作成
self.clock = pygame.time.Clock()
# 障害物の作成
self.obstruction = Obstruction(SCREEN_WIDTH - 200, SCREEN_HEIGHT - FLOOR_HEIGHT - 170 , 200, 180, os.path.join(RES_PATH, ENEMEY_FILES[0]))
# キャラクターの作成
self.character = []
self.character.append(Character(50, SCREEN_HEIGHT - FLOOR_HEIGHT , 70, 100, os.path.join(RES_PATH, CHARACTER_FILES[0]), 0))
self.character.append(Character(150, SCREEN_HEIGHT - FLOOR_HEIGHT , 80, 110, os.path.join(RES_PATH,CHARACTER_FILES[1]), 1))
# 背景画像をロード
self.background = pygame.image.load(os.path.join(RES_PATH, BACKGROUND_FILE))
self.background = pygame.transform.scale(self.background, (SCREEN_WIDTH, SCREEN_HEIGHT))
def run(self):
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
pass
elif event.type == pygame.KEYUP:
for i, _ in enumerate(self.character):
self.character[i].key_released()
elif event.type == pygame.MOUSEBUTTONDOWN:
pass
elif event.type == pygame.MOUSEBUTTONUP:
pass
# 画面に背景画像を描画
self.screen.fill((255, 255, 255))
self.screen.blit(self.background, (0, 0))
self.obstruction.draw(self.screen)
# キャラクターの更新と描画
for i, _ in enumerate(self.character):
self.character[i].update()
self.character[i].draw(self.screen)
# 画面を更新
pygame.display.flip()
# フレームレートを制御
self.clock.tick(60)
# ゲームの開始
if __name__ == "__main__":
game = Game()
game.run()
前回からの変更点
だいぶ変わってますが、ポイントは以下の通り。
キャラクターなど画像を使いたいのでそのリソースファイルを保存するフォルダ名とファイル名を定数で定義
# リソースファイル設定
RES_PATH = "resources" # リソースを保存するフォルダ名
ENEMEY_FILES = ["enemy.png"] # 敵キャラまずは1つ
BULLET_FILES = ["bullet1.png", "bullet2.png"] # キャラクターが撃つ弾の画像
CHARACTER_FILES = ["character1.png", "character2.png"] # キャラクター画像
BACKGROUND_FILE = "background.png" # 背景画像
ゲームに登場するものをGameObjectクラスとして定義。キャラクターとかはこのクラスを継承して使うことで、ゲームに登場するモノに共通する処理を担当する。オブジェクトを登場(__init__)させたり画面更新のためにオブジェクトの位置を変更(draw)したり。
# 登場オブジェクトクラス
class GameObject:
def __init__(self, x, y, width, height, image_path):
self.image = pygame.image.load(image_path)
self.image = pygame.transform.scale(self.image, (width, height))
self.obj = self.image.get_rect(topleft=(x, y))
def draw(self, screen):
screen.blit(self.image, self.obj)
キャラクター(Character)、キャラクターの出す攻撃(Beam)、障害物(Obstruction)クラスは、GameObjectクラスを継承して必要なメソッドを実装していきました。
# キャラクタークラス
class Character(GameObject):
def __init__(self, x, y, width, height, image_path, index):
super().__init__(x, y, width, height, image_path)
self.jump_speed = -20
self.gravity = 1
self.velocity_y = 0
self.is_jumping = False
self.index = index
self.direction = 1
self.beams = []
self.attacking = False
def jump(self):
if not self.is_jumping:
self.velocity_y = self.jump_speed
self.is_jumping = True
def update(self):
# キャラクターの画面更新処理
key_actions = {}
if self.index == 0:
key_actions = {'LEFT' : pygame.K_LEFT, 'RIGHT' : pygame.K_RIGHT, 'JUMP' : pygame.K_UP, 'ATTACK': pygame.K_DOWN}
elif self.index == 1:
key_actions = {'LEFT' : pygame.K_a, 'RIGHT' : pygame.K_d, 'JUMP' : pygame.K_w, 'ATTACK': pygame.K_s}
# キー判定
press_key = pygame.key.get_pressed()
if press_key[key_actions['LEFT']]:
if self.direction == 1:
# 右向きだった画像を反転させる
self.image = pygame.transform.flip(self.image, True, False)
self.move(-5, 0)
self.direction = -1
elif press_key[key_actions['RIGHT']]:
if self.direction == -1:
# 左向きだった画像を反転させる
self.image = pygame.transform.flip(self.image, True, False)
self.move(5, 0)
self.direction = 1
if press_key[key_actions['JUMP']]:
self.jump()
if press_key[key_actions['ATTACK']]:
if self.attacking == False:
self.beams.append(self.attack(self.index))
self.attacking = True
# 重力を適用
self.velocity_y += self.gravity
self.obj.y += self.velocity_y
# 地面に着地したときの処理
if self.obj.bottom >= SCREEN_HEIGHT - FLOOR_HEIGHT:
self.obj.bottom = SCREEN_HEIGHT - FLOOR_HEIGHT
self.is_jumping = False
self.velocity_y = 0
# ビーム更新
for i, beam in enumerate(self.beams):
# 画面から飛び出したビームはリストから消える
if beam.x > SCREEN_WIDTH or beam.x < 0:
self.beams.pop(i)
beam.update()
def move(self, dx, dy):
# 横移動
if self.obj.x + dx < 0:
self.obj.x = 0
elif self.obj.x + dx > SCREEN_WIDTH - 50:
self.obj.x = SCREEN_WIDTH - 50
else:
self.obj.x += dx
# 縦移動
self.obj.y += dy
def attack(self, beam_type):
# ビームを生成
beam_x = self.obj.right if self.direction == 1 else self.obj.left
beam_y = self.obj.centery
# 弾のリソースを設定
attack_res = os.path.join(RES_PATH, BULLET_FILES[beam_type])
return Beam(beam_x, beam_y, 50, 10, attack_res, self.direction)
def draw(self, screen):
super().draw(screen)
for beam in self.beams:
beam.draw(screen)
def key_released(self):
self.attacking = False
# Beamクラスの定義
class Beam(GameObject):
def __init__(self, x, y, width, height, image_path, direction):
super().__init__(x, y, width, height, image_path)
self.speed = 10 * direction
self.direction = direction
self.x = x
self.y = y
def update(self):
self.obj.x += self.speed
self.x = self.obj.x
# Beamクラスの定義
class Beam(GameObject):
def __init__(self, x, y, width, height, image_path, direction):
super().__init__(x, y, width, height, image_path)
self.speed = 10 * direction
self.direction = direction
self.x = x
self.y = y
def update(self):
self.obj.x += self.speed
self.x = self.obj.x
# 障害物オブジェクト
class Obstruction(GameObject):
def __init__(self, x, y, width, height, image_path):
super().__init__(x, y, width, height, image_path)
def check_collision(self, character):
return self.obj.colliderect(character.rect)
Gameクラスから必要なオブジェクトを作成(インスタンス化)し、メソッドを呼び出しています。
# ゲームのメインクラス
class Game:
def __init__(self):
# Pygameの初期化
pygame.init()
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("ゲームタイトル")
# フレームレート設定のためのクロックオブジェクト作成
self.clock = pygame.time.Clock()
# 障害物の作成
self.obstruction = Obstruction(SCREEN_WIDTH - 200, SCREEN_HEIGHT - FLOOR_HEIGHT - 170 , 200, 180, os.path.join(RES_PATH, ENEMEY_FILES[0]))
# キャラクターの作成
self.character = []
self.character.append(Character(50, SCREEN_HEIGHT - FLOOR_HEIGHT , 70, 100, os.path.join(RES_PATH, CHARACTER_FILES[0]), 0))
self.character.append(Character(150, SCREEN_HEIGHT - FLOOR_HEIGHT , 80, 110, os.path.join(RES_PATH,CHARACTER_FILES[1]), 1))
# 背景画像をロード
self.background = pygame.image.load(os.path.join(RES_PATH, BACKGROUND_FILE))
self.background = pygame.transform.scale(self.background, (SCREEN_WIDTH, SCREEN_HEIGHT))
def run(self):
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
pass
elif event.type == pygame.KEYUP:
for i, _ in enumerate(self.character):
self.character[i].key_released()
elif event.type == pygame.MOUSEBUTTONDOWN:
pass
elif event.type == pygame.MOUSEBUTTONUP:
pass
# 画面に背景画像を描画
self.screen.fill((255, 255, 255))
self.screen.blit(self.background, (0, 0))
self.obstruction.draw(self.screen)
# キャラクターの更新と描画
for i, _ in enumerate(self.character):
self.character[i].update()
self.character[i].draw(self.screen)
# 画面を更新
pygame.display.flip()
# フレームレートを制御
self.clock.tick(60)
解説は
あまりにも長くなったので、次回ということで😅