見出し画像

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)

解説は

あまりにも長くなったので、次回ということで😅

この記事が気に入ったらサポートをしてみませんか?